diff --git a/examples/ASTRAExample.py b/examples/ASTRAExample.py index 75f0476..2668303 100644 --- a/examples/ASTRAExample.py +++ b/examples/ASTRAExample.py @@ -23,34 +23,38 @@ # #----------------------------------------------------------------------- -import mrfbp -from mrfbp.ASTRAProjector import ASTRAProjector2D import astra import numpy as np +# Register MR-FBP plugin with ASTRA +import mrfbp +astra.plugin.register(mrfbp.plugin) + # Create ASTRA geometries vol_geom = astra.create_vol_geom(256,256) proj_geom = astra.create_proj_geom('parallel',1.0,256,np.linspace(0,np.pi,32,False)) -# Create the ASTRA projector -p = ASTRAProjector2D(proj_geom,vol_geom) +# Create the ASTRA projector (change 'linear' to 'cuda' to use GPU) +pid = astra.create_projector('linear', proj_geom, vol_geom) +p = astra.OpTomo(pid) # Load the phantom from disk testPhantom = np.load('phantom.npy') # Calculate the forward projection of the phantom -testSino = p*testPhantom +testSino = (p*testPhantom).reshape(p.sshape) # Add some noise to the sinogram testSino = astra.add_noise_to_sino(testSino,10**4) -# Create the MR-FBP Reconstructor -rec = mrfbp.Reconstructor(p) - # Reconstruct the image using MR-FBP, FBP, and SIRT. -mrRec = rec.reconstruct(testSino) -fbpRec = p.reconstruct('FBP_CUDA',testSino) -sirtRec = p.reconstruct('SIRT_CUDA',testSino,200) +mrRec = p.reconstruct('MR-FBP',testSino) +if astra.projector.is_cuda(pid): + fbpRec = p.reconstruct('FBP_CUDA',testSino) + sirtRec = p.reconstruct('SIRT_CUDA',testSino,200) +else: + fbpRec = p.reconstruct('FBP',testSino) + sirtRec = p.reconstruct('SIRT',testSino,200) # Show the different reconstructions on screen import pylab diff --git a/mrfbp/ASTRAProjector.py b/mrfbp/ASTRAProjector.py deleted file mode 100644 index e70b391..0000000 --- a/mrfbp/ASTRAProjector.py +++ /dev/null @@ -1,217 +0,0 @@ -#----------------------------------------------------------------------- -#Copyright 2014 Daniel M. Pelt -# -#Contact: D.M.Pelt@cwi.nl -#Website: http://www.dmpelt.com -# -# -#This file is part of the PyMR-FBP, a Python implementation of the -#MR-FBP tomographic reconstruction method. -# -#PyMR-FBP is free software: you can redistribute it and/or modify -#it under the terms of the GNU General Public License as published by -#the Free Software Foundation, either version 3 of the License, or -#(at your option) any later version. -# -#PyMR-FBP is distributed in the hope that it will be useful, -#but WITHOUT ANY WARRANTY; without even the implied warranty of -#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -#GNU General Public License for more details. -# -#You should have received a copy of the GNU General Public License -#along with PyMR-FBP. If not, see . -# -#----------------------------------------------------------------------- -import astra as at -import math -import numpy as np - -class ASTRAProjector2DTranspose(): - """Implements the ``proj.T`` functionality. - - Do not use directly, since it can be accessed as member ``.T`` of - an :class:`ASTRAProjector2D` object. - - """ - def __init__(self,parentProj): - self.parentProj = parentProj - - def __mul__(self,data): - return self.parentProj.backProject(data) - -class ASTRAProjector2D(object): - - """Implementation of the projector interface using the ASTRA toolbox with CUDA. - - A projector needs to implement: - - * Forward projecting - * Back projecting - * Creating a FBP reconstruction with a custom filter - - You can use this class as an abstracted weight matrix :math:`W`: multiplying an instance - ``proj`` of this class by an image results in a forward projection of the image, and multiplying - ``proj.T`` by a sinogram results in a backprojection of the sinogram:: - - proj = ASTRAProjector2D(...) - fp = proj*image - bp = proj.T*sinogram - - :param proj_geom: The projection geometry. - :type proj_geom: :class:`dict` - :param vol_geom: The volume geometry. - :type vol_geom: :class:`dict` - :param offsets: Optional offsets for the detectors - :type offsets: :class:`numpy.ndarray` - """ - - def __init__(self,proj_geom,vol_geom,offsets=None): - self.vol_geom = vol_geom - self.recSize = vol_geom['GridColCount'] - self.angles = proj_geom['ProjectionAngles'] - self.nDet = proj_geom['DetectorCount'] - nexpow = int(pow(2, math.ceil(math.log(2*self.nDet, 2)))) - self.filterSize = nexpow/2 +1 - self.nProj = self.angles.shape[0] - self.proj_geom = proj_geom - - if not offsets==None: - self.proj_geom['option'] = {} - self.proj_geom['option']['ExtraDetectorOffset'] = offsets - filt_proj_geom['option'] = {} - filt_proj_geom['option']['ExtraDetectorOffset'] = offsets - - filt_proj_geom = at.create_proj_geom('parallel',1.0,self.filterSize,self.angles) - self.filt_id = at.data2d.create('-sino', filt_proj_geom, 0) - self.sino_id = at.data2d.create('-sino', self.proj_geom, 0) - self.vol_id = at.data2d.create('-vol',self.vol_geom,0) - forwProjString = 'FP_CUDA' - backProjString = 'BP_CUDA' - cfg = at.astra_dict(backProjString) - cfg['ProjectionDataId'] = self.sino_id - cfg['ReconstructionDataId'] = self.vol_id - self.backProjAlgorithm = at.algorithm.create(cfg) - - cfg = at.astra_dict(forwProjString) - cfg['ProjectionDataId'] = self.sino_id - cfg['VolumeDataId'] = self.vol_id - self.forwProjAlgorithm = at.algorithm.create(cfg) - - self.T = ASTRAProjector2DTranspose(self) - - self.__setOutCircle() - - def __setOutCircle(self): - '''Creates a :class:`numpy.ndarray` mask of a circle''' - xx, yy = np.mgrid[:self.recSize, :self.recSize] - mid = (self.recSize-1.)/2. - circle = (xx - mid) ** 2 + (yy - mid) ** 2 - bnd = self.recSize**2/4. - self.outCircle=mask_id = at.data2d.create('-vol', self.vol_geom, np.array(circle<=bnd,dtype=np.float)) - - def backProject(self,sinogram): - """Backproject a sinogram. - - :param sinogram: The sinogram data - :type sinogram: :class:`numpy.ndarray` - :returns: :class:`numpy.ndarray` -- The backprojection. - - """ - at.data2d.store(self.sino_id,sinogram) - at.algorithm.run(self.backProjAlgorithm) - return at.data2d.get(self.vol_id) - - def forwProject(self,image): - """Forward project an image. - - :param image: The image data. - :type image: :class:`numpy.ndarray` - :returns: :class:`numpy.ndarray` -- The forward projection. - - """ - at.data2d.store(self.vol_id,image) - at.algorithm.run(self.forwProjAlgorithm) - return at.data2d.get(self.sino_id) - - def reconstructWithFullFilter(self,sinogram,filt): - """Create a FBP reconstruction of the sinogram with a custom filter - - :param sinogram: The sinogram data - :type sinogram: :class:`numpy.ndarray` - :param filt: 2D custom filter - :type filt: :class:`numpy.ndarray` - :returns: :class:`numpy.ndarray` -- The reconstruction. - - """ - at.data2d.store(self.sino_id,sinogram) - at.data2d.store(self.filt_id,filt) - cfg = at.astra_dict('FBP_CUDA') - cfg['ProjectionDataId'] = self.sino_id - cfg['ReconstructionDataId'] = self.vol_id - cfg['FilterType'] = 'sinogram' - cfg['FilterSinogramId'] = self.filt_id - recoAlg = at.algorithm.create(cfg) - at.algorithm.run(recoAlg,1) - at.algorithm.delete(recoAlg) - return at.data2d.get(self.vol_id) - - def reconstructWithFilter(self,sinogram,filt): - """Create a FBP reconstruction of the sinogram with a custom filter - - :param sinogram: The sinogram data - :type sinogram: :class:`numpy.ndarray` - :param filt: 1D custom filter - :type filt: :class:`numpy.ndarray` - :returns: :class:`numpy.ndarray` -- The reconstruction. - - """ - at.data2d.store(self.sino_id,sinogram) - at.data2d.store(self.filt_id,np.tile(filt,(self.nProj,1))) - cfg = at.astra_dict('FBP_CUDA') - cfg['ProjectionDataId'] = self.sino_id - cfg['ReconstructionDataId'] = self.vol_id - cfg['FilterType'] = 'sinogram' - cfg['FilterSinogramId'] = self.filt_id - recoAlg = at.algorithm.create(cfg) - at.algorithm.run(recoAlg,1) - at.algorithm.delete(recoAlg) - return at.data2d.get(self.vol_id) - - def reconstruct(self,method,sinogram,nIters=1,fbpfilter=None,maskCircle=False): - """Helper function to reconstruct a sinogram using the ASTRA toolbox. - - This function does not have to be implemented by other projectors, as - it is not used by PyNN-FBP. - - :param method: Name of the reconstruction algorithm. - :type method: :class:`string` - :param sinogram: The sinogram data - :type sinogram: :class:`numpy.ndarray` - :param nIters: Number of iterations to run. - :type nIters: :class:`int` - :param fbpfilter: Optional string to specify FBP filter (hamming, hann, etc) - :type fbpfilter: :class:`string` - :param maskCircle: Whether to reconstruct only inside unit circle (default: ``False``) - :type maskCircle: :class:`bool` - :returns: :class:`numpy.ndarray` -- The reconstruction. - - """ - cfg = at.astra_dict(method) - if not 'CUDA' in method: raise Exception('Use CUDA algorithms only') - cfg['ProjectionDataId'] = self.sino_id - cfg['ReconstructionDataId'] = self.vol_id - if not fbpfilter==None: - cfg['FilterType']=fbpfilter - if maskCircle: - cfg['options'] = {} - cfg['options']['ReconstructionMaskId'] = self.outCircle - recoAlg = at.algorithm.create(cfg) - at.data2d.store(self.sino_id,sinogram) - at.data2d.store(self.vol_id,0) - at.algorithm.run(recoAlg,nIters) - at.algorithm.delete(recoAlg) - return at.data2d.get(self.vol_id) - - def __mul__(self,data): - return self.forwProject(data) - diff --git a/mrfbp/Reconstructor.py b/mrfbp/Reconstructor.py deleted file mode 100644 index 2a8b032..0000000 --- a/mrfbp/Reconstructor.py +++ /dev/null @@ -1,151 +0,0 @@ -#----------------------------------------------------------------------- -#Copyright 2014 Daniel M. Pelt -# -#Contact: D.M.Pelt@cwi.nl -#Website: http://www.dmpelt.com -# -# -#This file is part of the PyMR-FBP, a Python implementation of the -#MR-FBP tomographic reconstruction method. -# -#PyMR-FBP is free software: you can redistribute it and/or modify -#it under the terms of the GNU General Public License as published by -#the Free Software Foundation, either version 3 of the License, or -#(at your option) any later version. -# -#PyMR-FBP is distributed in the hope that it will be useful, -#but WITHOUT ANY WARRANTY; without even the implied warranty of -#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -#GNU General Public License for more details. -# -#You should have received a copy of the GNU General Public License -#along with PyMR-FBP. If not, see . -# -#----------------------------------------------------------------------- - -from . import Reductors -import numpy as np -import numpy.linalg as na -import scipy.ndimage.filters as snf - -class Reconstructor: - '''The standard MR-FBP reconstructor. - - :param projector: Projector object that implements reconstructWithFilter - :param reductor: Reductor to use, if ``None``, ``Reductors.LogSymReductor`` is used - :param projectorFP: Optional different projector to use for final forward projection. - ''' - def __init__(self,projector,reductor=None,projectorFP=None): - self.projector = projector - if projectorFP==None: - self.projectorFP = self.projector - else: - self.projectorFP = projectorFP - if reductor==None: - reductor = Reductors.LogSymReductor(self.projector.filterSize,self.projectorFP.filterSize) - self.reductor = reductor - self.__setOutCircle() - - def __setOutCircle(self): - '''Creates a :class:`numpy.ndarray` mask of a circle''' - xx, yy = np.mgrid[:self.projector.recSize, :self.projector.recSize] - mid = (self.projector.recSize-1.)/2. - circle = (xx - mid) ** 2 + (yy - mid) ** 2 - bnd = self.projector.recSize**2/4. - self.outCircle=circle>bnd - - def _calca(self,sinogram): - '''Returns the MR-FBP system matrix A - - :param sinogram: Sinogram to calculate A with - :type sinogram: :class:`numpy.ndarray` - ''' - a = np.zeros((self.projectorFP.nDet*self.projectorFP.nProj,int(self.reductor.outSize)),dtype=np.float32) - for i in xrange(self.reductor.outSize): - img = self.projector.reconstructWithFilter(sinogram,self.reductor.filters[:,i]) - img[self.outCircle]=0 - a[:,i] = self.projectorFP.forwProject(img).flatten() - return a - - def reconstruct(self,sinogram,cSinogram=None): - '''Returns MR-FBP reconstruction - - :param sinogram: Sinogram to reconstruct - :type sinogram: :class:`numpy.ndarray` - :param cSinogram: Optional other sinogram to use as right-hand side. - :type cSinogram: :class:`numpy.ndarray` - ''' - if cSinogram==None: cSinogram=sinogram - a = self._calca(sinogram) - out = na.lstsq(a,cSinogram.flatten()) - f = self.reductor.getFilter(out[0]) - self.f = f - return self.projector.reconstructWithFilter(sinogram,f) - -class ReconstructorGradient: - '''The MR-FBP_GM reconstructor. - - :param projector: Projector object that implements reconstructWithFilter - :param reductor: Reductor to use, if ``None``, ``Reductors.LogSymReductor`` is used - :param projectorFP: Optional different projector to use for final forward projection. - ''' - def __init__(self,projector,reductor=None,projectorFP=None): - self.projector = projector - if projectorFP==None: - self.projectorFP = self.projector - else: - self.projectorFP = projectorFP - if reductor==None: - reductor = Reductors.LogSymReductor(self.projector.filterSize,self.projectorFP.filterSize) - self.reductor = reductor - self.__setOutCircle() - - def __setOutCircle(self): - '''Creates a :class:`numpy.ndarray` mask of a circle''' - xx, yy = np.mgrid[:self.projector.recSize, :self.projector.recSize] - mid = (self.projector.recSize-1.)/2. - circle = (xx - mid) ** 2 + (yy - mid) ** 2 - bnd = self.projector.recSize**2/4. - self.outCircle=circle>bnd - - def _calca(self,sinogram,lam): - '''Returns the MR-FBP_GM system matrix A - - :param sinogram: Sinogram to calculate A with - :type sinogram: :class:`numpy.ndarray` - :param lam: Lambda to relative weight to give to gradient error - :type lam: :class:`float` - ''' - nfp = self.projectorFP.nDet*self.projectorFP.nProj - ngr = self.projector.nDet*self.projector.nDet - a = np.zeros((nfp+2*ngr,int(self.reductor.outSize)),dtype=np.float32) - for i in xrange(self.reductor.outSize): - img = self.projector.reconstructWithFilter(sinogram,self.reductor.filters[:,i]) - xs = snf.sobel(img,0) - ys = snf.sobel(img,1) - img[self.outCircle]=0 - xs[self.outCircle]=0 - ys[self.outCircle]=0 - a[0:nfp,i] = self.projectorFP.forwProject(img).flatten() - a[nfp:nfp+ngr,i] = lam*xs.flatten() - a[nfp+ngr:nfp+2*ngr,i] = lam*ys.flatten() - return a - - def reconstruct(self,sinogram,lam,cSinogram=None): - '''Returns MR-FBP_GM reconstruction - - :param sinogram: Sinogram to reconstruct - :type sinogram: :class:`numpy.ndarray` - :param lam: Lambda to relative weight to give to gradient error - :type lam: :class:`float` - :param cSinogram: Optional other sinogram to use as right-hand side. - :type cSinogram: :class:`numpy.ndarray` - ''' - - if cSinogram==None: cSinogram=sinogram - a = self._calca(sinogram,lam) - out = na.lstsq(a,np.hstack((cSinogram.flatten(),np.zeros(2*self.projector.nDet*self.projector.nDet)))) - f = self.reductor.getFilter(out[0]) - self.f = f - return self.projector.reconstructWithFilter(sinogram,f) - diff --git a/mrfbp/Reductors.py b/mrfbp/Reductors.py deleted file mode 100644 index 04b23a4..0000000 --- a/mrfbp/Reductors.py +++ /dev/null @@ -1,84 +0,0 @@ -#----------------------------------------------------------------------- -#Copyright 2014 Daniel M. Pelt -# -#Contact: D.M.Pelt@cwi.nl -#Website: http://www.dmpelt.com -# -# -#This file is part of the PyMR-FBP, a Python implementation of the -#MR-FBP tomographic reconstruction method. -# -#PyMR-FBP is free software: you can redistribute it and/or modify -#it under the terms of the GNU General Public License as published by -#the Free Software Foundation, either version 3 of the License, or -#(at your option) any later version. -# -#PyMR-FBP is distributed in the hope that it will be useful, -#but WITHOUT ANY WARRANTY; without even the implied warranty of -#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -#GNU General Public License for more details. -# -#You should have received a copy of the GNU General Public License -#along with PyMR-FBP. If not, see . -# -#----------------------------------------------------------------------- - -import math -import numpy as np - -class Reductor(object): - '''Base object of a ``Reductor``, that takes input data and reduces it. - - Implementing objects should define `outSize`, the number of elements after - reduction, and a ``filters`` :class:`numpy.ndarray` of size ``(inSize,outSize)``, where - each row is a basis vector in Fourier space. - - :param inSize: Input size of vectors. - :type inSize: :class:`int` - ''' - def __init__(self,inSize): - self.size = inSize - self.inSize = self.size - def getFilter(self,weights): - '''Returns actual FBP filters, given the resulting weights of a trained neural network.''' - return np.dot(self.filters,weights) - -class LogSymReductor(Reductor): - '''An implementation of a ``Reductor`` with exponentially growing bin widths, and symmetric bins. - - :param nLinear: Number of bins of width 1 before starting exponential growth.' - :type nLinear: :class:`int` - ''' - def __init__(self,size,fpSize,nLinear=2): - Reductor.__init__(self,size) - self.name="LogSym" - cW=0 - nW=0 - width=1 - nL = nLinear - while cW<(fpSize-1)/2: - if nL>0: - nL-=1 - cW += 1 - else: - cW += width - width*=2 - nW+=1 - - self.filters = np.zeros((size,nW)) - x = np.linspace(0,2*np.pi,size,False) - cW=0 - nW=0 - width=1 - nL = nLinear - while cW<(fpSize-1)/2: - if nL>0: - nL-=1 - eW = cW+1 - else: - eW = cW+width - width*=2 - self.filters[:,nW] += np.cos(np.outer(np.arange(cW,eW),x)).sum(0) - cW=eW - nW+=1 - self.outSize = nW diff --git a/mrfbp/__init__.py b/mrfbp/__init__.py index 6729c2d..78f61a5 100644 --- a/mrfbp/__init__.py +++ b/mrfbp/__init__.py @@ -23,5 +23,4 @@ # #----------------------------------------------------------------------- -from .Reconstructor import * from .astra_plugin import plugin \ No newline at end of file