function A = hmf(A,n)

%HMF Hybrid median filtering.
%   B = HMF(A,N) performs hybrid median filtering of the matrix A using a
%   NxN box. Hybrid median filtering preserves edges better than a NxN
%   square kernel-based median filter because data from different spatial
%   directions are ranked separately. Three median values are calculated in
%   the NxN box: MR is the median of horizontal and vertical R pixels, and
%   MD is the median of diagonal D pixels. The filtered value is the median
%   of the two median values and the central pixel C: median([MR,MD,C]).
%   For N = 5:
%        |D  *  R  *  D|
%        |*  D  R  D  *|
%        |R  R  C  R  R|
%        |*  D  R  D  *|
%        |D  *  R  *  D|
%
%   B = HMF(A) uses N = 5 (default value).
%
%   A can be a 2-D array or an RGB image. If A is an RGB image, hybrid
%   median filtering is performed in the HSV color space.
%
%   Notes
%   -----
%   1) N must be odd. If N is even then N is incremented by 1.
%   2) The Image Processing Toolbox is required.
%   3) If the function NANMEDIAN exists (Statistics Toolbox), NaN are
%      treated as missing values and are ignored.
%
%   Examples
%   --------
%     % -- original grayscale image --
%     I = imread('eight.tif');
%     % noisy image
%     J = imnoise(I,'salt & pepper',0.03);
%     % hybrid median filtering
%     K = hmf(J);
%     % figures
%     subplot(121),imshow(J),subplot(122),imshow(K)
%     
%     % -- original RGB image --
%     [I,map] = imread('trees.tif');
%     I = ind2rgb(I,map);
%     % noisy image
%     J = imnoise(I,'salt & pepper',0.02);
%     % hybrid median filtering (using a 9x9 box)
%     K = hmf(J,9);
%     % figures
%     subplot(121),imshow(J),subplot(122),imshow(K)
% 
%   See also MEDFILT2, MEDFILT2RGB, MEDFILT3, COLFILT
%
%   -- Damien Garcia -- 2007/08, revised 2010/02 

error(nargchk(1,2,nargin));
if nargin==1, n = 5; end

if ~isscalar(n) || n<0, n = 5; end

% --- n must be odd. If not then n = n+1
n = round(n);
if rem(n+1,2)~=0, n = n+1; end

% --- Do we have an RGB image?
% RGB images can be only be uint8, uint16, single, or double
isRGB = ndims(A)==3 && (isfloat(A) || isa(A,'uint8') || isa(A,'uint16'));
% ---- Adapted from the obsolete function ISRGB ----
if isRGB
   if isfloat(A)
      % At first just test a small chunk to get a possible quick negative  
      mm = size(A,1);
      nn = size(A,2);
      chunk = A(1:min(mm,10),1:min(nn,10),:);         
      isRGB = (min(chunk(:))>=0 && max(chunk(:))<=1);
      % If the chunk is an RGB image, test the whole image
      if isRGB
         isRGB = (min(A(:))>=0 && max(A(:))<=1);
      end
   end
end
% ---- end of isrgb ----

assert(isRGB | ndims(A)==2,...
    'The input must be a 2-D array or an RGB image.')

classA = class(A);

% --- If the input is an RGB image, HMF is used in the HSV color space
if isRGB
    A = rgb2hsv(A);
    for k = 1:3, A(:,:,k) = hmf(A(:,:,k),n); end
    A = hsv2rgb(A);
    % HSV2RGB returns a double: change the class if necessary
    switch classA
        case 'uint8'
            A = uint8(A*255);
        case 'uint16'
            A = uint16(A*65535);
        case 'single'
            A = single(A);
    end
    return
end

% --- Plus & Cross masks
Plus = false(n,n);
Plus((n+1)/2,:) = true;
Plus(:,(n+1)/2) = true;
Plus = Plus(:);
Cross = false(n,n);
Cross((1:n)+n*(0:n-1)) = true;
Cross((1:n)+n*((n-1):-1:0)) = true;
Cross = Cross(:);


%% --- Hybrid median filtering

% Note: NANMEDIAN is used if this function exists (Statistics Toolbox)
existNaNmedian = exist('nanmedian','file');

% --- the COLFILT function zero-pads! => replicate boundaries
A = padarray(A,[(n-1)/2 (n-1)/2],'replicate');

M1 = colfilt(A,[n n],'sliding',@CrossMedian);
M2 = colfilt(A,[n n],'sliding',@PlusMedian);
if existNaNmedian
    A = nanmedian(cat(3,A,M1,M2),3);
else
    A = median(cat(3,A,M1,M2),3);
end

% remove the borders that were added by PADARRAY
A = A((n+1)/2:end-(n-1)/2,(n+1)/2:end-(n-1)/2);

function CM = CrossMedian(X)
    ncol = size(X,2);
    I = repmat(Cross,[1 ncol]);
    X = reshape(X(I),[2*n-1 ncol]);
    if existNaNmedian
        CM = nanmedian(X);
    else
        CM = median(X);
    end
end

function PM = PlusMedian(X)
    ncol = size(X,2);
    I = repmat(Plus,[1 ncol]);
    X = reshape(X(I),[2*n-1 ncol]);
    if existNaNmedian
        PM = nanmedian(X);
    else
        PM = median(X);
    end
end

end
