function hIm = imaqmontage(data, varargin)
%IMAQMONTAGE Display a sequence of image frames as a montage.
% 
%    IMAQMONTAGE(FRAMES) displays a montage of image frames in a MATLAB 
%    figure window using IMAGESC.
%
%    FRAMES can be any data set returned by GETDATA, PEEKDATA, or 
%    GETSNAPSHOT.
% 
%    IMAQMONTAGE(OBJ) calls GETSNAPSHOT on video input object OBJ and displays 
%    a single image frame in a MATLAB figure window using IMAGESC. OBJ must 
%    be a 1x1 video input object.
% 
%    IMAQMONTAGE(..., CLIM) where CLIM = [CLOW CHIGH] can specify the
%    image scaling. See IMAGESC for more information.
%
%    IMAQMONTAGE(..., 'CLim', CLIM, 'Parent', PARENT) where CLIM is as above, 
%    and PARENT is a valid AXES object that allows you to specify where the 
%    montage is displayed.  One or both property/value pairs can be 
%    specified. 
%
%    H = IMAQMONTAGE(...) returns a handle to an IMAGE object.
%
%    See also IMAGESC, IMAQHELP, IMAQDEVICE/GETDATA, IMAQDEVICE/PEEKDATA, 
%             IMAQDEVICE/GETSNAPSHOT, COLORMAP.

%    CP 1-22-03
%    Copyright 2001-2010 The MathWorks, Inc. 
%    $Revision: 1.1.6.5 $  $Date: 2010/12/27 01:17:37 $

% Error check.
argsIn = nargin;
clim = [];
parent = [];
if argsIn==0,
    error(message('imaqlib:imaqmontage:noFrames'));
elseif argsIn==1,
    % Initialize CLIM.
    clim = [];
elseif argsIn==2,
    clim = localCheckClim(varargin{1});
else
    if ~strcmp(varargin{1},'CLim') && ~strcmp(varargin{1},'Parent')
        error(message('imaqlib:imaqmontage:invalidProperty'));
    end
    switch varargin{1}
        case 'CLim'
            clim = localCheckClim(varargin{2});
        case 'Parent'
            parent = varargin{2};
    end
    if argsIn > 3 && argsIn < 6
        if ~strcmp(varargin{3},'CLim') && ~strcmp(varargin{3},'Parent')
            error(message('imaqlib:imaqmontage:invalidProperty'));
        end
        switch varargin{3}
            case 'CLim'
                clim = localCheckClim(varargin{4});
            case 'Parent'
                parent = varargin{4};
        end
    elseif argsIn >= 6
        error(message('imaqlib:imaqmontage:tooManyInputs'));
    end
end

% Parse input.
isCellData = false;
if ~isnumeric(data) || isempty(data),
    
    if isa(data, 'videoinput')
        % OBJ provided, get a frame, and determine 
        % the dimensions of the data set.
        try
            data = getsnapshot(data);
            [width, height, bands, nFrames] = size(data);
        catch err
            throw(err);
        end
        
    elseif iscell(data),
        % Determine the number of elements.
        nFrames = length(data);
        
        % Verify each element is valid.
        for c = 1:nFrames,
            if ~isnumeric(data{c}),
                error(message('imaqlib:imaqmontage:inputInvalid'));
            elseif c==1,
                % Determine the dimensions of the data set.
                [width, height, bands] = size(data{1});
                numDims = ndims(data{1});
                isCellData = true;                
            else
                [w, h, b] = size(data{c});
                if ~all([w h b]==[width height bands]) || (numDims~=ndims(data{c}))
                    error(message('imaqlib:imaqmontage:sizeMismatch'));
                end
            end
        end
        
    else
        % Invalid input.
        error(message('imaqlib:imaqmontage:inputInvalid'));
    end
    
else
    % Determine the dimensions of the data set, 
    % and a native data value.
    [width, height, bands, nFrames] = size(data);
end

% Determine a native data type value for casting purposes.
% Safer to do inside a TRY-CATCH in case we encounter [], 
% i.e. caller provides:
%   - imaqmontage(cell(1))
%   - GETSNAPSHOT returns []
%   - etc.
try
    if isCellData,
        nativeVal = data{1}(1, 1);
    else
        nativeVal = data(1, 1);
    end    
catch %#ok<CTCH>
    error(message('imaqlib:imaqmontage:inputInvalid'));
end

% All we want is the data type, not the pixel value.
% Make sure not to use the pixel color value.
nativeVal(1, 1) = 0;

% Displaying image frames can lead to a 
% different set of errors.
try
    if nargout==1
        hIm = localDisplay(width, height, bands, nFrames, nativeVal, data, isCellData, clim, parent);
    else
        localDisplay(width, height, bands, nFrames, nativeVal, data, isCellData, clim, parent);        
    end
catch err
    throw(err);
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function im = localDisplay(width, height, bands, nFrames, nativeVal, data, isCellData, clim, ax)

% Determine the number of axis rows and columns.
axCols = sqrt(nFrames);
if (axCols<1)
    % In case we have a slim image.
    axCols = 1;
end
axRows = nFrames/axCols;
if (ceil(axCols)-axCols) < (ceil(axRows)-axRows),
    axCols = ceil(axCols);
    axRows = ceil(nFrames/axCols);
else
    axRows = ceil(axRows); 
    axCols = ceil(nFrames/axRows);
end

% Size the storage to hold all frames.
storage = repmat(nativeVal, [axRows*width, axCols*height, bands, 1]);

% Fill the storage up with data pixels.
rows = 1:width; 
cols = 1:height;
if isCellData,
    % Cell array of matrices.
    for i=0:axRows-1,
        for j=0:axCols-1,
            k = j+i*axCols+1;
            if k<=nFrames,
                storage(rows+i*width, cols+j*height, :) = data{k}(:,:,:);
            else
                break;
            end
        end
    end
else
    % Regular old matrices.
    for i=0:axRows-1,
        for j=0:axCols-1,
            k = j+i*axCols+1;
            if k<=nFrames,
                storage(rows+i*width, cols+j*height, :) = data(:,:,:,k);
            else
                break;
            end
        end
    end
end

% Display the tiled frames nicely and 
% pop the window forward.
if ~isempty(ax) && ~isempty(clim)
    im = imagesc(storage, 'Parent', ax, clim);
elseif isempty(ax) && isempty(clim)
    im = imagesc(storage);
elseif isempty(clim)
    im = imagesc(storage, 'Parent', ax);
else
    im = imagesc(storage, clim);
end

ax = get(im, 'Parent');
fig = get(ax, 'Parent');
set(ax, 'XTick', [], 'YTick', [])
figure(fig)

% If working with single band images, update the colormap.
if bands==1,
    colormap(gray);
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function clim = localCheckClim( x )

if ~isnumeric(x) || (numel(x) ~= 2),
    error(message('imaqlib:imaqmontage:invalidClim'));
else
    % CLIM needs to be a row vector.
    clim = x(:)';
end
