changeset 845:0e5fd48b7d94

phantom: correct ellipses for the original Shepp-Logan model. * phantom.m: change the additive intensity of the first ellipse from 2 to 1 so it is displayed correctly in doube precision (even though original paper does use a value of 2. See comments on source for details). Correct additive intensity of ellipse "g" from 0.02 to 0.01. Rewrite documentation to use the deftypefn macros. Remove unecessary subfunctions select_phantom() and read_args() which are only called once anyway. * NEWS: mention changes to function.
author Carnë Draug <carandraug@octave.org>
date Fri, 06 Dec 2013 21:12:41 +0000
parents b52546b11470
children 00f512825eed
files NEWS inst/phantom.m
diffstat 2 files changed, 164 insertions(+), 140 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS
+++ b/NEWS
@@ -120,6 +120,10 @@
     object, no longer the indices for the elements in the object boundaries
     only. The connectivity default was changed to 8.
 
+ ** The original Shepp-Logan model in the function phantom as been changed to
+    return all values in the range [0 1] rather than [0 2] by changing the
+    intensity of the first ellipse from 2 to 1.
+
  ** Other functions that have been changed for smaller bugfixes, increased
     Matlab compatibility, or performance:
 
--- a/inst/phantom.m
+++ b/inst/phantom.m
@@ -1,4 +1,5 @@
 ## Copyright (C) 2010 Alex Opie <lx_op@orcon.net.nz>
+## Copyright (C) 2013 Carnë Draug <carandraug@octave.org>
 ##
 ## This program 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
@@ -14,40 +15,63 @@
 ## this program; if not, see <http://www.gnu.org/licenses/>.
 
 ## -*- texinfo -*-
-##
-## @defun {@var{P} =} phantom ('Shepp-Logan', @var{n})
+## @deftypefn  {Function File} {@var{P}} = phantom ()
+## @deftypefnx {Function File} {@var{P}} = phantom (@var{model})
+## @deftypefnx {Function File} {@var{P}} = phantom (@var{E})
+## @deftypefnx {Function File} {@var{P}} = phantom (@dots{}, @var{n})
+## @deftypefnx {Function File} {[@var{P}, @var{E}]} = phantom (@dots{})
+## Create computational phantom head.
 ##
-## Produces the Shepp-Logan phantom, with size @var{n} x @var{n}.
-## If @var{n} is omitted, 256 is used.
+## A phantom is a known object (either real or purely mathematical) that is
+## used for testing image reconstruction algorithms.  The Shepp-Logan phantom
+## is a popular mathematical model of a cranial slice, made up of a set of
+## overlaying ellipses.  This allows rigorous testing of computed tomography
+## (CT) algorithms as it can be analytically transformed with the radon
+## transform (see the functions @code{radon} and @code{iradon}).
 ##
-## @defunx {@var{P} =} phantom ('Modified Shepp-Logan', @var{n})
+## The phantom @var{P}, is created by overlaying ellipses as defined by the
+## matrix @var{E} or one of the standard @var{model}s, in a square of size
+## @var{n} by @var{n} (defaults to 256).
+##
+## The available standard @var{model}s (use the output argument @var{E} to
+## inspect the details of the different ellipses) are:
 ##
-## Produces a modified version of the Shepp-Logan phantom which has
-## higher contrast than the original,  with size @var{n} x @var{n}.
-## If @var{n} is omitted, 256 is used.
-##
-## @defunx {@var{P} =} phantom (@var{ellipses}, @var{n})
+## @table @asis
+## @item @qcode{"Sheep-Logan"}
+## This is the original Sheep-Logan model with 10 ellipses as described in
+## Table 1 of @cite{Shepp, Lawrence A., and Benjamin F. Logan. "The Fourier
+## reconstruction of a head section." Nuclear Science, IEEE Transactions on
+## 21, no. 3 (1974): 21-43.}
 ##
