##  Copyright (C) 2015  Daniel Kraft <d@domob.eu>
##  GNU Octave level-set package.
##
##  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.  If not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn  {Function File} {@var{dh} =} ls_hausdorff_dist (@var{phi1}, @var{phi2}, @var{h} = 1)
## @deftypefnx {Function File} {@var{dh} =} ls_hausdorff_dist (@var{sd1}, @var{sd2}, @qcode{"sd"})
##
## Approximate the Hausdorff distance between two sets.  The sets are given
## by their level-set functions @var{phi1} and @var{phi2}.  The Hausdorff
## distance is calculated as the maximum difference between their distance
## functions.  (Note that it is the ordinary distance function here, not
## the @emph{signed} distance function!)
##
## If we neglect possible approximation errors in the distance function,
## the result @var{dh} is guaranteed to be a lower bound of the exact
## Hausdorff distance.  It is within the real distance by, roughly, @var{h}.
##
## The second call form assumes that the level-set functions of the
## domains are actually already signed distance functions @var{sd1} and
## @var{sd2}.  In this case, the grid spacing @var{h} is not necessary.
## Since there is no need to call @code{ls_distance_fcn}, the calculation
## can be performed faster in this case.
## 
## @seealso{ls_equal, ls_distance_fcn, ls_signed_distance}
## @end deftypefn

function dh = ls_hausdorff_dist (phi1, phi2, h = 1)
  if (nargin () < 2 || nargin () > 3)
    print_usage ();
  endif
  
  sdMode = false;
  if (isnumeric (h))
    if (!isscalar (h))
      error ("H must be scalar");
    endif
  else
    if (!strcmp (h, "sd"))
      error ("invalid option, expected H or \"sd\"");
    endif
    sdMode = true;
  endif

  if (!all (size (phi1) == size (phi2)))
    error ("PHI1 and PHI2 must be of the same size");
  endif

  if (sdMode)
    d1 = max (phi1, 0);
    d2 = max (phi2, 0);
  else
    d1 = ls_distance_fcn (phi1, h);
    d2 = ls_distance_fcn (phi2, h);
  endif
  assert (all (d1(:)) >= 0);
  assert (all (d2(:)) >= 0);

  dh = norm (d1(:) - d2(:), inf);
endfunction

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Tests.

% Test for error conditions.
%!error <Invalid call to>
%!  ls_hausdorff_dist (1)
%!error <Invalid call to>
%!  ls_hausdorff_dist (1, 2, 3, 4)
%!error <H must be scalar>
%!  ls_hausdorff_dist (1, 2, [3, 3])
%!error <invalid option, expected H or "sd">
%!  ls_hausdorff_dist (1, 2, "foo")

% Basic test with various circle combinations.
% Also exercise the "sd" variant.
%!test
%!  n = 50;
%!  x = linspace (-5, 5, n);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!
%!  phi1 = ls_genbasic (XX, YY, "sphere", [3, 0], 1);
%!  phi2 = ls_genbasic (XX, YY, "sphere", [-2, 0], 2);
%!  assert (ls_hausdorff_dist (phi1, phi2, h), 6, h);
%!
%!  % Make sure that phi isn't a signed-distance fcn already.
%!  % ls_genbasic should return the same, but make it explicit.
%!  rSq = XX.^2 + YY.^2;
%!  d = ls_hausdorff_dist (rSq - 4^2, rSq - 2^2, h);
%!  assert (d, 2, h);
%!  dWrong = ls_hausdorff_dist (rSq - 4^2, rSq - 2^2, "sd");
%!  assert (abs (d - dWrong) > 10 * h);
%!  dRight = ls_hausdorff_dist (sqrt (rSq) - 4, sqrt (rSq) - 2, "sd");
%!  assert (dRight, d, h);

% Test the case where the difference of the *signed* distance
% functions gives the wrong answer.
%!test
%!  n = 200;
%!  x = linspace (-5, 5, n);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!
%!  phiA = ls_genbasic (XX, YY, "sphere", [0, 0], 4);
%!  phiInner = ls_genbasic (XX, YY, "sphere", [0, 0], 2);
%!  phiB = ls_setdiff (phiA, phiInner);
%!
%!  d1 = ls_hausdorff_dist (phiA, phiB, h);
%!  d2 = ls_hausdorff_dist (phiB, phiA, h);
%!  assert (d1, d2);
%!  assert (d1, 2, 2 * h);
%!  dc = ls_hausdorff_dist (ls_complement (phiA), ...
%!                              ls_complement (phiB), h);
%!  assert (dc, 4, 2 * h);
%!
%!  sdA = ls_signed_distance (phiA, h);
%!  sdB = ls_signed_distance (phiB, h);
%!  sdDiff = norm (sdA(:) - sdB(:), inf);
%!  assert (abs (sdDiff - d1) > 10 * h);
