function [H,HNAN] = imagescnan(varargin)
%IMAGESCNAN Scale data and display as image with uncolored NaNs.
%
% SYNTAX:
% imagescnan(U)
% imagescnan(U,...,'NanColor',CNAN)
% imagescnan(U,...,'NanMask',MNAN)
% imagescnan(U,...,IOPT)
% imagescnan(X,Y,U,...)
% [H,HNAN] = imagescnan(...);
%
% INPUT:
% U - 2 dimensional N-by-M image or N-by-M-by-3 RGB image.
% X - 2 extrema X-axis data; or the M values; or the N-by-M values
% as obtained from MESHGRID (see DESCRIPTION below).
% DEFAULT: [1 N]
% Y - 2 extrema X-axis data; or the N values; or the N-by-M values
% as obtained from MESHGRID (see DESCRIPTION below).
% DEFAULT: [1 M]
% CNAN - Color for the NaNs elements. May be a char specifier or an [R
% G B] triplet specifying the color.
% DEFAULT: invisible (axes background color)
% MNAN - Elements to be ignored besides not finite values. May be an
% scalar or a logical M-by-N matrix indicating the elements to
% be ignored.
% DEFAULT: []
% IOPT - IMAGE function normal optional pair arguments like
% ('Parent',H) or/and CLIM like optional last argument as in
% IMAGESC.
% DEFAULT: none
%
% OUTPUT (all optional):
% H - Image handle
% HNAN - Handle of every ignored (NaN) value colored patch.
%
% DESCRIPTION:
% MATLAB function IMAGESC does not work properly with NaNs. This
% programs deals with this problem by including colored patches over
% this elements and maybe others specyfied by the user with MNAN.
%
% Besides, those functions does not work properly with X,Y values
% variable interval, but this functions does it by generating a whole
% new image of several rectangular patches, but whose centers may not
% lay in the specified coordinate (see NOTE below). This functionality
% is experimental and not recommended (see ADDITIONAL NOTES inside this
% program).
%
% In previous release, 2-dim input images were transformed into a
% 3-dim RGB image. This is not used anymore (see ADDITIONAL NOTES
% inside this file).
%
% NOTE:
% * Optional inputs use its DEFAULT value when not given or [].
% * Optional outputs may or not be called.
% * If X is a two element vector, min(X) will be the coordinate of the
% first column and max(X) of the last column.
% * If Y is a two element vector, min(Y) will be the coordinate of the
% first row and max(Y) of the last row.
% * If vector X-axis is decreasing U=fliplr(U) will be used.
% * If vector Y-axis is decreasing U=flipud(U) will be used.
% * When X or Y do not have a constant increasing/decreasing step, the
% vertices of the color rectangules are set in the middle of each
% pair of coordinates. For this reason its center may not lay on the
% specified coordinate, except on the coordinates at the edges where
% it always lays on the center.
% * To get a non-scaled image (IMAGE instead of IMAGESC) use:
% >> H = imagescnan(...);
% >> set(H,'CDataMapping','direct')
% * ADDITIONAL NOTES are included inside this file.
%
% EXAMPLE:
% % Compares with normal IMAGESC:
% N = 100;
% PNaNs = 0.10;
% U = peaks(N);
% U(round(1 + (N^2-1).*rand(N^2*PNaNs,1))) = NaN; % Adds NaNs
% subplot(221), imagesc(U)
% title('With IMAGESC: ugly NaNs')
% subplot(222), imagescnan(U)
% title('With IMAGESCNAN: uncolored NaNs')
% % Compares with SPY:
% subplot(223), spy(isnan(U))
% title('SPY(isnan(U))')
% subplot(224), imagescnan(isnan(U),'NaNMask',0), axis equal tight
% title('SPY with IMAGESCNAN')
%
% SEE ALSO:
% IMAGE, IMAGESC, COLORBAR, IMREAD, IMWRITE
% and
% CMAPPING, CBFREEZE by Carlos Vargas
% at http://www.mathworks.com/matlabcentral/fileexchange
%
%
% ---
% MFILE: imagescnan.m
% VERSION: 2.1 (Aug 20, 2009) (download)
% MATLAB: 7.7.0.471 (R2008b)
% AUTHOR: Carlos Adrian Vargas Aguilera (MEXICO)
% CONTACT: nubeobscura@hotmail.com
% ADDITIONAL NOTES:
% * I keep getting a kind of BUG with the edges of the patched NaNs. I
% added two NOTE inside this program that may fix this problem.
% Another way is to convert the intensity matrix U into RGB colors by
% using the CMAPPING function, as used by the first version of this
% program.
% * Besides, if the matrix is too large, sometimes there is an
% undocumented failure while drawing the patch NaNs. Is recommended
% to use U = cmapping(U,[],'k','discrete') instead, and change the
% CLIM to [min(U(:)) max(U(:))].
% * The use of not homogeneous step interval X,Y axes is not
% recommended because the program tries to put its value in the
% middle of the colored rectangule (as IMAGESC does) and soetimes the
% result may not be what the user wants. So this is for experimental
% use only.
% REVISIONS:
% 1.0 Released. (Jun 30, 2008)
% 1.1 Fixed bug when CAXIS used. Colorbar freezed colormap. Fixed
% bug in color vector input (Found by Greg King) and now
% accets RGB image as input. (Jul 14, 2008)
% 2.0 Totally rewritten code. Do not converts to RGB anymore. Do not
% freezes the colormap anymore. Do not output any colorbar. New
% X and Y variable steps accepted input. Now uses patches. (Jun
% 08, 2009)
% 2.1 Fixed bug with RGB input. Added a NOTE about the use of
% CMAPPING. (Aug 20, 2009)
% DISCLAIMER:
% imagescnan.m is provided "as is" without warranty of any kind, under
% the revised BSD license.
% Copyright (c) 2008,2009 Carlos Adrian Vargas Aguilera
% INPUTS CHECK-IN
% -------------------------------------------------------------------------
% Initializes:
X = [];
Y = [];
CNAN = [];
MNAN = [];
ha = [];
% Checks number of inputs:
if nargin<1
error('CVARGAS:imagescnan:notEnoughInputs',...
'At least 1 input is required.')
elseif nargout>2
error('CVARGAS:imagescnan:tooManyOutputs',...
'At most 2 outputs are allowed.')
end
% Gets X,Y,U:
if ((nargin==1) || (nargin==2))
U = varargin{1};
varargin(1) = [];
else
if (isnumeric(varargin{1}) && isnumeric(varargin{2}) && ...
isnumeric(varargin{3}))
X = varargin{1};
Y = varargin{2};
U = varargin{3};
varargin(1:3) = [];
else
U = varargin{1};
varargin(1) = [];
end
end
% Check U:
ndim = ndims(U);
if (ndim==2)
[M,N] = size(U);
O = 1;
elseif (ndim==3)
[M,N,O] = size(U);
if (O~=3)
error('CVARGAS:imagescnan:incorrectRgbImage',...
'RGB image must be of size M-by-N-by-3.')
end
else
error('CVARGAS:imagescnan:incorrectImageSize',...
'Image must be 2-dimensional or a 3-dim RGB image.')
end
% Check X:
aequal = true; % Equal intervals on x-axis?
dX = [];
if isempty(X)
X = [1 N];
else
if (ndims(X)>2)
error('CVARGAS:imagescnan:incorrectXDims',...
'X must be a vector or a matrix as a result of MESHGRID.')
end
if any(~isfinite(X(:)))
error('CVARGAS:imagescnan:incorrectXValue',...
'X elements must be numeric and finite.')
end
[Mx,Nx] = size(X);
if ((Mx*Nx)==2)
if X(2)(eps*max(abs(dX(:)))*1000))
error('CVARGAS:imagescnan:incorrectXMatrix',...
'X matrix must be as generated by MESHGRID.')
end
X = X(1,:);
elseif (~any([Mx Nx]==1) && ~((Mx*Nx)==N))
error('CVARGAS:imagescnan:incorrectXSize',...
'X must be an scalar or a matrix.')
end
% Forces ascending x-axis:
[X,I] = sort(X(:).');
for k = 1:O % Fixed bug Aug 2009
U(:,:,k) = U(:,I,k);
end
clear I
% Checks equal intervals:
dX = diff(X);
if any(abs(dX(1)-dX(2:end))>(eps*max(dX)*1000))
if aequal
aequal = false;
end
else
X = [X(1) X(end)];
dX = [];
end
end
end
% Check Y:
dY = [];
if isempty(Y)
Y = [1 M];
else
if (ndims(Y)>2)
error('CVARGAS:imagescnan:incorrectYDims',...
'Y must be a vector or a matrix as a result of MESHGRID.')
end
if any(~isfinite(Y(:)))
error('CVARGAS:imagescnan:incorrectYValue',...
'Y elements must be numeric and finite.')
end
[My,Ny] = size(Y);
if ((My*Ny)==2)
if Y(2)(eps*max(abs(dY(:)))*1000))
error('CVARGAS:imagescnan:incorrectYMatrix',...
'Y matrix must be as generated by MESHGRID.')
end
Y = Y(:,1);
elseif (~any([My Ny]==1) && ~((My*Ny)==M))
error('CVARGAS:imagescnan:incorrectYSize',...
'Y must be an scalar or a matrix.')
end
% Forces ascending y-axis:
[Y,I] = sort(Y(:).');
for k = 1:O % Fixed bug Aug 2009
U(:,:,k) = U(I,:,k);
end
clear I
% Checks equal intervals:
dY = diff(Y);
if any(abs(dY(1)-dY(2:end))>(eps*max(dY)*1000))
if aequal
aequal = false;
end
else
Y = [Y(1) Y(end)];
dY = [];
end
end
end
% Checks varargin:
ind = [];
Nopt = length(varargin);
for k = 1:Nopt-1
if (~isempty(varargin{k}) && ischar(varargin{k}))
if strncmpi(varargin{k},'NanColor',4)
CNAN = varargin{k+1};
ind = [ind k k+1];
elseif strncmpi(varargin{k},'NanMask',4)
MNAN = varargin{k+1};
ind = [ind k k+1];
elseif (strncmpi(varargin{k},'Parent',2) && isempty(CNAN))
try
CNAN = get(varargin{k+1},'Color');
ha = varargin{k+1};
catch
error('CVARGAS:imagescnan:incorrectParentHandle',...
'''Parent'' must be a valid axes handle.')
end
end
end
end
varargin(ind) = [];
Nargin = length(varargin);
% Check ha:
if isempty(ha)
ha = gca;
end
% Check CNAN:
if isempty(CNAN)
CNAN = get(ha,'Color');
elseif ischar(CNAN)
switch lower(CNAN)
case 'y', CNAN = [1 1 0];
case 'm', CNAN = [1 0 0];
case 'c', CNAN = [0 1 1];
case 'r', CNAN = [1 0 0];
case 'g', CNAN = [0 1 0];
case 'b', CNAN = [0 0 1];
case 'w', CNAN = [1 1 1];
case 'k', CNAN = [0 0 0];
otherwise
error('CVARGAS:imagescnan:incorrectNancString',...
'Color string must be a valid color identifier. One of ''ymcrgbwk''.')
end
elseif isnumeric(CNAN) && (length(CNAN)==3)
CNAN = CNAN(:).'; % Forces row vector.
else
error('CVARGAS:imagescnan:incorrectNancInput',...
'Not recognized CNAN input.')
end
% Check MNAN:
if isempty(MNAN)
MNAN = any(~isfinite(U),3);
else
if (ndims(MNAN)==2)
[Mm,Nm] = size(MNAN);
if ((Mm*Nm)==1)
MNAN = (any(~isfinite(U),3) | any(U==MNAN,3));
elseif ((Mm==M) && (Nm==N) && islogical(MNAN))
MNAN = (any(~isfinite(U),3) | MNAN);
else
error('CVARGAS:imagescnan:incorrectNanmSize',...
'MNAN must be an scalar or a logical matrix of size M-by-N.')
end
else
error('CVARGAS:imagescnan:incorrectNanmDims',...
'MNAN must be an scalar or a matrix.')
end
end
% -------------------------------------------------------------------------
% MAIN
% -------------------------------------------------------------------------
% Generates the image:
if aequal
% IMAGESC way.
H = imagesc(X,Y,U,varargin{:});
else
% PATCH way.
% Check clim:
if (rem(Nargin,2)==1)
clim = varargin{end};
varargin(end) = [];
if ((length(clim)~=2) || (clim(1)>clim(2)))
error('CVARGAS:imagescnan:incorrectClimInput',...
'clim must be a 2 element increasing vector.')
end
else
clim = [];
end
% Generates vertices between coordinates (coordinates may not be at the
% center of these vertices):
if (length(X)~=N)
X = (0:N-1)*((X(2)-X(1))/(N-1)) + X(1);
end
if (length(Y)~=M)
Y = (0:M-1)*((Y(2)-Y(1))/(M-1)) + Y(1);
end
if isempty(dX)
dX = diff(X);
end
if isempty(dY)
dY = diff(Y);
end
[X,Y] = meshgrid([X(1)-dX(1)/2 X+dX([1:N-1 N-1])/2],...
[Y(1)-dY(1)/2 Y+dY([1:M-1 M-1])/2]);
% Generates faces:
ind = (1:(M+1)*N)';
ind(M+1:M+1:end) = [];
% Generates patches:
H = patch(...
'Vertices' ,[X(:) Y(:)],...
'Faces' ,[ind ind+1 ind+M+2 ind+M+1],...
'FaceVertexCData',U(:),...
'FaceColor' ,'flat',...
'EdgeColor' ,'none',... % NOTE: Sometimes this is not required.
varargin{:});
set(ha,...
'YDir' ,'reverse',...
'View' ,[0 90],...
'Box' ,'on',...
'Layer','top')
axis(ha,'tight')
% Sets clim:
if ~isempty(clim)
set(ha,'CLim',clim)
else
set(ha,'CLimMode','auto')
end
end
% Adds NaNs patches:
if any(MNAN(:))
if aequal
% dX and dY is constant:
[MNAN,NNAN] = ind2sub([M,N],find(MNAN));
Nnan = length(MNAN);
dX = (X(2)-X(1))/(N-1)/2;
dY = (Y(2)-Y(1))/(M-1)/2;
HNAN = patch(repmat((X(1)+(NNAN(:)'-1)*(2*dX)),4,1) + ...
(repmat([-1 1 1 -1]'*dX,1,Nnan)),...
repmat((Y(1)+(MNAN(:)'-1)*(2*dY)),4,1) + ...
(repmat([1 1 -1 -1]'*dY,1,Nnan)),...
CNAN,...
'EdgeColor',CNAN,... 'EdgeColor','none',...
varargin{1:Nargin-rem(Nargin,2)});
else
% dX and/or dY is not constant:
MNAN = find(MNAN);
HNAN = patch(...
'Vertices' ,[X(:) Y(:)],...
'Faces' ,[ind(MNAN) ind(MNAN)+1 ind(MNAN)+M+2 ind(MNAN)+M+1],...
'FaceColor' ,CNAN,...
'EdgeColor' ,'none',... 'EdgeColor',CNAN,... % NOTE: may be better?
varargin{:});
end
else
HNAN = [];
end
% OUTPUTS CHECK-OUT
% -------------------------------------------------------------------------
% Clears outputs?:
if (nargout==0)
clear H
end
% [EOF] imagescnan.m