Repository: scikit-image/scikit-image

Issue:
Radon transform: Circle mode as the default
A while ago, we introduced an optional "circle mode". The reasons for this is explained here: #567.

I propose to make this circle mode the default, for the reasons listed in the referenced PR, plus the bonus of consistency with other reconstruction algorithms implemented in skimage. As this is an API change, the code should issue a FutureWarning for at least one release. Since I'm hearing rumours that a new release is approaching, now would be a good time to start issuing the warning.



Discussion:
How about doing the following:

```
def radon(..., circle=None):
...
if circle is None:
    circle = True
    warn ...
```

In case circle is not None it must have been set manually by the user?

That would be a good way to implement it, yes. But we should probably
discuss whether we want to change the default mode or not before we
implement it?

2013/10/2 Johannes Schönberger notifications@github.com

> How about doing the following:
> 
> def radon(..., circle=None):
> ...
> if circle is None:
>     circle = True
>     warn ...
> 
> In case circle is not None it must have been set manually by the user?
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/scikit-image/scikit-image/issues/744#issuecomment-25551324
> .

Is this what most people would want to use? If so, then yes.

Everyone doing tomography experiments (myself included) would prefer the
`circle=True` version, as one simply does not have experimental data for
the part of the image outside the circle. When it comes to studying
tomography reconstruction algorithms, the case is that some algorithms are
more accurate when restricting the reconstruction to a circular region
(applies to SART and other algebraic techniques, but not to the FBP). My
opinion is that the circle mode should be the default as it provides a more
"fair" comparison between algorithms. Although I believe people researching
improved algorithms would agree, I can't be entirely sure here since I do
not actively research improved reconstruction algorithms myself.

In conclusion, I think it is a good idea to change the default. But we
should of course document clearly the difference between the two modes (I
think that is ok already).

2013/10/2 Stefan van der Walt notifications@github.com

> Is this what most people would want to use? If so, then yes.
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/scikit-image/scikit-image/issues/744#issuecomment-25556862
> .

We have the opportunity to start this deprecation before the 0.11 release, if you want.


Resolving patch:
diff --git a/doc/examples/transform/plot_radon_transform.py b/doc/examples/transform/plot_radon_transform.py
--- a/doc/examples/transform/plot_radon_transform.py
+++ b/doc/examples/transform/plot_radon_transform.py
@@ -31,9 +31,19 @@
 
 For further information on tomographic reconstruction, see
 
