Skip to content

Commit

Permalink
demo to reconstruct with nltv prior
Browse files Browse the repository at this point in the history
  • Loading branch information
dkazanc committed Dec 6, 2018
1 parent 04e5caf commit 9081df9
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 13 deletions.
10 changes: 9 additions & 1 deletion INSTALLATION
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Here we provide detailed installation information for the following dependencies
## ASTRA-toolbox
---------- can be downloaded at https://www.astra-toolbox.com/downloads/index.html and installed according to the documentation. In Matlab provide path to the package.

## Software dependencies for FISTA-Tomo:
## Software dependencies for FISTA-Tomo (Matlab advices):

* [CCPi-Regularisation Toolkit](https://github.com/vais-ral/CCPi-Regularisation-Toolkit)
---------- uncompress the content into "Supplementary/CCPi-Regularisation-Toolkit/" folder.
Expand All @@ -18,3 +18,11 @@ Here we provide detailed installation information for the following dependencies
1. Start matlab and proceed to the folder "supplementary/TomoPhantom/matlab/install"
2. Run compile_mex_linux.m or compile_mex_windows.m to install the package


## Installation in Python (conda):
Install with `conda install -c dkazanc fista-tomo` or build with:

or build yourself:

conda build Wrappers/Python/conda-recipe --numpy 1.12 --python 3.5
conda install fista-tomo --use-local --force
11 changes: 7 additions & 4 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# Regularised FISTA-type iterative reconstruction algorithm for X-ray tomographic reconstruction with highly inaccurate measurements

**This software supports research published in the following journal papers [1,2,3] with applications in [4-6]. Software depends on several software packages and requires a GPU (Nvidia) card to operate. FISTA-tomo is implemented in both MATLAB and Python (work in progress).**
**This software supports research published in the following journal papers [1,2,3] with applications in [4-6]. Software depends on several software packages and requires a GPU (Nvidia) card to operate. FISTA-tomo is implemented in both MATLAB and Python.**

<div align="center">
<img src="docs/images/recsFISTA_stud.png" height="216"><br>
</div>
<div align="center">
<img src="docs/images/TV_vs_NLTV.jpg" height="300"><br>
</div>

## Software highlights:
* Tomographic projection data are simulated without the "inverse crime" using [TomoPhantom](https://github.com/dkazanc/TomoPhantom). Noise and artifacts (zingers, rings) can be modelled and added to data.
Expand All @@ -18,9 +21,9 @@
* C compilers (GCC/MinGW) and nvcc [CUDA SDK](https://developer.nvidia.com/cuda-downloads) compilers

### Software dependencies:
* [ASTRA-toolbox](https://www.astra-toolbox.com/)
* [TomoPhantom](https://github.com/dkazanc/TomoPhantom)
* [CCPi-RegularisationToolkit](https://github.com/vais-ral/CCPi-Regularisation-Toolkit)
* [ASTRA-toolbox](https://www.astra-toolbox.com/) for projection operations
* [TomoPhantom](https://github.com/dkazanc/TomoPhantom) for simulation
* [CCPi-RegularisationToolkit](https://github.com/vais-ral/CCPi-Regularisation-Toolkit) for regularisation
* See [INSTALLATION](https://github.com/dkazanc/FISTA-tomo/blob/master/INSTALLATION) for detailed information

### Installation in Python (conda):
Expand Down
239 changes: 239 additions & 0 deletions Wrappers/Python/Demos/DemoRec_NLTV_2D.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
GPLv3 license (ASTRA toolbox)
Script to generate 2D analytical phantoms and their sinograms with added noise
and then reconstruct using Non-local Total variation (NLTV) regularised FISTA algorithm.
NLTV method is quite different to the generic structure of other regularisers, hence
a separate implementation
Dependencies:
* astra-toolkit, install conda install -c astra-toolbox astra-toolbox
* CCPi-RGL toolkit (for regularisation), install with
conda install ccpi-regulariser -c ccpi -c conda-forge
or https://github.com/vais-ral/CCPi-Regularisation-Toolkit
* TomoPhantom, https://github.com/dkazanc/TomoPhantom
@author: Daniil Kazantsev
"""
import numpy as np
import timeit
import matplotlib.pyplot as plt
from tomophantom import TomoP2D
import os
import tomophantom
from tomophantom.supp.qualitymetrics import QualityTools


model = 13 # select a model
N_size = 512 # set dimension of the phantom
# one can specify an exact path to the parameters file
# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat'
path = os.path.dirname(tomophantom.__file__)
path_library2D = os.path.join(path, "Phantom2DLibrary.dat")
phantom_2D = TomoP2D.Model(model, N_size, path_library2D)

plt.close('all')
plt.figure(1)
plt.rcParams.update({'font.size': 21})
plt.imshow(phantom_2D, vmin=0, vmax=1, cmap="gray")
plt.colorbar(ticks=[0, 0.5, 1], orientation='vertical')
plt.title('{}''{}'.format('2D Phantom using model no.',model))

# create sinogram analytically
angles_num = int(0.5*np.pi*N_size); # angles number
angles = np.linspace(0.0,179.9,angles_num,dtype='float32')
angles_rad = angles*(np.pi/180.0)
P = int(np.sqrt(2)*N_size) #detectors

sino_an = TomoP2D.ModelSino(model, N_size, P, angles, path_library2D)

plt.figure(2)
plt.rcParams.update({'font.size': 21})
plt.imshow(sino_an, cmap="gray")
plt.colorbar(ticks=[0, 150, 250], orientation='vertical')
plt.title('{}''{}'.format('Analytical sinogram of model no.',model))
#%%
# Adding artifacts and noise
from tomophantom.supp.artifacts import ArtifactsClass

# adding noise
artifacts_add = ArtifactsClass(sino_an)
#noisy_sino = artifacts_add.noise(sigma=0.1,noisetype='Gaussian')
noisy_sino = artifacts_add.noise(sigma=2000,noisetype='Poisson')

"""
# adding zingers
artifacts_add =ArtifactsClass(noisy_sino)
noisy_zing = artifacts_add.zingers(percentage=0.25, modulus = 10)
"""

#adding stripes
"""
artifacts_add =ArtifactsClass(noisy_zing)
noisy_zing_stripe = artifacts_add.stripes(percentage=1, maxthickness = 1)
noisy_zing_stripe[noisy_zing_stripe < 0] = 0
"""
plt.figure()
plt.rcParams.update({'font.size': 21})
plt.imshow(noisy_sino,cmap="gray")
plt.colorbar(ticks=[0, 150, 250], orientation='vertical')
plt.title('{}''{}'.format('Analytical noisy sinogram with artifacts',model))
#%%
print ("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")
print ("Reconstructing with FISTA method (ASTRA used for projection)")
print ("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")
from fista.tomo.recModIter import RecTools
from ccpi.filters.regularisers import PatchSelect

# set parameters and initiate a class object
Rectools = RecTools(DetectorsDimH = P, # DetectorsDimH # detector dimension (horizontal)
DetectorsDimV = None, # DetectorsDimV # detector dimension (vertical) for 3D case only
AnglesVec = angles_rad, # array of angles in radians
ObjSize = N_size, # a scalar to define reconstructed object dimensions
datafidelity='LS',# data fidelity, choose LS, PWLS (wip), GH (wip), Student (wip)
OS_number = None, # the number of subsets, NONE/(or > 1) ~ classical / ordered subsets
tolerance = 1e-06, # tolerance to stop outer iterations earlier
device='gpu')

lc = Rectools.powermethod() # calculate Lipschitz constant (run once to initilise)

# first reconstruct using FBP method
from tomophantom.supp.astraOP import AstraTools
Atools = AstraTools(P, angles_rad, N_size, 'gpu') # initiate a class object
FBPrec = Atools.fbp2D(noisy_sino)

plt.figure()
plt.imshow(FBPrec, vmin=0, vmax=1, cmap="BuPu")
plt.colorbar(ticks=[0, 0.5, 1], orientation='vertical')
plt.title('FBP reconstruction')

#%%
print ("Pre-calculating weights for non-local patches using FBP image...")

pars = {'algorithm' : PatchSelect, \
'input' : FBPrec,\
'searchwindow': 7, \
'patchwindow': 2,\
'neighbours' : 15 ,\
'edge_parameter':0.9}
H_i, H_j, Weights = PatchSelect(pars['input'], pars['searchwindow'], pars['patchwindow'], pars['neighbours'],
pars['edge_parameter'],'gpu')
"""
plt.figure()
plt.imshow(Weights[0,:,:], vmin=0, vmax=1, cmap="gray")
plt.colorbar(ticks=[0, 0.5, 1], orientation='vertical')
"""
#%%

tic=timeit.default_timer()
print ("Run FISTA reconstrucion algorithm with TV regularisation...")
RecFISTA_regTV = Rectools.FISTA(noisy_sino, iterationsFISTA = 250, \
regularisation = 'FGP_TV', \
regularisation_parameter = 0.0009,\
regularisation_iterations = 60,\
lipschitz_const = lc)
toc=timeit.default_timer()
Run_time = toc - tic
print("FISTA-TV completed in {} seconds".format(Run_time))

tic=timeit.default_timer()
print ("Run FISTA reconstrucion algorithm with NLTV regularisation...")
RecFISTA_regNLTV = Rectools.FISTA(noisy_sino, iterationsFISTA = 250, \
regularisation = 'NLTV', \
regularisation_parameter = 0.002,\
regularisation_iterations = 3,\
NLTV_H_i = H_i,\
NLTV_H_j = H_j,\
NLTV_Weights = Weights,\
lipschitz_const = lc)
toc=timeit.default_timer()
Run_time = toc - tic
print("FISTA-NLTV completed in {} seconds".format(Run_time))

# calculate errors
Qtools = QualityTools(phantom_2D, RecFISTA_regTV)
RMSE_FISTA_TV = Qtools.rmse()
Qtools = QualityTools(phantom_2D, RecFISTA_regNLTV)
RMSE_FISTA_NLTV = Qtools.rmse()
print("RMSE for TV-regularised FISTA is {}".format(RMSE_FISTA_TV))
print("RMSE for NLTV-regularised FISTA is {}".format(RMSE_FISTA_NLTV))

plt.figure()
plt.subplot(121)
plt.imshow(RecFISTA_regTV, vmin=0, vmax=1, cmap="BuPu")
plt.text(0.0, 550, 'RMSE is %s\n' %(round(RMSE_FISTA_TV, 3)), {'color': 'b', 'fontsize': 20})
plt.title('TV Regularised FISTA reconstruction')
plt.subplot(122)
plt.imshow(RecFISTA_regNLTV, vmin=0, vmax=1, cmap="BuPu")
plt.text(0.0, 550, 'RMSE is %s\n' %(round(RMSE_FISTA_NLTV, 3)), {'color': 'b', 'fontsize': 20})
plt.title('NLTV-Regularised FISTA reconstruction')
plt.show()

#%%
print ("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")
print ("%%%%%%%%%%%Reconstructing with FISTA-OS method%%%%%%%%%%%%%%")
print ("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")
from fista.tomo.recModIter import RecTools

# set parameters and initiate a class object
Rectools = RecTools(DetectorsDimH = P, # DetectorsDimH # detector dimension (horizontal)
DetectorsDimV = None, # DetectorsDimV # detector dimension (vertical) for 3D case only
AnglesVec = angles_rad, # array of angles in radians
ObjSize = N_size, # a scalar to define reconstructed object dimensions
datafidelity='LS',# data fidelity, choose LS, PWLS (wip), GH (wip), Student (wip)
OS_number = 24, # the number of subsets, NONE/(or > 1) ~ classical / ordered subsets
tolerance = 1e-06, # tolerance to stop outer iterations earlier
device='gpu')

lc = Rectools.powermethod() # calculate Lipschitz constant (run once to initilise)

tic=timeit.default_timer()
print ("Run FISTA-OS reconstrucion algorithm with TV regularisation...")
RecFISTA_TV_os = Rectools.FISTA(noisy_sino, iterationsFISTA = 20, \
regularisation = 'FGP_TV', \
regularisation_parameter = 0.00003,\
regularisation_iterations = 70,\
lipschitz_const = lc)
toc=timeit.default_timer()
Run_time = toc - tic
print("FISTA-OS-TV completed in {} seconds".format(Run_time))

tic=timeit.default_timer()
print ("Run FISTA-OS reconstrucion algorithm with NLTV regularisation...")
RecFISTA_NLTV_os = Rectools.FISTA(noisy_sino, iterationsFISTA = 20, \
regularisation = 'NLTV', \
regularisation_parameter = 0.00015,\
regularisation_iterations = 30,\
NLTV_H_i = H_i,\
NLTV_H_j = H_j,\
NLTV_Weights = Weights,\
lipschitz_const = lc)

toc=timeit.default_timer()
Run_time = toc - tic
print("FISTA-OS-NLTV completed in {} seconds".format(Run_time))

# calculate errors
Qtools = QualityTools(phantom_2D, RecFISTA_TV_os)
RMSE_FISTA_OS_TV = Qtools.rmse()
Qtools = QualityTools(phantom_2D, RecFISTA_NLTV_os)
RMSE_FISTA_OS_NLTV = Qtools.rmse()
print("RMSE for FISTA-OS-TV is {}".format(RMSE_FISTA_OS_TV))
print("RMSE for FISTA-OS-TNLV is {}".format(RMSE_FISTA_OS_NLTV))

plt.figure()
plt.subplot(121)
plt.imshow(RecFISTA_TV_os, vmin=0, vmax=1, cmap="BuPu")
plt.colorbar(ticks=[0, 0.5, 1], orientation='vertical')
plt.text(0.0, 550, 'RMSE is %s\n' %(round(RMSE_FISTA_OS_TV, 3)), {'color': 'b', 'fontsize': 20})
plt.title('TV-regularised FISTA-OS reconstruction')
plt.subplot(122)
plt.imshow(RecFISTA_NLTV_os, vmin=0, vmax=1, cmap="BuPu")
plt.colorbar(ticks=[0, 0.5, 1], orientation='vertical')
plt.text(0.0, 550, 'RMSE is %s\n' %(round(RMSE_FISTA_OS_NLTV, 3)), {'color': 'b', 'fontsize': 20})
plt.title('NLTV-regularised FISTA-OS reconstruction')
plt.show()
#%%
4 changes: 2 additions & 2 deletions Wrappers/Python/conda-recipe/meta.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package:
name: fista-tomo
version: "0.1.1"
version: "0.1.2"


build:
Expand All @@ -19,4 +19,4 @@ requirements:
about:
home: https://github.com/dkazanc/FISTA-tomo
license: GPLv3
summary: 'FISTA iterative reconstruction algorithm for tomography'
summary: 'FISTA-based iterative reconstruction algorithms for tomography'
4 changes: 2 additions & 2 deletions Wrappers/Python/conda-recipe/meta.yaml~
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package:
name: fista-tomo
version: "0.1"
version: "0.1.2"


build:
Expand All @@ -19,4 +19,4 @@ requirements:
about:
home: https://github.com/dkazanc/FISTA-tomo
license: GPLv3
summary: 'FISTA iterative reconstruction algorithm for tomography'
summary: 'FISTA-based iterative reconstruction algorithms for tomography'
14 changes: 10 additions & 4 deletions Wrappers/Python/fista/tomo/recModIter.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ def FISTA(self,
TGV_LipschitzConstant = 12.0, # TGV specific parameter for convergence
edge_param = 0.01, # edge (noise) threshold parameter for NDF and DIFF4th
NDF_penalty = 1, # NDF specific penalty type: 1 - Huber, 2 - Perona-Malik, 3 - Tukey Biweight
NLTV_H_i = 0, # NLTV-specific penalty type, the array of i-related indices
NLTV_H_j = 0, # NLTV-specific penalty type, the array of j-related indices
NLTV_Weights = 0, # NLTV-specific penalty type, the array of Weights
methodTV = 0, # 0/1 - isotropic/anisotropic TV
nonneg = 0 # 0/1 disabled/enabled nonnegativity (for FGP_TV currently)
):
Expand Down Expand Up @@ -128,10 +131,10 @@ def FISTA(self,

# The dependency on the CCPi-RGL toolkit for regularisation
if regularisation is not None:
if ((regularisation != 'ROF_TV') and (regularisation != 'FGP_TV') and (regularisation != 'SB_TV') and (regularisation != 'LLT_ROF') and (regularisation != 'TGV') and (regularisation != 'NDF') and (regularisation != 'DIFF4th')):
raise('Unknown regularisation method, select: ROF_TV, FGP_TV, SB_TV, LLT_ROF, TGV, NDF, DIFF4th')
if ((regularisation != 'ROF_TV') and (regularisation != 'FGP_TV') and (regularisation != 'SB_TV') and (regularisation != 'LLT_ROF') and (regularisation != 'TGV') and (regularisation != 'NDF') and (regularisation != 'DIFF4th') and (regularisation != 'NLTV')):
raise('Unknown regularisation method, select: ROF_TV, FGP_TV, SB_TV, LLT_ROF, TGV, NDF, DIFF4th, NLTV')
else:
from ccpi.filters.regularisers import ROF_TV, FGP_TV, SB_TV, LLT_ROF, TGV, NDF, DIFF4th
from ccpi.filters.regularisers import ROF_TV, FGP_TV, SB_TV, LLT_ROF, TGV, NDF, DIFF4th, NLTV

#****************************************************************************#
# FISTA algorithm begins here:
Expand Down Expand Up @@ -188,10 +191,13 @@ def FISTA(self,
if (regularisation == 'DIFF4th'):
# Anisotropic diffusion of higher order
X = DIFF4th(X, regularisation_parameter, edge_param, regularisation_iterations, time_marching_parameter, self.device)
if (regularisation == 'NLTV'):
# Non-local Total Variation
X = NLTV(X, NLTV_H_i, NLTV_H_j, NLTV_H_i, NLTV_Weights, regularisation_parameter, regularisation_iterations)
t = (1.0 + np.sqrt(1.0 + 4.0*t**2))*0.5; # updating t variable
X_t = X + ((t_old - 1.0)/t)*(X - X_old) # updating X
else:
#print('FISTA stopped at iteration', iter)
break
#****************************************************************************#
return X
return X
Binary file added docs/images/TV_vs_NLTV.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 9081df9

Please sign in to comment.