-## Produces a custom phantom using the ellipses described in @var{ellipses}.
-## Each row of @var{ellipses} describes one ellipse, and must have 6 columns:
-## @{I, a, b, x0, y0, phi@}:
-##  @table @abbr
-##  @item I
-##    is the additive intensity of the ellipse
+## @item @qcode{"Modified Shepp-Logan"} (default)
+## A modification of the original Shepp-Logan model to give a better contrast,
+## as described in Table B.3 of @cite{Toft, Peter Aundal. "The radon
+## transform-theory and implementation." PhD diss., Department of Mathematical
+## Modelling, Technical University of Denmark, 1996.}
+##
+## @end table
 ##
-##  @item a
+## A 6 column matrix @var{E} can be used to generate a custom image by
+## superimposing arbitrary ellipses.  Each row defines a single ellipse, with
+## each column for the values of @{I, a, b, x0, y0, phi@}:
+##
+## @table @abbr
+## @item I
+##   is the additive intensity of the ellipse
+##
+## @item a
 ##    is the length of the major axis
 ##
-##  @item b
+## @item b
 ##    is the length of the minor axis
 ##
-##  @item x0
+## @item x0
 ##    is the horizontal offset of the centre of the ellipse
 ##
-##  @item y0
-##    is the vercal offset of the centre of the ellipse
+## @item y0
+##    is the vertical offset of the centre of the ellipse
 ##
-##  @item phi
+## @item phi
 ##    is the counterclockwise rotation of the ellipse in degrees,
 ##    measured as the angle between the x axis and the ellipse major axis.
 ##
@@ -55,153 +79,149 @@
 ##
 ## The image bounding box in the algorithm is @{[-1, -1], [1, 1]@}, so the
 ## values of a, b, x0, y0 should all be specified with this in mind.
-## If @var{n} is omitted, 256 is used.
-##
-## @defunx {@var{P} =} phantom (@var{n})
-##
-## Creates a modified Shepp-Logan phantom with size @var{n} x @var{n}.
-##
-## @defunx {@var{P} = } phantom ()
-##
-## Creates a modified Shepp-Logan phantom with size 256 x 256.
-## @end defun
-##
-## Create a Shepp-Logan or modified Shepp-Logan phantom.
-##
-## A phantom is a known object (either real or purely mathematical) that
-## is used for testing image reconstruction algorithms.  The Shepp-Logan
-## phantom is a popular mathematical model of a cranial slice, made up
-## of a set of ellipses.  This allows rigorous testing of computed 
-## tomography (CT) algorithms as it can be analytically transformed with
-## the radon transform (see the function @command{radon}).
 ##
 ## Example:
-## 
+##
 ## @example
-##   P = phantom (512);
-##   imshow (P, []);
+## @group
+## P = phantom (512);
+## imshow (P);
+## @end group
 ## @end example
 ##
-## References:
-##
-##  Shepp, L. A.; Logan, B. F.; Reconstructing Interior Head Tissue 
-##  from X-Ray Transmissions, IEEE Transactions on Nuclear Science,
-##  Feb. 1974, p. 232.
-##
-##  Toft, P.; "The Radon Transform - Theory and Implementation", Ph.D. thesis,
-##  Department of Mathematical Modelling, Technical University 
-##  of Denmark, June 1996.
+## @seealso{iradon, radon}
+## @end deftypefn
+
+function [head, ellipses] = phantom (varargin)
+
+  if (nargin > 2)
+    print_usage ()
+  endif
+
+  ## Would be really cool if we implemented a 3D phantom as already described
+  ## in Cheng Guan Koay, Joelle E. Sarlls, and Evren Ozarslan (2007).
+  ## "Three-Dimensional Analytical Magnetic Resonance Imaging Phantom in the
+  ## Fourier Domain". Magnetic Resonance in Medicine 58:430 - 436.
+  ## The Table 1 on their paper to generate the 3D model, would take 8 columns,
+  ## an extra value for z axis coordinates, and extra axis length.
+  ## They mention other phantom heads as more canonical 3D head phantoms (read
+  ## the introduction)
+
+  ## Defaults
+  ellipses  = mod_shepp_logan ();
+  n         = 256;
+
+  if (nargin)
+    ## Check validity of N
+    chk_n = @(x) isnumeric (x) && isscalar (x) && ceil (x) == x;
 
