Merge pull request #584 from josteinbf/iradon-algebraic

SART algorithm for tomography reconstruction
This commit is contained in:
Emmanuelle Gouillart
2013-07-06 10:24:24 -07:00
7 changed files with 629 additions and 35 deletions
+1
View File
@@ -144,3 +144,4 @@
- Jostein Bø Fløystad
Reconstruction circle mode for Radon transform
Simultaneous Algebraic Reconstruction Technique for inverse Radon transform
+173 -30
View File
@@ -1,57 +1,200 @@
# -*- coding: utf-8 -*-
"""
===============
Radon transform
===============
The radon transform is a technique widely used in tomography to
reconstruct an object from different projections. A projection is, for
example, the scattering data obtained as the output of a tomographic
scan.
In computed tomography, the tomography reconstruction problem is to obtain
a tomographic slice image from a set of projections [1]_. A projection is formed
by drawing a set of parallel rays through the 2D object of interest, assigning
the integral of the object's contrast along each ray to a single pixel in the
projection. A single projection of a 2D object is one dimensional. To
enable computed tomography reconstruction of the object, several projections
must be acquired, each of them corresponding to a different angle between the
rays with respect to the object. A collection of projections at several angles
is called a sinogram, which is a linear transform of the original image.
For more information see:
The inverse Radon transform is used in computed tomography to reconstruct
a 2D image from the measured projections (the sinogram). A practical, exact
implementation of the inverse Radon transform does not exist, but there are
several good approximate algorithms available.
- http://en.wikipedia.org/wiki/Radon_transform
- http://www.clear.rice.edu/elec431/projects96/DSP/bpanalysis.html
As the inverse Radon transform reconstructs the object from a set of
projections, the (forward) Radon transform can be used to simulate a
tomography experiment.
This script performs the radon transform, and reconstructs the
input image based on the resulting sinogram.
This script performs the Radon transform to simulate a tomography experiment
and reconstructs the input image based on the resulting sinogram formed by
the simulation. Two methods for performing the inverse Radon transform
and reconstructing the original image are compared: The Filtered Back
Projection (FBP) and the Simultaneous Algebraic Reconstruction
Technique (SART).
.. seealso::
- AC Kak, M Slaney, "Principles of Computerized Tomographic Imaging",
http://www.slaney.org/pct/pct-toc.html
- http://en.wikipedia.org/wiki/Radon_transform
The forward transform
=====================
As our original image, we will use the Shepp-Logan phantom. When calculating
the Radon transform, we need to decide how many projection angles we wish
to use. As a rule of thumb, the number of projections should be about the
same as the number of pixels there are across the object (to see why this
is so, consider how many unknown pixel values must be determined in the
reconstruction process and compare this to the number of measurements
provided by the projections), and we follow that rule here. Below is the
original image and its Radon transform, often known as its _sinogram_:
"""
from __future__ import print_function, division
import numpy as np
import matplotlib.pyplot as plt
from skimage.io import imread
from skimage import data_dir
from skimage.transform import radon, iradon, rescale
from skimage.transform import radon, rescale
image = imread(data_dir + "/phantom.png", as_grey=True)
image = rescale(image, scale=0.4)
plt.figure(figsize=(8, 8.5))
plt.figure(figsize=(8, 4.5))
plt.subplot(221)
plt.subplot(121)
plt.title("Original")
plt.imshow(image, cmap=plt.cm.Greys_r)
plt.subplot(222)
projections = radon(image, theta=[0, 45, 90])
plt.plot(projections)
plt.title("Projections at\n0, 45 and 90 degrees")
plt.xlabel("Projection axis")
plt.ylabel("Intensity")
projections = radon(image)
plt.subplot(223)
theta = np.linspace(0., 180., max(image.shape), endpoint=True)
sinogram = radon(image, theta=theta, circle=True)
plt.subplot(122)
plt.title("Radon transform\n(Sinogram)")
plt.xlabel("Projection angle (degrees)")
plt.ylabel("Projection axis")
plt.imshow(projections, aspect='auto')
reconstruction = iradon(projections)
plt.subplot(224)
plt.title("Reconstruction\nfrom sinogram")
plt.imshow(reconstruction, cmap=plt.cm.Greys_r)
plt.xlabel("Projection angle (deg)")
plt.ylabel("Projection position (pixels)")
plt.imshow(sinogram, cmap=plt.cm.Greys_r,
extent=(0, 180, 0, sinogram.shape[0]), aspect='auto')
plt.subplots_adjust(hspace=0.4, wspace=0.5)
plt.show()
"""
.. image:: PLOT2RST.current_figure
Reconstruction with the Filtered Back Projection (FBP)
======================================================
The mathematical foundation of the filtered back projection is the Fourier
slice theorem [2]_. It uses Fourier transform of the projection and
interpolation in Fourier space to obtain the 2D Fourier transform of the image,
which is then inverted to form the reconstructed image. The filtered back
projection is among the fastest methods of performing the inverse Radon
transform. The only tunable parameter for the FBP is the filter, which is
applied to the Fourier transformed projections. It may be used to suppress
high frequency noise in the reconstruction. ``skimage`` provides a few
different options for the filter.
"""
from skimage.transform import iradon
reconstruction_fbp = iradon(sinogram, theta=theta, circle=True)
error = reconstruction_fbp - image
print('FBP rms reconstruction error: %.3g' % np.sqrt(np.mean(error**2)))
imkwargs = dict(vmin=-0.2, vmax=0.2)
plt.figure(figsize=(8, 4.5))
plt.subplot(121)
plt.title("Reconstruction\nFiltered back projection")
plt.imshow(reconstruction_fbp, cmap=plt.cm.Greys_r)
plt.subplot(122)
plt.title("Reconstruction error\nFiltered back projection")
plt.imshow(reconstruction_fbp - image, cmap=plt.cm.Greys_r, **imkwargs)
plt.show()
"""
.. image:: PLOT2RST.current_figure
Reconstruction with the Simultaneous Algebraic Reconstruction Technique
=======================================================================
Algebraic reconstruction techniques for tomography are based on a
straightforward idea: for a pixelated image the value of a single ray in a
particular projection is simply a sum of all the pixels the ray passes through
on its way through the object. This is a way of expressing the forward Radon
transform. The inverse Radon transform can then be formulated as a (large) set
of linear equations. As each ray passes through a small fraction of the pixels
in the image, this set of equations is sparse, allowing iterative solvers for
sparse linear systems to tackle the system of equations. One iterative method
has been particularly popular, namely Kaczmarz' method [3]_, which has the
property that the solution will approach a least-squares solution of the
equation set.
The combination of the formulation of the reconstruction problem as a set
of linear equations and an iterative solver makes algebraic techniques
relatively flexible, hence some forms of prior knowledge can be incorporated
with relative ease.
``skimage`` provides one of the more popular variations of the algebraic
reconstruction techniques: the Simultaneous Algebraic Reconstruction Technique
(SART) [1]_ [4]_. It uses Kaczmarz' method [3]_ as the iterative solver. A good
reconstruction is normally obtained in a single iteration, making the method
computationally effective. Running one or more extra iterations will normally
improve the reconstruction of sharp, high frequency features and reduce the
mean squared error at the expense of increased high frequency noise (the user
will need to decide on what number of iterations is best suited to the problem
at hand. The implementation in ``skimage`` allows prior information of the
form of a lower and upper threshold on the reconstructed values to be supplied
to the reconstruction.
"""
from skimage.transform import iradon_sart
reconstruction_sart = iradon_sart(sinogram, theta=theta)
error = reconstruction_sart - image
print('SART (1 iteration) rms reconstruction error: %.3g'
% np.sqrt(np.mean(error**2)))
plt.figure(figsize=(8, 8.5))
plt.subplot(221)
plt.title("Reconstruction\nSART")
plt.imshow(reconstruction_sart, cmap=plt.cm.Greys_r)
plt.subplot(222)
plt.title("Reconstruction error\nSART")
plt.imshow(reconstruction_sart - image, cmap=plt.cm.Greys_r, **imkwargs)
# Run a second iteration of SART by supplying the reconstruction
# from the first iteration as an initial estimate
reconstruction_sart2 = iradon_sart(sinogram, theta=theta,
image=reconstruction_sart)
error = reconstruction_sart2 - image
print('SART (2 iterations) rms reconstruction error: %.3g'
% np.sqrt(np.mean(error**2)))
plt.subplot(223)
plt.title("Reconstruction\nSART, 2 iterations")
plt.imshow(reconstruction_sart2, cmap=plt.cm.Greys_r)
plt.subplot(224)
plt.title("Reconstruction error\nSART, 2 iterations")
plt.imshow(reconstruction_sart2 - image, cmap=plt.cm.Greys_r, **imkwargs)
plt.show()
"""
.. image:: PLOT2RST.current_figure
.. [1] AC Kak, M Slaney, "Principles of Computerized Tomographic Imaging",
IEEE Press 1988. http://www.slaney.org/pct/pct-toc.html
.. [2] Wikipedia, Radon transform,
http://en.wikipedia.org/wiki/Radon_transform#Relationship_with_the_Fourier_transform
.. [3] S Kaczmarz, "Angenäherte auflösung von systemen linearer
gleichungen", Bulletin International de lAcademie Polonaise des
Sciences et des Lettres 35 pp 355--357 (1937)
.. [4] AH Andersen, AC Kak, "Simultaneous algebraic reconstruction technique
(SART): a superior implementation of the ART algorithm", Ultrasonic
Imaging 6 pp 81--94 (1984)
"""
+2 -1
View File
@@ -2,7 +2,7 @@ from ._hough_transform import (hough_circle, hough_ellipse, hough_line,
probabilistic_hough_line)
from .hough_transform import (hough, probabilistic_hough, hough_peaks,
hough_line_peaks)
from .radon_transform import radon, iradon
from .radon_transform import radon, iradon, iradon_sart
from .finite_radon_transform import frt2, ifrt2
from .integral import integral_image, integrate
from ._geometric import (warp, warp_coords, estimate_transform,
@@ -24,6 +24,7 @@ __all__ = ['hough_circle',
'hough_line_peaks',
'radon',
'iradon',
'iradon_sart',
'frt2',
'ifrt2',
'integral_image',
+202
View File
@@ -0,0 +1,202 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
import numpy as np
cimport numpy as cnp
cimport cython
from libc.math cimport cos, sin, floor, ceil, sqrt, abs, M_PI
cpdef bilinear_ray_sum(cnp.double_t[:, :] image, cnp.double_t theta,
cnp.double_t ray_position):
"""
Compute the projection of an image along a ray.
Parameters
----------
image : 2D array, dtype=float
Image to project.
theta : float
Angle of the projection
ray_position : float
Position of the ray within the projection
Returns
-------
projected_value : float
Ray sum along the projection
norm_of_weights :
A measure of how long the ray's path through the reconstruction
circle was
"""
theta = theta / 180. * M_PI
cdef cnp.double_t radius = image.shape[0] // 2 - 1
cdef cnp.double_t projection_center = image.shape[0] // 2
cdef cnp.double_t rotation_center = image.shape[0] // 2
# (s, t) is the (x, y) system rotated by theta
cdef cnp.double_t t = ray_position - projection_center
# s0 is the half-length of the ray's path in the reconstruction circle
cdef cnp.double_t s0
s0 = sqrt(radius**2 - t**2) if radius**2 >= t**2 else 0.
cdef Py_ssize_t Ns = 2 * (<Py_ssize_t> ceil(2 * s0)) # number of steps
# along the ray
cdef cnp.double_t ray_sum = 0.
cdef cnp.double_t weight_norm = 0.
cdef cnp.double_t ds, dx, dy, x0, y0, x, y, di, dj,
cdef cnp.double_t index_i, index_j, weight
cdef Py_ssize_t k, i, j
if Ns > 0:
# step length between samples
ds = 2 * s0 / Ns
dx = -ds * cos(theta)
dy = -ds * sin(theta)
# point of entry of the ray into the reconstruction circle
x0 = s0 * cos(theta) - t * sin(theta)
y0 = s0 * sin(theta) + t * cos(theta)
for k in range(Ns+1):
x = x0 + k * dx
y = y0 + k * dy
index_i = x + rotation_center
index_j = y + rotation_center
i = <Py_ssize_t> floor(index_i)
j = <Py_ssize_t> floor(index_j)
di = index_i - floor(index_i)
dj = index_j - floor(index_j)
# Use linear interpolation between values
# Where values fall outside the array, assume zero
if i > 0 and j > 0:
weight = (1. - di) * (1. - dj) * ds
ray_sum += weight * image[i, j]
weight_norm += weight**2
if i > 0 and j < image.shape[1] - 1:
weight = (1. - di) * dj * ds
ray_sum += weight * image[i, j+1]
weight_norm += weight**2
if i < image.shape[0] - 1 and j > 0:
weight = di * (1 - dj) * ds
ray_sum += weight * image[i+1, j]
weight_norm += weight**2
if i < image.shape[0] - 1 and j < image.shape[1] - 1:
weight = di * dj * ds
ray_sum += weight * image[i+1, j+1]
weight_norm += weight**2
return ray_sum, weight_norm
cpdef bilinear_ray_update(cnp.double_t[:, :] image,
cnp.double_t[:, :] image_update,
cnp.double_t theta, cnp.double_t ray_position,
cnp.double_t projected_value):
"""
Compute the update along a ray using bilinear interpolation.
Parameters
----------
image : 2D array, dtype=float
Current reconstruction estimate
image_update : 2D array, dtype=float
Array of same shape as ``image``. Updates will be added to this array.
theta : float
Angle of the projection
ray_position : float
Position of the ray within the projection
projected_value : float
Projected value (from the sinogram)
Returns
-------
deviation :
Deviation before updating the image
"""
cdef cnp.double_t ray_sum, weight_norm, deviation
ray_sum, weight_norm = bilinear_ray_sum(image, theta, ray_position)
if weight_norm > 0.:
deviation = -(ray_sum - projected_value) / weight_norm
else:
deviation = 0.
theta = theta / 180. * M_PI
cdef cnp.double_t radius = image.shape[0] // 2 - 1
cdef cnp.double_t projection_center = image.shape[0] // 2
cdef cnp.double_t rotation_center = image.shape[0] // 2
# (s, t) is the (x, y) system rotated by theta
cdef cnp.double_t t = ray_position - projection_center
# s0 is the half-length of the ray's path in the reconstruction circle
cdef cnp.double_t s0
s0 = sqrt(radius*radius - t*t) if radius**2 >= t**2 else 0.
cdef Py_ssize_t Ns = 2 * (<Py_ssize_t> ceil(2 * s0))
cdef cnp.double_t hamming_beta = 0.46164 # beta for equiripple Hamming window
cdef cnp.double_t ds, dx, dy, x0, y0, x, y, di, dj, index_i, index_j
cdef cnp.double_t hamming_window
cdef Py_ssize_t k, i, j
if Ns > 0:
# Step length between samples
ds = 2 * s0 / Ns
dx = -ds * cos(theta)
dy = -ds * sin(theta)
# Point of entry of the ray into the reconstruction circle
x0 = s0 * cos(theta) - t * sin(theta)
y0 = s0 * sin(theta) + t * cos(theta)
for k in range(Ns+1):
x = x0 + k * dx
y = y0 + k * dy
index_i = x + rotation_center
index_j = y + rotation_center
i = <Py_ssize_t> floor(index_i)
j = <Py_ssize_t> floor(index_j)
di = index_i - floor(index_i)
dj = index_j - floor(index_j)
hamming_window = ((1 - hamming_beta)
- hamming_beta * cos(2 * M_PI * k / (Ns - 1)))
if i > 0 and j > 0:
image_update[i, j] += (deviation * (1. - di) * (1. - dj)
* ds * hamming_window)
if i > 0 and j < image.shape[1] - 1:
image_update[i, j+1] += (deviation * (1. - di) * dj
* ds * hamming_window)
if i < image.shape[0] - 1 and j > 0:
image_update[i+1, j] += (deviation * di * (1 - dj)
* ds * hamming_window)
if i < image.shape[0] - 1 and j < image.shape[1] - 1:
image_update[i+1, j+1] += (deviation * di * dj
* ds * hamming_window)
return deviation
@cython.boundscheck(True)
def sart_projection_update(cnp.double_t[:, :] image not None,
cnp.double_t theta,
cnp.double_t[:] projection not None,
cnp.double_t projection_shift=0.):
"""
Compute update to a reconstruction estimate from a single projection
using bilinear interpolation.
Parameters
----------
image : 2D array, dtype=float
Current reconstruction estimate
theta : float
Angle of the projection
projection : 1D array, dtype=float
Projected values, taken from the sinogram
projection_shift : float
Shift the position of the projection by this many pixels before
using it to compute an update to the reconstruction estimate
Returns
-------
image_update : 2D array, dtype=float
Array of same shape as ``image`` containing updates that should be
added to ``image`` to improve the reconstruction estimate
"""
cdef cnp.ndarray[cnp.double_t, ndim=2] image_update = np.zeros_like(image)
cdef cnp.double_t ray_position
cdef Py_ssize_t i
for i in range(projection.shape[0]):
ray_position = i + projection_shift
bilinear_ray_update(image, image_update, theta, ray_position,
projection[i])
return image_update
+162 -1
View File
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
"""
radon.py - Radon and inverse radon transforms
@@ -16,8 +17,9 @@ from __future__ import division
import numpy as np
from scipy.fftpack import fftshift, fft, ifft
from ._warps_cy import _warp_fast
from ._radon_transform import sart_projection_update
__all__ = ["radon", "iradon"]
__all__ = ["radon", "iradon", "iradon_sart"]
def radon(image, theta=None, circle=False):
@@ -254,3 +256,162 @@ def iradon(radon_image, theta=None, output_size=None,
raise ValueError("Unknown interpolation: %s" % interpolation)
return reconstructed * np.pi / (2 * len(th))
def order_angles_golden_ratio(theta):
"""
Order angles to reduce the amount of correlated information
in subsequent projections.
Parameters
----------
theta : 1D array of floats
Projection angles in degrees. Duplicate angles are not allowed.
Returns
-------
indices : 1D array of unsigned integers
Indices into ``theta`` such that ``theta[indices]`` gives the
approximate golden ratio ordering of the projections.
Notes
-----
The method used here is that of the golden ratio introduced
by T. Kohler.
References:
-Kohler, T. "A projection access scheme for iterative
reconstruction based on the golden section." Nuclear Science
Symposium Conference Record, 2004 IEEE. Vol. 6. IEEE, 2004.
-Winkelmann, Stefanie, et al. "An optimal radial profile order
based on the Golden Ratio for time-resolved MRI."
Medical Imaging, IEEE Transactions on 26.1 (2007): 68-76.
"""
interval = 180
def angle_distance(a, b):
difference = a - b
return min(abs(difference % interval), abs(difference % -interval))
remaining = list(np.argsort(theta)) # indices into theta
# yield an arbitrary angle to start things off
index = remaining.pop(0)
angle = theta[index]
yield index
# determine subsequent angles using the golden ratio method
angle_increment = interval * (1 - (np.sqrt(5) - 1) / 2)
while remaining:
angle = (angle + angle_increment) % interval
insert_point = np.searchsorted(theta[remaining], angle)
index_below = insert_point - 1
index_above = 0 if insert_point == len(remaining) else insert_point
distance_below = angle_distance(angle, theta[remaining[index_below]])
distance_above = angle_distance(angle, theta[remaining[index_above]])
if distance_below < distance_above:
yield remaining.pop(index_below)
else:
yield remaining.pop(index_above)
def iradon_sart(radon_image, theta=None, image=None, projection_shifts=None,
clip=None, relaxation=0.15):
"""
Inverse radon transform
Reconstruct an image from the radon transform, using a single iteration of
the Simultaneous Algebraic Reconstruction Technique (SART) algorithm.
Parameters
----------
radon_image : 2D array, dtype=float
Image containing radon transform (sinogram). Each column of
the image corresponds to a projection along a different angle.
theta : 1D array, dtype=float, optional
Reconstruction angles (in degrees). Default: m angles evenly spaced
between 0 and 180 (if the shape of `radon_image` is (N, M)).
image : 2D array, dtype=float, optional
Image containing an initial reconstruction estimate. Shape of this
array should be ``(radon_image.shape[0], radon_image.shape[0])``. The
default is an array of zeros.
projection_shifts : 1D array, dtype=float
Shift the projections contained in ``radon_image`` (the sinogram) by
this many pixels before reconstructing the image. The i'th value
defines the shift of the i'th column of ``radon_image``.
clip : length-2 sequence of floats
Force all values in the reconstructed tomogram to lie in the range
``[clip[0], clip[1]]``
relaxation : float
Relaxation parameter for the update step. A higher value can
improve the convergence rate, but one runs the risk of instabilities.
Values close to or higher than 1 are not recommended.
Returns
-------
output : ndarray
Reconstructed image.
Notes
-----
Algebraic Reconstruction Techniques are based on formulating the tomography
reconstruction problem as a set of linear equations. Along each ray,
the projected value is the sum of all the values of the cross section along
the ray. A typical feature of SART (and a few other variants of algebraic
techniques) is that it samples the cross section at equidistant points
along the ray, using linear interpolation between the pixel values of the
cross section. The resulting set of linear equations are then solved using
a slightly modified Kaczmarz method.
When using SART, a single iteration is usually sufficient to obtain a good
reconstruction. Further iterations will tend to enhance high-frequency
information, but will also often increase the noise.
References:
-AC Kak, M Slaney, "Principles of Computerized Tomographic
Imaging", IEEE Press 1988.
-AH Andersen, AC Kak, "Simultaneous algebraic reconstruction technique
(SART): a superior implementation of the ART algorithm", Ultrasonic
Imaging 6 pp 81--94 (1984)
-S Kaczmarz, "Angenäherte auflösung von systemen linearer
gleichungen", Bulletin International de lAcademie Polonaise des
Sciences et des Lettres 35 pp 355--357 (1937)
-Kohler, T. "A projection access scheme for iterative
reconstruction based on the golden section." Nuclear Science
Symposium Conference Record, 2004 IEEE. Vol. 6. IEEE, 2004.
-Kaczmarz' method, Wikipedia,
http://en.wikipedia.org/wiki/Kaczmarz_method
"""
if radon_image.ndim != 2:
raise ValueError('radon_image must be two dimensional')
reconstructed_shape = (radon_image.shape[0], radon_image.shape[0])
if theta is None:
theta = np.linspace(0, 180, radon_image.shape[1], endpoint=False)
elif theta.shape != (radon_image.shape[1],):
raise ValueError('Shape of theta (%s) does not match the '
'number of projections (%d)'
% (projection_shifts.shape, radon_image.shape[1]))
if image is None:
image = np.zeros(reconstructed_shape, dtype=np.float)
elif image.shape != reconstructed_shape:
raise ValueError('Shape of image (%s) does not match first dimension '
'of radon_image (%s)'
% (image.shape, reconstructed_shape))
if projection_shifts is None:
projection_shifts = np.zeros((radon_image.shape[1],), dtype=np.float)
elif projection_shifts.shape != (radon_image.shape[1],):
raise ValueError('Shape of projection_shifts (%s) does not match the '
'number of projections (%d)'
% (projection_shifts.shape, radon_image.shape[1]))
if not clip is None:
if len(clip) != 2:
raise ValueError('clip must be a length-2 sequence')
clip = (float(clip[0]), float(clip[1]))
relaxation = float(relaxation)
for angle_index in order_angles_golden_ratio(theta):
image_update = sart_projection_update(image, theta[angle_index],
radon_image[:, angle_index],
projection_shifts[angle_index])
image += relaxation * image_update
if not clip is None:
image = np.clip(image, clip[0], clip[1])
return image
+5
View File
@@ -15,6 +15,7 @@ def configuration(parent_package='', top_path=None):
cython(['_hough_transform.pyx'], working_path=base_path)
cython(['_warps_cy.pyx'], working_path=base_path)
cython(['_radon_transform.pyx'], working_path=base_path)
config.add_extension('_hough_transform', sources=['_hough_transform.c'],
include_dirs=[get_numpy_include_dirs()])
@@ -22,6 +23,10 @@ def configuration(parent_package='', top_path=None):
config.add_extension('_warps_cy', sources=['_warps_cy.c'],
include_dirs=[get_numpy_include_dirs(), '../_shared'])
config.add_extension('_radon_transform',
sources=['_radon_transform.c'],
include_dirs=[get_numpy_include_dirs()])
return config
if __name__ == '__main__':
@@ -1,15 +1,17 @@
from __future__ import print_function
from __future__ import division
from __future__ import print_function, division
import numpy as np
from numpy.testing import assert_raises
import itertools
import os.path
from skimage.transform import radon, iradon
from skimage.io import imread
from skimage import data_dir
__PHANTOM = imread(data_dir + "/phantom.png", as_grey=True)[::2, ::2]
__PHANTOM = imread(os.path.join(data_dir, "phantom.png"),
as_grey=True)[::2, ::2]
def _get_phantom():
@@ -296,6 +298,85 @@ def test_radon_iradon_circle():
yield check_radon_iradon_circle, interpolation, shape, output_size
def test_order_angles_golden_ratio():
from skimage.transform.radon_transform import order_angles_golden_ratio
np.random.seed(1231)
lengths = [1, 4, 10, 180]
for l in lengths:
theta_ordered = np.linspace(0, 180, l, endpoint=False)
theta_random = np.random.uniform(0, 180, l)
for theta in (theta_random, theta_ordered):
indices = [x for x in order_angles_golden_ratio(theta)]
# no duplicate indices allowed
assert len(indices) == len(set(indices))
def test_iradon_sart():
from skimage.io import imread
from skimage import data_dir
from skimage.transform import rescale, radon, iradon_sart
debug = False
shepp_logan = imread(os.path.join(data_dir, "phantom.png"), as_grey=True)
image = rescale(shepp_logan, scale=0.4)
theta_ordered = np.linspace(0., 180., image.shape[0], endpoint=False)
theta_missing_wedge = np.linspace(0., 150., image.shape[0], endpoint=True)
for theta, error_factor in ((theta_ordered, 1.),
(theta_missing_wedge, 2.)):
sinogram = radon(image, theta, circle=True)
reconstructed = iradon_sart(sinogram, theta)
if debug:
from matplotlib import pyplot as plt
plt.figure()
plt.subplot(221)
plt.imshow(image, interpolation='nearest')
plt.subplot(222)
plt.imshow(sinogram, interpolation='nearest')
plt.subplot(223)
plt.imshow(reconstructed, interpolation='nearest')
plt.subplot(224)
plt.imshow(reconstructed - image, interpolation='nearest')
plt.show()
delta = np.mean(np.abs(reconstructed - image))
print('delta (1 iteration) =', delta)
assert delta < 0.016 * error_factor
reconstructed = iradon_sart(sinogram, theta, reconstructed)
delta = np.mean(np.abs(reconstructed - image))
print('delta (2 iterations) =', delta)
assert delta < 0.013 * error_factor
reconstructed = iradon_sart(sinogram, theta, clip=(0, 1))
delta = np.mean(np.abs(reconstructed - image))
print('delta (1 iteration, clip) =', delta)
assert delta < 0.015 * error_factor
np.random.seed(1239867)
shifts = np.random.uniform(-3, 3, sinogram.shape[1])
x = np.arange(sinogram.shape[0])
sinogram_shifted = np.vstack(np.interp(x + shifts[i], x,
sinogram[:, i])
for i in range(sinogram.shape[1])).T
reconstructed = iradon_sart(sinogram_shifted, theta,
projection_shifts=shifts)
if debug:
from matplotlib import pyplot as plt
plt.figure()
plt.subplot(221)
plt.imshow(image, interpolation='nearest')
plt.subplot(222)
plt.imshow(sinogram_shifted, interpolation='nearest')
plt.subplot(223)
plt.imshow(reconstructed, interpolation='nearest')
plt.subplot(224)
plt.imshow(reconstructed - image, interpolation='nearest')
plt.show()
delta = np.mean(np.abs(reconstructed - image))
print('delta (1 iteration, shifted sinogram) =', delta)
assert delta < 0.018 * error_factor
if __name__ == "__main__":
from numpy.testing import run_module_suite
run_module_suite()