changeset 6952:cedd10403877 octave-forge

Adding function `iradon\' and a helper function `rho_filter\'
author lxop
date Tue, 30 Mar 2010 07:37:36 +0000
parents 599a1386ef92
children ec1763dad25f
files main/image/INDEX main/image/inst/iradon.m main/image/inst/rho_filter.m
diffstat 3 files changed, 346 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/main/image/INDEX	Sun Mar 28 08:55:37 2010 +0000
+++ b/main/image/INDEX	Tue Mar 30 07:37:36 2010 +0000
@@ -56,6 +56,8 @@
  makelut applylut
  deriche
  radon
+ rho_filter
+ iradon
  nonmax_supress
 Black and white image functions
  bwarea
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/image/inst/iradon.m	Tue Mar 30 07:37:36 2010 +0000
@@ -0,0 +1,169 @@
+## Copyright (C) 2010 Alex Opie <lx_op@orcon.net.nz>
+##
+##
+## 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 Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## This program 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 this program; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @defun @var{recon} = iradon (@var{proj}, @var{theta}, @var{interp}, @
+##                              @var{filter}, @var{scaling}, @var{output_size})
+##
+## Performs filtered back-projection on the projections in @var{proj}
+## to reconstruct an approximation of the original image.
+##
+## @var{proj} should be a matrix whose columns are projections of an
+## image (or slice).  Each element of @var{theta} is used as the angle
+## (in degrees) that the corresponding column of @var{proj} was
+## projected at.  If @var{theta} is omitted, it is assumed that
+## projections were taken at evenly spaced angles between 0 and 180 degrees.
+## @var{theta} can also be a scalar, in which case it is taken as the
+## angle between projections if more than one projection is provided.
+## 
+## @var{interp} determines the type of interpolation that is used
+## in the back-projection.  It must be one of the types accepted by
+## @command{interp1}, and defaults to 'Linear' if it is omitted.
+##
+## @var{filter} and @var{scaling} determine the type of rho filter 
+## to apply.  See the help for @command{rho_filter} for their use.
+##
+## @var{output_size} sets the edge length of the output image (it
+## is always square).  This argument does not scale the image.  If it
+## is omitted, the length is taken to be
+## @group
+## 2 * floor (size (proj, 1) / (2 * sqrt (2))).
+## @end group
+## 
+## If @var{proj} was obtained using @command{radon}, there is no
+## guarantee that the reconstructed image will be exactly the same
+## size as the original.
+## 
+## @defunx [@var{recon}, @var{filt}] = iradon (...)
+##
+## This form also returns the filter frequency response in the vector
+## @var{filt}.
+## @end defun
+##
+## Performs filtered back-projection in order to reconstruct an
+## image based on its projections.
+##
+## Filtered back-projection is the most common means of reconstructing
+## images from CT scans.  It is a two step process: First, each of 
+## the projections is filtered with a `rho filter', so named due
+## to its frequency domain definition, which is simply |rho|, where
+## rho is the radial axis in a polar coordinate system.  Second, 
+## the filtered projections are each `smeared' across the image
+## space.  This is the back-projection part.
+##
+## Usage example:
+## @example
+##   P = phantom ();
+##   projections = radon (P, 1:179);
+##   reconstruction = iradon (filtered_projections, 1:179, 'Spline', 'Hann');
+##   figure, imshow (reconstruction, [])
+## @end example
+
+function [recon, filt] = iradon (proj, theta, interp, filter, scaling, output_size)
+  
+  if (nargin == 0)
+    error ("No projections provided to iradon");
+  endif
+  
+  if (nargin < 6)
+    output_size = 2 * floor (size (proj, 1) / (2 * sqrt (2)));
+  endif
+  if (nargin < 5) || (length (scaling) == 0)
+    scaling = 1;
+  endif
+  if (nargin < 4) || (length (filter) == 0)
+    filter = "Ram-Lak";
+  endif
+  if (nargin < 3) || (length (interp) == 0)
+    interp = "linear";
+  endif
+  if (nargin < 2) || (length (theta) == 0)
+    theta = 180 * (0:1:size (proj, 2) - 1) / size (proj, 2);
+  endif
+  
+  if (isscalar (theta)) && (size (proj, 2) != 1)
+    theta = (0:size (proj, 2) - 1) * theta;
+  endif
+  
+  if (length (theta) != size (proj, 2))
+    error ("iradon: Number of projections does not match number of angles")
+  endif
+  if (!isscalar (scaling))
+    error ("iradon: Frequency scaling value must be a scalar");
+  endif
+  if (!length (find (strcmpi (interp, {'nearest', 'linear', 'spline', \
+                                       'pchip', 'cubic'}))))
+    error ("iradon: Invalid interpolation method specified");
+  endif
+  
+  ## Convert angles to radians
+  theta *= pi / 180;
+  
+  ## First, filter the projections
+  [filtered, filt] = rho_filter (proj, filter, scaling);
+  
+  ## Next, back-project
+  recon = back_project (filtered, theta, interp, output_size);
+  
+endfunction
+
+
+function recon = back_project (proj, theta, interpolation, dim)
+  ## Make an empty image
+  recon = zeros (dim, dim);
+  
+  ## Zero pad the projections if the requested image
+  ## has a diagonal longer than the projections
+  diagonal = ceil (dim * sqrt (2)) + 1;
+  if (size (proj, 1) < diagonal)
+    diff = 2 * ceil ((diagonal - size (proj, 1)) / 2);
+    proj = padarray (proj, diff / 2);
+  endif
+  
+  ## Create the x & y values for each pixel
+  centre = floor ((dim + 1) / 2);
+  x = (0:dim - 1) - centre + 1;
+  x = repmat (x, dim, 1);
+   
+  y = (dim - 1: -1 : 0)' - centre;
+  y = repmat (y, 1, dim);
+  
+  ## s axis for projections, needed by interp1
+  s = (0:size (proj, 1) - 1) - floor (size (proj, 1) / 2);
+  
+  ## Sum each projection's contribution
+  for i = 1:length (theta)
+    s_dash = (x * cos (theta (i)) + y * sin (theta (i)));
+    interpolated = interp1 (s, proj (:, i), s_dash (:), ["*", interpolation]);
+    recon += reshape (interpolated, dim, dim);
+  endfor
+  
+  ## Scale the reconstructed values to their original size
+  recon *= pi / (2 * length (theta));
+  
+endfunction
+
+%!demo
+%! P = phantom ();
+%! figure, imshow (P, []), title ("Original image")
+%! projections = radon (P, 0:179);
+%! reconstruction = iradon (projections, 0:179, 'Spline', 'Hann');
+%! figure, imshow (reconstruction, []), title ("Reconstructed image")
+
+% $Log$
+% 2010-30-03 lxop
+% First submitted to Octave-Forge
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/image/inst/rho_filter.m	Tue Mar 30 07:37:36 2010 +0000
@@ -0,0 +1,175 @@
+## Copyright (C) 2010 Alex Opie <lx_op@orcon.net.nz>
+##
+##
+## 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 Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## This program 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 this program; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @defun {@var{filtered} =} rho_filter (@var{proj}, @var{type}, @var{scaling})
+##
+## Filters the parallel ray projections in the columns of @var{proj},
+## according to the filter type chosen by @var{type}.  @var{type}
+## can be chosen from
+## @itemize
+## @item 'none'
+## @item 'Ram-Lak' (default)
+## @item 'Shepp-Logan'
+## @item 'Cosine'
+## @item 'Hann'
+## @item 'Hamming'
+## @end itemize
+## 
+## If given, @var{scaling} determines the proportion of frequencies
+## below the nyquist frequency that should be passed by the filter.
+## The window function is compressed accordingly, to avoid an abrupt
+## truncation of the frequency response.
+##
+## @defunx {[@var{filtered}, @var{filter}] =} rho_filter (...)
+##
+## This form also returns the frequency response of the filter in
+## the vector @var{filter}.
+##
+## @end defun
+## 
+## Performs rho filtering on the parallel ray projections provided.
+##
+## Rho filtering is performed as part of the filtered back-projection
+## method of CT image reconstruction.  It is the filtered part of
+## the name.
+## The simplest rho filter is the Ramachadran-Lakshminarayanan (Ram-Lak),
+## which is simply |rho|, where rho is the radial component of spatial
+## frequency.  However, this can cause unwanted amplification of noise, 
+## which is what the other types attempt to minimise, by introducing
+## roll-off into the response.  The Hann and Hamming filters multiply
+## the standard response by a Hann or Hamming window, respectively.
+## The cosine filter is the standard response multiplied by a cosine
+## shape, and the Shepp-Logan filter multiplies the response with
+## a sinc shape.  The 'none' filter performs no filtering, and is
+## included for completeness and to enable incorporating this function
+## easily into scripts or functions that may offer the ability to choose
+## to apply no filtering.
+##
+## This function is designed to be used by the function @command{iradon},
+## but has been exposed to facilitate custom inverse radon transforms
+## and to more clearly break down the process for educational purposes.
+## The operations
+## @example
+##     filtered = rho_filter (proj);
+##     reconstruction = iradon (filtered, 1, 'linear', 'none');
+## @end example
+## are exactly equivalent to
+## @example
+##     reconstruction = iradon (proj, 1, 'linear', 'Ram-Lak');
+## @end example
+##
+## Usage example:
+## @example
+##   P = phantom ();
+##   projections = radon (P);
+##   filtered_projections = rho_filter (projections, 'Hamming');
+##   reconstruction = iradon (filtered_projections, 1, 'linear', 'none');
+##   figure, imshow (reconstruction, [])
+## @end example
+
+function [filtered_proj, filt] = rho_filter (proj, type, scaling)
+
+  filtered_proj = proj;
+
+  if (strcmpi (type, 'none'))
+    return;
+  endif
+  
+  if (nargin < 2) || (size (type) == 0)
+    type = 'ram-lak';
+  endif
+  
+  if (nargin < 3)
+    scaling = 1;
+  endif
+  
+  if (scaling > 1)
+    error ('Scaling factor must be in [0,1]');
+  endif
+  
+  ## Extend the projections to a power of 2
+  new_len = 2 * 2^nextpow2 (size (filtered_proj, 1));
+  filtered_proj (new_len, 1) = 0;
+  
+  ## Scale the frequency response
+  int_len = (new_len * scaling);
+  if (mod (floor (int_len), 2))
+    int_len = ceil (int_len);
+  else
+    int_len = floor (int_len);
+  endif
+  
+  ## Create the basic filter response
+  rho = scaling * (0:1 / (int_len / 2):1);
+  rho = [rho'; rho(end - 1:-1:2)'];
+  
+  ## Create the window to apply to the filter response
+  if (strcmpi (type, 'ram-lak'))
+    filt = 1;
+  elseif (strcmpi (type, 'hamming'))
+    filt = fftshift (hamming (length (rho)));
+  elseif (strcmpi (type, 'hann'))
+    filt = fftshift (hann (length (rho)));
+  elseif (strcmpi (type, 'cosine'))
+    f = 0.5 * (0:length (rho) - 1)' / length (rho);
+    filt = fftshift (sin (2 * pi * f));
+  elseif (strcmpi (type, 'shepp-logan'))
+    f = (0:length (rho) / 2)' / length (rho);
+    filt = sin (pi * f) ./ (pi * f);
+    filt (1) = 1;
+    filt = [filt; filt(end - 1:-1:2)];
+  else
+    error ('rho_filter: Unknown window type');
+  endif
+  
+  ## Apply the window
+  filt = filt .* rho;
+  
+  ## Pad the response to the correct length
+  len_diff = new_len - int_len;
+  if (len_diff != 0)
+    pad = len_diff / 2;
+    filt = padarray (fftshift (filt), pad);
+    filt = fftshift (filt);
+  endif
+  
+  filtered_proj = fft (filtered_proj);
+  
+  ## Perform the filtering
+  for i = 1:size (filtered_proj, 2)
+    filtered_proj (:, i) = filtered_proj (:, i) .* filt;
+  endfor
+  
+  ## Finally bring the projections back to the spatial domain
+  filtered_proj = real (ifft (filtered_proj));
+  
+  ## Chop the projections back to their original size
+  filtered_proj (size (proj, 1) + 1:end, :) = [];
+  
+endfunction
+
+%!demo
+%! P = phantom ();
+%! projections = radon (P);
+%! filtered_projections = rho_filter (projections, 'Hamming');
+%! reconstruction = iradon (filtered_projections, 1, 'linear', 'none');
+%! figure, imshow (reconstruction, [])
+
+% $Log$
+% 2010-03-19 lxop
+% Function completed to Matlab compatible level.