-function p = phantom (varargin)
+    in = varargin{1};
+    if (ischar (in))
+      switch (tolower (in))
+        case "shepp-logan",           ellipses = shepp_logan ();
+        case "modified shepp-logan",  ellipses = mod_shepp_logan ();
+        otherwise
+          error ("phantom: unknown MODEL `%s'", in);
+      endswitch
+    elseif (isnumeric (in) && ndims (in) == 2 && columns (in) == 6)
+      ellipses = in;
+    elseif (chk_n (in))
+      n = in;
+      ## If N is the first argument, we can't have more
+      if (nargin > 1)
+        print_usage ();
+      endif
+    else
+      error ("phantom: first argument must either be MODEL, E, or N");
+    endif
 
-  [n, ellipses] = read_args (varargin {:});
+    ## If there is a second input argument, must be N
+    if (nargin > 1)
+      if (chk_n (varargin{2}))
+        n = varargin{2};
+      else
+        error ("phantom: N must be numeric scalar");
+      endif
+    endif
+  endif
 
-  # Blank image
-  p = zeros (n);
+  ## Initialize blank image
+  head = zeros (n);
 
   # Create the pixel grid
   xvals = (-1 : 2 / (n - 1) : 1);
   xgrid = repmat (xvals, n, 1);
 
-  for i = 1:size (ellipses, 1)    
+  for i = 1:rows (ellipses)
     I   = ellipses (i, 1);
     a2  = ellipses (i, 2)^2;
     b2  = ellipses (i, 3)^2;
     x0  = ellipses (i, 4);
     y0  = ellipses (i, 5);
     phi = ellipses (i, 6) * pi / 180;  # Rotation angle in radians
-    
-    # Create the offset x and y values for the grid
+
+    ## Create the offset x and y values for the grid
     x = xgrid - x0;
     y = rot90 (xgrid) - y0;
-    
-    cos_p = cos (phi); 
+
+    cos_p = cos (phi);
     sin_p = sin (phi);
-    
-    # Find the pixels within the ellipse
+
+    ## Find the pixels within the ellipse
     locs = find (((x .* cos_p + y .* sin_p).^2) ./ a2 ...
-     + ((y .* cos_p - x .* sin_p).^2) ./ b2 <= 1);
-    
-    # Add the ellipse intensity to those pixels
-    p (locs) = p (locs) + I;
+      + ((y .* cos_p - x .* sin_p).^2) ./ b2 <= 1);
+
+    ## Add the ellipse intensity to those pixels
+    head(locs) += I;
   endfor
 endfunction
 
-function [n, ellip] = read_args (varargin)
-  n = 256;
-  ellip = mod_shepp_logan ();
-  
-  if (nargin == 1)
-    if (ischar (varargin {1}))
-      ellip = select_phantom (varargin {1});
-    elseif (numel (varargin {1}) == 1)
-      n = varargin {1};
-    else
-      if (size (varargin {1}, 2) != 6)
-        error ("Wrong number of columns in user phantom");
-      endif
-      ellip = varargin {1};
-    endif
-  elseif (nargin == 2)
-    n = varargin {2};
-    if (ischar (varargin {1}))
-      ellip = select_phantom (varargin {1});
-    else
-      if (size (varargin {1}, 2) != 6)
-        error ("Wrong number of columns in user phantom");
-      endif
-      ellip = varargin {1};
-    endif
-  elseif (nargin > 2)
-    warning ("Extra arguments passed to phantom were ignored");
-  endif
+function ellipses = shepp_logan ()
+  ## Standard head phantom, taken from Shepp & Logan
+  ##
+  ## Note that the first element of this matrix, the gray value for the first
+  ## ellipse (human skull), has a value of 1.0 even though the paper gives it a
+  ## a value of 2.0 (see Table 1 on page 32 and Figure 1 on page 34). This
+  ## change is so that the **head** intensity values appear in the range [0 1]
+  ## rather than the range [1 2].
+  ##
+  ##    **The problem with this**
+  ##
+  ## The background still need an intensity value which is going to be 0. This
+  ## means that we can't distinguish between the background and the ventricles
+  ## (ellipse "c" and "d" whose intensities are a + b + c and a + b + d, see
+  ## Figure 1) since they will have an intensity value of 0 (actually, because
+  ## of machine precision the ventricules will be almost 0). But if we didn't
+  ## made this change, the ** image** range would be [0 2] with all of the head
+  ## details compressed in half of the display range. Also, Matlab seems to be
+  ## doing the same.
+  persistent ellipses = [ 1     0.69    0.92     0      0         0
+                         -0.98  0.6624  0.874    0     -0.0184    0
+                         -0.02  0.11    0.31     0.22   0       -18
+                         -0.02  0.16    0.41    -0.22   0        18
+                          0.01  0.21    0.25     0      0.35      0
+                          0.01  0.046   0.046    0      0.1       0
+                          0.01  0.046   0.046    0     -0.1       0
+                          0.01  0.046   0.023   -0.08  -0.605     0
+                          0.01  0.023   0.023    0     -0.606     0
+                          0.01  0.023   0.046    0.06  -0.605     0];
 endfunction
 