-- AC Kak, M Slaney, "Principles of Computerized Tomographic Imaging",
-  http://www.slaney.org/pct/pct-toc.html
-- http://en.wikipedia.org/wiki/Radon_transform
+.. [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, "Angenaeherte Aufloesung von Systemen linearer
+       Gleichungen", Bulletin International de l'Academie 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)
 
 The forward transform
 =====================
@@ -45,7 +55,7 @@
 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_:
+original image and its Radon transform, often known as its *sinogram*:
 """
 
 from __future__ import print_function, division
@@ -172,15 +182,3 @@
 ax[3].set_title("Reconstruction error\nSART, 2 iterations")
 ax[3].imshow(reconstruction_sart2 - image, cmap=plt.cm.Greys_r, **imkwargs)
 plt.show()
-
-######################################################################
-# References
-#
-# .. [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, "Angenaeherte Aufloesung von Systemen linearer Gleichungen", Bulletin International de l'Academie 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)
diff --git a/skimage/transform/__init__.py b/skimage/transform/__init__.py
--- a/skimage/transform/__init__.py
+++ b/skimage/transform/__init__.py
@@ -1,7 +1,8 @@
 from .hough_transform import (hough_line, hough_line_peaks,
                               probabilistic_hough_line, hough_circle,
                               hough_ellipse)
-from .radon_transform import radon, iradon, iradon_sart
+from .radon_transform import (radon, iradon, iradon_sart,
+                              order_angles_golden_ratio)
 from .finite_radon_transform import frt2, ifrt2
 from .integral import integral_image, integrate
 from ._geometric import (warp, warp_coords, estimate_transform,
@@ -23,6 +24,7 @@
            'radon',
            'iradon',
            'iradon_sart',
+           'order_angles_golden_ratio',
            'frt2',
            'ifrt2',
            'integral_image',
diff --git a/skimage/transform/radon_transform.py b/skimage/transform/radon_transform.py
--- a/skimage/transform/radon_transform.py
+++ b/skimage/transform/radon_transform.py
@@ -1,18 +1,4 @@
 # -*- coding: utf-8 -*-
-"""
-radon.py - Radon and inverse radon transforms
-
-Based on code of Justin K. Romberg
-(http://www.clear.rice.edu/elec431/projects96/DSP/bpanalysis.html)
-J. Gillam and Chris Griffin.
-
-References:
-    -B.R. Ramesh, N. Srinivasa, K. Rajgopal, "An Algorithm for Computing
-    the Discrete Radon Transform With Some Applications", Proceedings of
-    the Fourth IEEE Region 10 International Conference, TENCON '89, 1989.
-    -A. C. Kak, Malcolm Slaney, "Principles of Computerized Tomographic
-    Imaging", IEEE Press 1988.
-"""
 from __future__ import division
 import numpy as np
 from scipy.fftpack import fft, ifft, fftfreq
@@ -23,10 +9,10 @@
 from warnings import warn
 
 
-__all__ = ["radon", "iradon", "iradon_sart"]
+__all__ = ['radon', 'order_angles_golden_ratio', 'iradon', 'iradon_sart']
 
 
-def radon(image, theta=None, circle=False):
+def radon(image, theta=None, circle=None):
     """
     Calculates the radon transform of an image given specified
     projection angles.
@@ -42,6 +28,7 @@ def radon(image, theta=None, circle=False):
         Assume image is zero outside the inscribed circle, making the
         width of each projection (the first dimension of the sinogram)
         equal to ``min(image.shape)``.
+        The default behavior (None) is equivalent to False.
 
     Returns
     -------
@@ -50,11 +37,28 @@ def radon(image, theta=None, circle=False):
         at the pixel index ``radon_image.shape[0] // 2`` along the 0th
         dimension of ``radon_image``.
 
+    References
+    ----------
+    .. [1] AC Kak, M Slaney, "Principles of Computerized Tomographic
+           Imaging", IEEE Press 1988.
+    .. [2] B.R. Ramesh, N. Srinivasa, K. Rajgopal, "An Algorithm for Computing
+           the Discrete Radon Transform With Some Applications", Proceedings of
+           the Fourth IEEE Region 10 International Conference, TENCON '89, 1989
+
+    Notes
+    -----
+    Based on code of Justin K. Romberg
+    (http://www.clear.rice.edu/elec431/projects96/DSP/bpanalysis.html)
+
     """
     if image.ndim != 2:
         raise ValueError('The input image must be 2-D')
     if theta is None:
         theta = np.arange(180)
+    if circle is None:
+        warn('The default of `circle` in `skimage.transform.radon` '
+             'will change to `True` in version 0.15.')
+        circle = False
 
     if circle:
         radius = min(image.shape) // 2
@@ -122,7 +126,7 @@ def _sinogram_circle_to_square(sinogram):
 
 
 def iradon(radon_image, theta=None, output_size=None,
-           filter="ramp", interpolation="linear", circle=False):
+           filter="ramp", interpolation="linear", circle=None):
     """
     Inverse radon transform.
 
@@ -153,6 +157,7 @@ def iradon(radon_image, theta=None, output_size=None,
         Assume the reconstructed image is zero outside the inscribed circle.
         Also changes the default output_size to match the behaviour of
         ``radon`` called with ``circle=True``.
+        The default behavior (None) is equivalent to False.
 
     Returns
     -------
@@ -161,6 +166,14 @@ def iradon(radon_image, theta=None, output_size=None,
         with indices
         ``(reconstructed.shape[0] // 2, reconstructed.shape[1] // 2)``.
 
+    References
+    ----------
+    .. [1] AC Kak, M Slaney, "Principles of Computerized Tomographic
+           Imaging", IEEE Press 1988.
+    .. [2] B.R. Ramesh, N. Srinivasa, K. Rajgopal, "An Algorithm for Computing
+           the Discrete Radon Transform With Some Applications", Proceedings of
+           the Fourth IEEE Region 10 International Conference, TENCON '89, 1989
+
     Notes
     -----
     It applies the Fourier slice theorem to reconstruct an image by
@@ -179,7 +192,7 @@ def iradon(radon_image, theta=None, output_size=None,
         raise ValueError("The given ``theta`` does not match the number of "
                          "projections in ``radon_image``.")
     interpolation_types = ('linear', 'nearest', 'cubic')
-    if not interpolation in interpolation_types:
+    if interpolation not in interpolation_types:
         raise ValueError("Unknown interpolation: %s" % interpolation)
     if not output_size:
         # If output size not specified, estimate from input radon image
@@ -188,6 +201,10 @@ def iradon(radon_image, theta=None, output_size=None,
         else:
             output_size = int(np.floor(np.sqrt((radon_image.shape[0]) ** 2
                                                / 2.0)))
+    if circle is None:
+        warn('The default of `circle` in `skimage.transform.iradon` '
+             'will change to `True` in version 0.15.')
+        circle = False
     if circle:
         radon_image = _sinogram_circle_to_square(radon_image)
 
@@ -404,7 +421,7 @@ def iradon_sart(radon_image, theta=None, image=None, projection_shifts=None,
         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 clip is not None:
         if len(clip) != 2:
             raise ValueError('clip must be a length-2 sequence')
         clip = (float(clip[0]), float(clip[1]))
@@ -415,6 +432,6 @@ def iradon_sart(radon_image, theta=None, image=None, projection_shifts=None,
                                               radon_image[:, angle_index],
                                               projection_shifts[angle_index])
         image += relaxation * image_update
-        if not clip is None:
+        if clip is not None:
             image = np.clip(image, clip[0], clip[1])
     return image


Test patch:
diff --git a/skimage/transform/tests/test_radon_transform.py b/skimage/transform/tests/test_radon_transform.py
--- a/skimage/transform/tests/test_radon_transform.py
+++ b/skimage/transform/tests/test_radon_transform.py
@@ -116,7 +116,7 @@ def test_iradon_center():
 def check_radon_iradon(interpolation_type, filter_type):
     debug = False
     image = PHANTOM
-    reconstructed = iradon(radon(image), filter=filter_type,
+    reconstructed = iradon(radon(image, circle=False), filter=filter_type,
                            interpolation=interpolation_type)
     delta = np.mean(np.abs(image - reconstructed))
     print('\n\tmean error:', delta)
@@ -151,19 +151,18 @@ def test_iradon_angles():
     image = np.tri(size) + np.tri(size)[::-1]
     # Large number of projections: a good quality is expected
     nb_angles = 200
-    radon_image_200 = radon(image, theta=np.linspace(0, 180, nb_angles,
-                                                     endpoint=False))
-    reconstructed = iradon(radon_image_200)
+    theta = np.linspace(0, 180, nb_angles, endpoint=False)
+    radon_image_200 = radon(image, theta=theta, circle=False)
+    reconstructed = iradon(radon_image_200, circle=False)
     delta_200 = np.mean(abs(_rescale_intensity(image) - _rescale_intensity(reconstructed)))
     assert delta_200 < 0.03
     # Lower number of projections
     nb_angles = 80
-    radon_image_80 = radon(image, theta=np.linspace(0, 180, nb_angles,
-                                                    endpoint=False))
+    radon_image_80 = radon(image, theta=theta, circle=False)
     # Test whether the sum of all projections is approximately the same
     s = radon_image_80.sum(axis=0)
     assert np.allclose(s, s[0], rtol=0.01)
-    reconstructed = iradon(radon_image_80)
+    reconstructed = iradon(radon_image_80, circle=False)
     delta_80 = np.mean(abs(image / np.max(image) -
                            reconstructed / np.max(reconstructed)))
     # Loss of quality when the number of projections is reduced
@@ -175,8 +174,8 @@ def check_radon_iradon_minimal(shape, slices):
     theta = np.arange(180)
     image = np.zeros(shape, dtype=np.float)
     image[slices] = 1.
-    sinogram = radon(image, theta)
-    reconstructed = iradon(sinogram, theta)
+    sinogram = radon(image, theta, circle=False)
+    reconstructed = iradon(sinogram, theta, circle=False)
     print('\n\tMaximum deviation:', np.max(np.abs(image - reconstructed)))
     if debug:
         _debug_plot(image, reconstructed, sinogram)
@@ -197,8 +196,8 @@ def test_radon_iradon_minimal():
 
 def test_reconstruct_with_wrong_angles():
     a = np.zeros((3, 3))
-    p = radon(a, theta=[0, 1, 2])
-    iradon(p, theta=[0, 1, 2])
+    p = radon(a, theta=[0, 1, 2], circle=False)
+    iradon(p, theta=[0, 1, 2], circle=False)
     assert_raises(ValueError, iradon, p, theta=[0, 1, 2, 3])