-function e = select_phantom (name)
-  if (strcmpi (name, 'shepp-logan'))
-    e = shepp_logan ();
-  elseif (strcmpi (name, 'modified shepp-logan'))
-    e = mod_shepp_logan ();
-  else
-    error ("Unknown phantom type: %s", name);
-  endif
+function ellipses = mod_shepp_logan ()
+  ## Modified version of Shepp & Logan's head phantom, adjusted to improve
+  ## contrast.  Taken from Peter Toft PhD thesis, Table B.3
+  persistent ellipses = [ 1.0   0.69    0.92     0.0    0.0       0
+                         -0.8   0.6624  0.874    0.0   -0.0184    0
+                         -0.2   0.11    0.31     0.22   0.0     -18
+                         -0.2   0.16    0.41    -0.22   0.0      18
+                          0.1   0.21    0.25     0.0    0.35      0
+                          0.1   0.046   0.046    0.0    0.1       0
+                          0.1   0.046   0.046    0.0   -0.1       0
+                          0.1   0.046   0.023   -0.08  -0.605     0
+                          0.1   0.023   0.023    0.0   -0.606     0
+                          0.1   0.023   0.046    0.06  -0.605     0];
 endfunction
 
-function e = shepp_logan
-  #  Standard head phantom, taken from Shepp & Logan
-  e = [  2,   .69,   .92,    0,      0,   0;  
-      -.98, .6624, .8740,    0, -.0184,   0;
-      -.02, .1100, .3100,  .22,      0, -18;
-      -.02, .1600, .4100, -.22,      0,  18;
-       .01, .2100, .2500,    0,    .35,   0;
-       .01, .0460, .0460,    0,     .1,   0;
-       .02, .0460, .0460,    0,    -.1,   0;
-       .01, .0460, .0230, -.08,  -.605,   0; 
-       .01, .0230, .0230,    0,  -.606,   0;
-       .01, .0230, .0460,  .06,  -.605,   0];
-endfunction
-
-function e = mod_shepp_logan
-  #  Modified version of Shepp & Logan's head phantom, 
-  #  adjusted to improve contrast.  Taken from Toft.
-  e = [  1,   .69,   .92,    0,      0,   0;
-      -.80, .6624, .8740,    0, -.0184,   0;
-      -.20, .1100, .3100,  .22,      0, -18;
-      -.20, .1600, .4100, -.22,      0,  18;
-       .10, .2100, .2500,    0,    .35,   0;
-       .10, .0460, .0460,    0,     .1,   0;
-       .10, .0460, .0460,    0,    -.1,   0;
-       .10, .0460, .0230, -.08,  -.605,   0; 
-       .10, .0230, .0230,    0,  -.606,   0;
-       .10, .0230, .0460,  .06,  -.605,   0];
-endfunction
-
-#function e = ??
-#  # Add any further phantoms of interest here
-#  e = [ 0, 0, 0, 0, 0, 0;
-#        0, 0, 0, 0, 0, 0];
-#endfunction
-
 %!demo
 %! P = phantom (512);
-%! imshow (P, []);
+%! imshow (P);