classdef animatorApp < handle
%ANIMATORAPP Data animation viewer
% This GUI tool allows you to animate your data with controls for
% playback speed and looping.
%
% ANIMATORAPP Properties:
% hAxes - handle to the animation axes
% hLine - array of handles to the graphics objects created
%
% APP = ANIMATORAPP opens an animation viewer. There needs to be some
% valid data in the workspace. See next section for information on the
% valid data types. APP is an object representing the Application. It has
% two public properties, which are handles to the graphics. These can be
% used to do programmatic customizations to the plots.
%
% APP = ANIMATORAPP(X, Y) animates the data. The data has to be in one of the
% following formats. The general form is a 3-D array. 1st dimension is
% the number of elements in a signal (m). 2nd dimension is the number of
% lines (n). 3rd dimension is the number of frames (p).
% 1. X can be either an m by 1 (2D) array or an m by n by p (3D) array.
% If 2D, all frames will use the same X vector
%
% 2. X - m by 1
% Y - m by n by p
% (This is for animating n lines, with a single X vector for ALL of
% p frames)
%
% 3. X - m by 1 by p OR m by n by p
% Y - m by n by p
% (This is for animating n lines, with a fixed X vector for EACH
% of the p frames OR X-Y pairs per frame)
%
% 4. X - [] (empty)
% Y - m by n by p
% (Y will be animated against it's index 1:m)
%
% APP = ANIMATORAPP(X, Y, PARAM1, VALUE1, ...) accepts additional arguments:
% 'axis' : {'auto'}, 'equal
% 'xlim' : 'auto', [XMIN, XMAX]. Default uses the full range
% 'ylim' : 'auto', [YMIN, YMAX]. Default uses the full range
% 'title' : <title text>
% 'xlabel' : <xlabel text>
% 'ylabel' : <ylabel text>
% 'smooth' : {'off'}, 'on'. Anti-aliasing
% 'frame' : {1}. Starting frame number
% 'speed' : {9}. Integer between -10 and 10. 10 is fastest, -10 is
% fastest in the reverse obj.direction.
% 'framerate': {1}, 2, 3, 5, 10. Animate every # frames.
%
% GUI Features:
% The controls allows you to speed up and slow down (or reverse) the
% playback. You can pause at any time. You can also drag the time line
% bar to go to arbitrary frames. Also, use the arrow keys to move between
% frames (left or right) or change the speed (up or down). Spacebar
% pauses/starts the animation. In addition to the animation speed, the
% animation frame interval rate can be set from the menu.
%
% The graphics properties can be customized via a context menu on the
% objects. Right-click on the plotted lines to bring up the context menu.
%
% The animation can be exported to an AVI (R2010b or newer) or an
% Animated GIF. The Animated GIF option requires the Image Processing
% Toolbox (if R2008b or older), for converting RGB to Indexed data.
%
% Example 1:
% x = (0:.01:10)';
% y = nan(length(x), 2, 400);
% for idx = 1:400;
% y(:, 1, idx) = sin(2*x) + cos(0.25*sqrt(idx)*x);
% y(:, 2, idx) = -cos(0.7*x) + sin(0.4*sqrt(idx)*x);
% end
% animatorApp(x, y);
%
% Example 2: Programmatic customization
% load animatorSampleData;
%
% % Vibrating string
% app = animatorApp(X1,Y1,'ylim',[-.7 .7],'title','Vibrating String','smooth','on');
% set(app.hLine, 'marker', 'o');
%
% % Two double-pendulum
% app = animatorApp(X2,Y2,'axis','equal','title','Double Double-Pendulum');
% set(app.hLine, 'LineWidth', 3, 'Marker', '.', 'MarkerSize', 20);
% set(app.hAxes, 'XGrid', 'on', 'YGrid', 'on');
%
% See also animator.
% Versions:
% v1.0 - Original version (Aug, 2007)
% v1.1 - Added option for specifying initial frame and animation speed
% v2.0 - Added exporting option (AVI or Animated GIF) (Nov, 2007)
% v2.1 - Added settings dialog for AVI and Animated GIF (Nov, 2007)
% v2.3 - Refactor functions (Nov, 2007)
% v2.4 - Changed graphics to Painters. Some graphics card has problems (Oct 2008)
% v2.5 - Changed back to OpenGL.
% v3.0 - Converted to object oriented code and added the ability to load
% different data sets (Aug 2012)
%
% Jiro Doke
% Copyright 2007-2012 The MathWorks, Inc.
properties (Dependent)
hAxes
hLine
end
properties (Hidden, Access = protected)
appVer
MainAxes
handles
x
y
sigType
numFrames
aspectRatio
xlimit
ylimit
titleText
xlabelText
ylabelText
isSmooth
frm
RRateID
frameInterval
direction
curRateID
RRates
isLoop = true
isPlaying = false
isSaving = false
ptrID = 3
tm
end
properties (Constant, Hidden, Access = protected)
ptrTypes = {'hand', 'lrdrag', 'arrow'}
cm = [.3, .3, .3; .9, .9, .9; .2, .6, .2; .8, 1, .8; .2, .2, .6; .8, .8, 1];
cc1a = [1 2 2 1] % patch color for PAUSED
cc1b = [3 4 4 3] % patch color for PLAYING
end
methods
function obj = animatorApp(x, y, varargin)
obj.appVer = '3.0';
if nargin > 0
obj.x = x;
obj.y = y;
% Error checking
errorcheck(obj);
end
% Check for additional input arguments
if nargin < 3
args = {};
else
if mod(nargin-2,2)
error('Additional arguments must be Param/Value pairs');
end
args = varargin;
end
processArguments(obj, args);
% Define other constants
obj.RRates = [.01, .02, .05, .075, .1, .2, .5, .75, 1, 2];
obj.RRates = [-obj.RRates,0,obj.RRates(end:-1:1)]; % pre-defined refresh rates
obj.direction = sign(obj.RRates(obj.RRateID)); % forward(+) or reverse(-)
obj.curRateID = obj.RRateID; % for keeping track of current rate
% Create main GUI
createGUI(obj);
% Create timer object for animation
obj.tm = timer(...
'ExecutionMode' , 'fixedrate', ...
'ObjectVisibility' , 'off', ...
'Tag' , 'animationTimer', ...
'StartFcn' , @obj.mystartFcn, ...
'StopFcn' , @obj.mystopFcn, ...
'TimerFcn' , @obj.timerUpdateFcn);
if obj.RRateID == 11
set(obj.tm, 'Period', 0.01 );
else
set(obj.tm, 'Period', obj.RRates(obj.RRateID));
end
% Start animation timer if data exists
if ~isempty(obj.x) && ~isempty(obj.y)
start(obj.tm);
end
end %animator
function out = get.hAxes(obj)
out = obj.MainAxes;
end
function out = get.hLine(obj)
out = obj.handles.Lines;
end
end
methods (Hidden, Access = protected)
%--------------------------------------------------------------------------
% Function for creating the GUI
function createGUI(obj)
% Create figure
hFig = figure(...
'Name' , 'ANIMATOR', ...
'Numbertitle' , 'off', ...
'Units' , 'pixels', ...
'DockControls' , 'on', ...
'Toolbar' , 'none', ...
'Menubar' , 'none', ...
'Color' , [.8 .8 .8], ...
'Colormap' , obj.cm, ...
'DoubleBuffer' , 'on', ...
'Renderer' , 'OpenGL', ...
'ResizeFcn' , @obj.figResizeFcn, ...
'KeyPressFcn' , @obj.keypressFcn, ...
'DeleteFcn' , @obj.figDeleteFcn, ...
'Visible' , 'off', ...
'HandleVisibility' , 'callback', ...
'WindowButtonMotionFcn' , @obj.motionFcn, ...
'Tag' , 'MainFigure', ...
'defaultUIControlBackgroundColor' , [.8 .8 .8], ...
'defaultAxesUnits' , 'pixels', ...
'defaultPatchEdgeColor' , 'none', ...
'defaultUIPanelUnits' , 'pixels');
% Create menu
hMenu0 = uimenu('Parent', hFig, 'Label', 'Data');
uimenu('Parent', hMenu0, 'Label', 'Load...', 'Callback', @obj.loadDataFcn);
allFrameRates = [1 2 3 5 10];
frameChecked = {'off'; 'off'; 'off'; 'off'; 'off'};
frameChecked{allFrameRates == obj.frameInterval} = 'on';
hMenu1 = uimenu('Parent',hFig, 'Label','Extra' );
hMenu2 = uimenu('Parent',hMenu1,'Label','Animate every...');
hFrameMenu2(1) = uimenu('Parent',hMenu2,'Label','1 frame' );
hFrameMenu2(2) = uimenu('Parent',hMenu2,'Label','2 frames' );
hFrameMenu2(3) = uimenu('Parent',hMenu2,'Label','3 frames' );
hFrameMenu2(4) = uimenu('Parent',hMenu2,'Label','5 frames' );
hFrameMenu2(5) = uimenu('Parent',hMenu2,'Label','10 frames' );
hMenu3 = uimenu('Parent',hMenu1,'Label','Export to...' );
hFrameMenu3(1) = uimenu('Parent',hMenu3,'Label','AVI' );
hFrameMenu3(2) = uimenu('Parent',hMenu3,'Label','Animated GIF' );
set(hFrameMenu2, 'Callback' , @obj.changeStepRate );
set(hFrameMenu2, 'Tag' , 'FrameRateMenu' );
set(hFrameMenu2, {'Checked'}, frameChecked );
set(hFrameMenu3, 'Callback' , @obj.exportAnimation);
uimenu('Parent', hMenu1, 'Label', 'Edit title/labels...', 'Callback', @obj.labelCallback );
uimenu('Parent', hMenu1, 'Label', 'Smooth lines' , 'Callback', @obj.smoothLineCallback, 'Tag', 'SmoothLineMenu');
uimenu('Parent', hMenu1, 'Label', 'Help...' , 'Callback', @obj.helpFcn , 'Separator', 'on');
uimenu('Parent', hMenu1, 'Label', 'About...' , 'Callback', @obj.aboutFcn );
% UI Context Menu for plot customization
hContext = uicontextmenu('Parent', hFig, 'Tag', 'ContextMenu');
hCLineStyle = uimenu('Parent', hContext, 'Label', 'Line Style');
hContextMenuItems(1) = uimenu('Parent', hCLineStyle, 'Label', '-');
hContextMenuItems(2) = uimenu('Parent', hCLineStyle, 'Label', '--');
hContextMenuItems(3) = uimenu('Parent', hCLineStyle, 'Label', ':');
hContextMenuItems(4) = uimenu('Parent', hCLineStyle, 'Label', '-.');
hCLineWidthMenu = uimenu('Parent', hContext, 'Label', 'Line Width');
hContextMenuItems(5) = uimenu('Parent', hCLineWidthMenu, 'Label', '0.5');
hContextMenuItems(6) = uimenu('Parent', hCLineWidthMenu, 'Label', '1');
hContextMenuItems(7) = uimenu('Parent', hCLineWidthMenu, 'Label', '2');
hContextMenuItems(8) = uimenu('Parent', hCLineWidthMenu, 'Label', '3');
hContextMenuItems(9) = uimenu('Parent', hCLineWidthMenu, 'Label', '4');
hContextMenuItems(10) = uimenu('Parent', hCLineWidthMenu, 'Label', '6');
hCLineColorMenu = uimenu('Parent', hContext, 'Label', 'Line Color');
hContextMenuItems(11) = uimenu('Parent', hCLineColorMenu, 'Label', '0, 0, 1', 'ForegroundColor', [0, 0, 1]);
hContextMenuItems(12) = uimenu('Parent', hCLineColorMenu, 'Label', '0, 0.5, 0', 'ForegroundColor', [0, 0.5, 0]);
hContextMenuItems(13) = uimenu('Parent', hCLineColorMenu, 'Label', '1, 0, 0', 'ForegroundColor', [1, 0, 0]);
hContextMenuItems(14) = uimenu('Parent', hCLineColorMenu, 'Label', '0, .75, .75', 'ForegroundColor', [0, .75, .75]);
hContextMenuItems(15) = uimenu('Parent', hCLineColorMenu, 'Label', '.75, 0, .75', 'ForegroundColor', [.75, 0, .75]);
hContextMenuItems(16) = uimenu('Parent', hCLineColorMenu, 'Label', '.75, .75, 0', 'ForegroundColor', [.75, .75, 0]);
hContextMenuItems(17) = uimenu('Parent', hCLineColorMenu, 'Label', '.25, .25, .25', 'ForegroundColor', [.25, .25, .25]);
hCMarkerMenu = uimenu('Parent', hContext, 'Label', 'Marker');
hContextMenuItems(18) = uimenu('Parent', hCMarkerMenu, 'Label', '.');
hContextMenuItems(19) = uimenu('Parent', hCMarkerMenu, 'Label', 'o');
hContextMenuItems(20) = uimenu('Parent', hCMarkerMenu, 'Label', 'x');
hContextMenuItems(21) = uimenu('Parent', hCMarkerMenu, 'Label', '+');
hContextMenuItems(22) = uimenu('Parent', hCMarkerMenu, 'Label', '*');
hContextMenuItems(23) = uimenu('Parent', hCMarkerMenu, 'Label', 's');
hContextMenuItems(24) = uimenu('Parent', hCMarkerMenu, 'Label', 'd');
hContextMenuItems(25) = uimenu('Parent', hCMarkerMenu, 'Label', 'v');
hContextMenuItems(26) = uimenu('Parent', hCMarkerMenu, 'Label', '^');
hContextMenuItems(27) = uimenu('Parent', hCMarkerMenu, 'Label', '<');
hContextMenuItems(28) = uimenu('Parent', hCMarkerMenu, 'Label', '>');
hContextMenuItems(29) = uimenu('Parent', hCMarkerMenu, 'Label', 'p');
hContextMenuItems(30) = uimenu('Parent', hCMarkerMenu, 'Label', 'h');
hCMarkerSizeMenu = uimenu('Parent', hContext, 'Label', 'Marker Size');
hContextMenuItems(31) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '5');
hContextMenuItems(32) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '8');
hContextMenuItems(33) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '10');
hContextMenuItems(34) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '12');
hContextMenuItems(35) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '15');
hContextMenuItems(36) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '20');
hContextMenuItems(37) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '25');
hContextMenuItems(38) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '30');
set(hContextMenuItems, 'Callback', @obj.contextmenuFcn);
% Create main axes
obj.MainAxes = axes('Parent', hFig);
% Control panel
hPanel = uipanel(...
'Parent' , hFig , ...
'BackgroundColor' , [.8 .8 .8] , ...
'Tag' , 'ControlPanelAxes', ...
'BorderType' , 'etchedin' );
% Time line bar
hAx = axes(...
'Parent' , hPanel , ...
'Visible' , 'off' , ...
'Tag' , 'TimelineAxes', ...
'XLim' , [0 1] , ...
'YLim' , [-1 1] );
patch([0, 0, 1, 1], [-.5, .5, .5, -.5], [.9 .9 .9], ...
'Tag' , 'timeLine', ...
'ButtonDownFcn' , @obj.initiateDragFcn, ...
'Parent' , hAx);
patch([0, 0, 0, 0], [-.5, .5, .5, -.5], obj.cc1b, ...
'HitTest' , 'off', ...
'CDataMapping' , 'direct', ...
'FaceColor' , 'interp', ...
'Tag' , 'TimelinePatch', ...
'Parent' , hAx);
% Message text boxes
uicontrol(...
'Parent' , hPanel, ...
'style' , 'text', ...
'Tag' , 'FrameText', ...
'HorizontalAlignment' , 'left', ...
'FontWeight' , 'Bold');
uicontrol(...
'Parent' , hPanel, ...
'style' , 'text', ...
'Tag' , 'StatusText', ...
'HorizontalAlignment' , 'right');
% Playback controls
hAx2 = axes(...
'Parent' , hPanel, ...
'Visible' , 'off', ...
'XLim' , [ 0 1], ...
'YLim' , [-1 1], ...
'Tag' , 'PlaybackAxes');
patch([0, 0, 1, 1], [-.5, .5, .5, -.5], [5 6 6 5], ...
'CDataMapping' , 'direct', ...
'FaceColor' , 'interp', ...
'Parent' , hAx2);
line(...
[ 0 0 NaN .25 .25 NaN .5 .5 NaN .75 .75 NaN .99 .99], ...
[-.3 .3 NaN -.3 .3 NaN -.75 .75 NaN -.3 .3 NaN -.3 .3], ...
'Parent', hAx2);
line([(obj.RRateID-1)/20, (obj.RRateID-1)/20], [-.9, .9], ...
'LineWidth' , 4, ...
'Color' , [.3 .3 .3], ...
'Tag' , 'speedBar', ...
'ButtonDownFcn' , @obj.initiateDragFcn,...
'Parent' , hAx2);
% Load icons for controls
icons = load(fullfile(fileparts(mfilename), 'animatorIcons.mat'));
% Create playback control buttons from the icons
hBtn(1) = axes(...
'Parent' , hPanel , ...
'Tag' , 'slowerAxes');
hBtn(2) = axes(...
'Parent' , hPanel , ...
'Tag' , 'fasterAxes');
hBtn(3) = axes(...
'Parent' , hPanel , ...
'Tag' , 'pauseAxes' );
hBtn(4) = axes(...
'Parent' , hPanel , ...
'Tag' , 'repeatAxes');
image(icons.rewindCData, ...
'Parent' , hBtn(1), ...
'AlphaData' , icons.rewindAlpha, ...
'Tag' , 'slowerImage', ...
'ButtonDownFcn' , @obj.speedBtnFcn);
image(icons.fastforwardCData, ...
'Parent' , hBtn(2), ...
'AlphaData' , icons.fastforwardAlpha, ...
'Tag' , 'fasterImage', ...
'ButtonDownFcn' , @obj.speedBtnFcn);
image(icons.pauseCData, ...
'Parent' , hBtn(3), ...
'AlphaData' , icons.pauseAlpha, ...
'Tag' , 'pauseImage', ...
'ButtonDownFcn' , @obj.speedBtnFcn);
image(icons.repeatCData, ...
'Parent' , hBtn(4), ...
'AlphaData' , icons.repeatAlpha, ...
'Tag' , 'repeatImage', ...
'ButtonDownFcn' , @obj.speedBtnFcn);
axis(hBtn, 'off', 'image');
set(hBtn, {'Tag'}, {'slowerAxes'; 'fasterAxes'; 'pauseAxes'; 'repeatAxes'});
obj.handles = guihandles(hFig);
obj.handles.icons = icons;
% Load and prep data
if isempty(obj.x) && isempty(obj.y)
loadDataFcn(obj);
% If still no data
if isempty(obj.x) && isempty(obj.y)
obj.RRateID = 11;
end
else
hL = setupData(obj);
obj.handles.Lines = hL;
end
% Process additional settings (user-specified)
if strcmp(obj.aspectRatio, 'equal') % Aspect ratio
set(obj.MainAxes, 'DataAspectRatio', [1 1 1]);
elseif strcmp(obj.aspectRatio, 'auto')
set(obj.MainAxes, 'DataAspectRatioMode', 'auto');
end
if ~isempty(obj.xlimit) % X-limits
if strcmpi(obj.xlimit, 'auto')
set(obj.MainAxes, 'XLimMode', 'auto');
else
set(obj.MainAxes, 'XLim', obj.xlimit);
end
end
if ~isempty(obj.ylimit) % Y-limits
if strcmpi(obj.ylimit, 'auto')
set(obj.MainAxes, 'YLimMode', 'auto');
else
set(obj.MainAxes, 'YLim', obj.ylimit);
end
end
if ~isempty(obj.titleText) % Axes title
title(obj.MainAxes, obj.titleText);
end
if ~isempty(obj.xlabelText) % Axes x-label
xlabel(obj.MainAxes, obj.xlabelText);
end
if ~isempty(obj.ylabelText) % Axes y-label
ylabel(obj.MainAxes, obj.ylabelText);
end
set(obj.MainAxes, 'XColor', [.5 .5 .5], 'YColor', [.5 .5 .5]);
if strcmp(obj.isSmooth, 'on')
try
set(obj.handles.SmoothLineMenu, 'Checked', 'on');
set(hL, 'LineSmoothing', obj.isSmooth);
catch %#ok<CTCH>
set(obj.handles.SmoothLineMenu, 'Checked', 'off');
obj.isSmooth = 'off';
disp('Anti-aliasing not supported.');
end
end
% Make axes invisible from outside
set(findobj(hFig, 'type', 'axes'), 'HandleVisibility', 'callback');
movegui(hFig, 'center');
set(hFig, 'Visible' , 'on');
end %createGUI
%--------------------------------------------------------------------------
% Called when the "Smooth Line" menu item is selected
function smoothLineCallback(obj, varargin)
if strcmp(obj.isSmooth, 'on')
smoothOnOff(obj, 'off');
else
smoothOnOff(obj, 'on');
end
end %smoothLineCallback
%--------------------------------------------------------------------------
% Called to turn smoothing on or off
function smoothOnOff(obj, onoff)
if strcmp(onoff, 'on')
try
set(obj.hLine, 'LineSmoothing', 'on');
set(obj.handles.SmoothLineMenu, 'Checked', 'on');
obj.isSmooth = 'on';
catch %#ok<CTCH>
set(obj.handles.SmoothLineMenu, 'Checked', 'off');
obj.isSmooth = 'off';
end
else
set(obj.hLine, 'LineSmoothing', 'off');
set(obj.handles.SmoothLineMenu, 'Checked', 'off');
obj.isSmooth = 'off';
end
end
%--------------------------------------------------------------------------
% Called when the context menu on a graphics object is selected
function contextmenuFcn(obj, hObj, varargin) %#ok<INUSL>
val = get(hObj, 'Label');
switch get(get(hObj, 'Parent'), 'Label')
case 'Line Style'
set(gco, 'LineStyle', val);
case 'Line Width'
set(gco, 'LineWidth', str2double(val));
case 'Line Color'
set(gco, 'Color', str2num(val)); %#ok<ST2NM>
case 'Marker'
set(gco, 'Marker', val);
case 'Marker Size'
set(gco, 'MarkerSize', str2double(val));
end
end %contextmenuFcn
%--------------------------------------------------------------------------
% Called when the "Load Data" menu item is selected
function loadDataFcn(obj, varargin)
% Pause if current animation is playing
if obj.isPlaying
obj.curRateID = obj.RRateID;
pauseAnimation(obj);
end
% Save original data in case of error
origX = obj.x;
origY = obj.y;
% Bring up dialog to select variables from base workspace
[obj.x, obj.y] = loadVars();
% If nothing is selected, rever to original
if isempty(obj.x) && isempty(obj.y)
obj.x = origX;
obj.y = origY;
return;
end
try
% Error checking
errorcheck(obj);
catch ME
errordlg(ME.message, 'Error', 'modal');
obj.x = origX;
obj.y = origY;
return;
end
hL = setupData(obj);
obj.handles.Lines = hL;
updateLines(obj);
smoothOnOff(obj, obj.isSmooth)
end %loadDataFcn
%--------------------------------------------------------------------------
% Called when a new data is loaded to create initial plot
function hL = setupData(obj)
if isfield(obj.handles, 'Lines') && ~isempty(obj.handles.Lines) && all(ishandle(obj.handles.Lines))
delete(obj.handles.Lines);
end
% Go to frame 1
obj.frm = 1;
% Create plot
switch obj.sigType
case 1 % x: m by 1, y: m by 1 by p
hL = plot(obj.MainAxes, obj.x, obj.y(:, :, obj.frm), 'Tag', 'Lines');
xlim(obj.MainAxes, [min(obj.x), max(obj.x)]);
ylim(obj.MainAxes, [min(obj.y(:)), max(obj.y(:))]);
case 2 % x: m by 1, y: m by n by p
hL = plot(obj.MainAxes, obj.x, cell2mat(obj.y(:,:,obj.frm)), 'Tag', 'Lines');
xlim(obj.MainAxes, [min(obj.x(:)), max(obj.x(:))]);
ylim(obj.MainAxes, [min(min(cell2mat(squeeze(obj.y)))), max(max(cell2mat(squeeze(obj.y))))]);
case 3 % x: m by n by p, y: m by n by p
hL = plot(obj.MainAxes, cell2mat(obj.x(:,:,obj.frm)), cell2mat(obj.y(:,:,obj.frm)), 'Tag', 'Lines');
xlim(obj.MainAxes, [min(min(cell2mat(squeeze(obj.x)))), max(max(cell2mat(squeeze(obj.x))))]);
ylim(obj.MainAxes, [min(min(cell2mat(squeeze(obj.y)))), max(max(cell2mat(squeeze(obj.y))))]);
end
% Attach context menu to the plots
set(hL, 'UIContextMenu', obj.handles.ContextMenu);
end %setupData
%--------------------------------------------------------------------------
% Called when "Edit title/labels..." menu is selected
function labelCallback(obj, varargin)
answer = inputdlg({'Title:', 'X-Label:', 'Y-Label:'}, ...
'Enter labels', 1, {get(get(obj.MainAxes, 'Title'), 'String'), ...
get(get(obj.MainAxes, 'XLabel'), 'String'), ...
get(get(obj.MainAxes, 'YLabel'), 'String')});
if ~isempty(answer)
title(obj.MainAxes , answer{1});
xlabel(obj.MainAxes, answer{2});
ylabel(obj.MainAxes, answer{3});
end
end %labelCallback
%--------------------------------------------------------------------------
% Called when "Help..." menu is selected
function helpFcn(obj, varargin) %#ok<INUSD>
helpdlg({'Use the lower right controls to change the refresh rate.', ...
'Individual frames can be viewed interactively by', ...
'clicking and dragging on the time line bar.', '', ...
'The following key controls are available:', ...
' Right/Left arrows: step through frames one by one', ...
' Up/Down arrows: increase/decrease refresh rate', ...
' Spacebar: toggle between run/pause'}, 'Help for Animator');
end %helpFcn
%--------------------------------------------------------------------------
% Called when "About..." menu is selected
function aboutFcn(obj, varargin)
helpdlg({'Animator is a tool for visualizing animation data stored in array formats.','','Author:', ...
' Jiro Doke (jiro.doke@mathworks.com)', ...
' Copyright 2007-2010 The MathWorks, Inc.'}, ...
sprintf('About Animator v%s', obj.appVer));
end %aboutFcn
%--------------------------------------------------------------------------
% Called when changing the number of animation frame interval
function changeStepRate(obj, varargin)
obj.frameInterval = str2double(strtok(get(varargin{1}, 'Label')));
set(obj.handles.FrameRateMenu , 'Checked', 'off');
set(varargin{1} , 'Checked', 'on' );
end %changeStepRate
%--------------------------------------------------------------------------
% Called when exporting animation to AVI or Animated GIF
function exportAnimation(obj, varargin)
% If the animation is playing, pause it.
if obj.isPlaying
obj.curRateID = obj.RRateID;
pauseAnimation(obj);
end
switch get(varargin{1}, 'Label')
case 'AVI'
if verLessThan('matlab', '7.11')
errordlg('Creating AVI files only supported in R2010b or newer.', 'Error', 'modal');
return;
end
isAVI = true;
case 'Animated GIF'
isAVI = false;
end
if isAVI
frameRate = abs(1/(obj.RRates(obj.curRateID)));
settings = aviSettings(frameRate, obj.frameInterval);
if isempty(settings) % Cancelled
return
end
vid = VideoWriter(settings.filename, settings.Profile);
propNames = fieldnames(settings.WriteableProperties);
for iProp = 1:length(propNames)
vid.(propNames{iProp}) = settings.WriteableProperties.(propNames{iProp});
end
open(vid);
% Change the title of the figure window
set(obj.handles.MainFigure, 'Name', 'ANIMATOR - Creating AVI file (press ''q'' to cancel)');
else
frameDelay = abs(obj.RRates(obj.curRateID));
settings = gifSettings(frameDelay, obj.frameInterval);
if isempty(settings) % Cancelled
return
end
% Change the title of the figure window
set(obj.handles.MainFigure, 'Name', 'ANIMATOR - Creating Animated GIF file (press ''q'' to cancel)');
end
% Turn on Saving mode
obj.isSaving = true;
% Get the current figure position - for GETFRAME (see below)
set(obj.handles.MainFigure, 'Units', 'pixels');
figPos = get(obj.handles.MainFigure, 'Position');
% Start from the first frame
obj.frm = 1;
updateLines(obj);
f = getframe(obj.handles.MainFigure, [5, 55, figPos(3)-10, figPos(4)-55]);
if isAVI
writeVideo(vid, f);
else
try
[indexCData, cMap] = rgb2ind(f.cdata, double(intmax(class(f.cdata)))+1);
catch %#ok<CTCH>
errordlg('Requires Image Processing Toolbox...');
return;
end
try
imwrite(indexCData, cMap, settings.filename, ...
'WriteMode' , 'overwrite' , ...
'LoopCount' , settings.Loop , ...
'DisposalMethod', settings.DisposalMethod , ...
'DelayTime' , settings.FrameDelay );
catch %#ok<CTCH>
errordlg('Writing to GIF files is not supported in this release of MATLAB');
return;
end
end
% Loop through all frames until finished
while true
obj.frm = obj.frm + settings.FrameInterval;
if obj.frm > obj.numFrames % processed all frames
obj.frm = obj.numFrames;
break;
end
if ~obj.isSaving % 'q' was pressed by the user
break;
end
updateLines(obj);
f = getframe(obj.handles.MainFigure, [5, 55, figPos(3)-10, figPos(4)-55]);
if isAVI
writeVideo(vid, f);
else
[indexCData, cMap] = rgb2ind(f.cdata, double(intmax(class(f.cdata)))+1);
imwrite(indexCData, cMap, settings.filename, ...
'WriteMode' , 'append' , ...
'DisposalMethod', settings.DisposalMethod , ...
'DelayTime' , settings.FrameDelay );
end
end
if isAVI
% Save AVI file
close(vid);
end
if ~obj.isSaving
msgbox('Cancelled');
delete(fname);
else
msgbox('Done');
end
obj.isSaving = false;
set(obj.handles.MainFigure, 'Name', 'ANIMATOR - Paused');
end %exportAnimation
%--------------------------------------------------------------------------
% Called when a key is pressed
function keypressFcn(obj, varargin)
k = get(obj.handles.MainFigure, 'CurrentKey');
switch k
case 'rightarrow' % go to the next frame
pauseAnimation(obj);
obj.frm = obj.frm + 1;
if obj.isLoop && obj.frm > obj.numFrames
obj.frm = 1;
else
obj.frm = min([obj.numFrames, obj.frm]);
end
updateLines(obj);
case 'leftarrow' % go to the previous frame
pauseAnimation(obj);
obj.frm = obj.frm - 1;
if obj.isLoop && obj.frm < 1
obj.frm = obj.numFrames;
else
obj.frm = max([1, obj.frm]);
end
updateLines(obj);
case 'uparrow' % speed up the refresh rate
obj.RRateID = min([length(obj.RRates), obj.RRateID + 1]);
updateRefreshRate(obj);
case 'downarrow' % slow down the refresh rate
obj.RRateID = max([1, obj.RRateID - 1]);
updateRefreshRate(obj);
case 'space' % toggle play/pause
if obj.isPlaying
obj.curRateID = obj.RRateID;
pauseAnimation(obj);
else
obj.RRateID = obj.curRateID;
start(obj.tm);
end
case 'q' % cancel exporting of animation
obj.isSaving = false;
end
end %keypressFcn
%--------------------------------------------------------------------------
% Called when pause button is pressed
function pauseAnimation(obj)
obj.RRateID = 11; % this corresponds to zero refresh rate
set(obj.handles.speedBar, 'XData', [0.5, 0.5]);
stop(obj.tm);
end %pauseAnimation
%--------------------------------------------------------------------------
% Called whenever the figure is resized to reposition the components
% "nicely"
function figResizeFcn(obj, varargin)
set(obj.handles.MainFigure, 'Units', 'pixels');
figPos = get(obj.handles.MainFigure, 'Position');
panelPos = [2, 2, figPos(3)-4, 50];
set(obj.MainAxes , 'Position', [65, 100, figPos(3)-90, figPos(4)-130]);
set(obj.handles.ControlPanelAxes , 'Position', panelPos);
set(obj.handles.TimelineAxes , 'Position', [5, panelPos(4)-25, panelPos(3)-130, 20]);
set(obj.handles.PlaybackAxes , 'Position', [panelPos(3)-80, 25, 70, panelPos(4)-30]);
set(obj.handles.FrameText , 'Position', [5, 2, (panelPos(3)-130)/2, panelPos(4)-35]);
set(obj.handles.StatusText , 'Position', [5+(panelPos(3)-130)/2, 2, (panelPos(3)-130)/2, panelPos(4)-35]);
set(obj.handles.slowerAxes , 'Position', [panelPos(3)-80 (panelPos(4)-36)/2 16 16]);
set(obj.handles.fasterAxes , 'Position', [panelPos(3)-24 (panelPos(4)-36)/2 16 16]);
set(obj.handles.pauseAxes , 'Position', [panelPos(3)-52 (panelPos(4)-36)/2 16 16]);
set(obj.handles.repeatAxes , 'Position', [panelPos(3)-120 panelPos(4)-23 16 16]);
end %figResizeFcn
%--------------------------------------------------------------------------
% Called when the time line bar or the speed bar is clicked
function initiateDragFcn(obj, varargin)
switch get(varargin{1}, 'Tag')
case 'speedBar' % speed control is clicked
stop(obj.tm);
set(obj.handles.MainFigure , 'WindowButtonMotionFcn', @obj.speedFcn);
setptr(obj.handles.MainFigure, 'closedhand');
case 'timeLine' % time line bar is clicked
pauseAnimation(obj);
set(obj.handles.MainFigure, 'WindowButtonMotionFcn', @obj.timelineFcn);
timelineFcn(obj);
end
set(obj.handles.MainFigure, 'WindowButtonUpFcn', {@obj.stopDragFcn, get(varargin{1}, 'Tag')});
end %initiateDragFcn
%--------------------------------------------------------------------------
% Called when the speed control is being dragged
function speedFcn(obj, varargin)
pt = get(obj.handles.PlaybackAxes, 'CurrentPoint');
xVal = max([0, min([1, pt(1)])]);
set(obj.handles.speedBar, 'XData', [xVal, xVal]);
obj.RRateID = round(xVal*20+1);
end %speedFcn
%--------------------------------------------------------------------------
% Called when time line bar is being dragged
function timelineFcn(obj, varargin)
pt = get(obj.handles.TimelineAxes, 'CurrentPoint');
xVal = max([0, min([1, pt(1)])]);
obj.frm = max([1, round(obj.numFrames * xVal)]);
updateLines(obj);
end %timelineFcn
%--------------------------------------------------------------------------
% Called when the click-n-drag has completed
function stopDragFcn(obj, varargin)
set(obj.handles.MainFigure, 'WindowButtonMotionFcn', @obj.motionFcn);
set(obj.handles.MainFigure, 'WindowButtonUpFcn' , '' );
if strcmp(varargin{3}, 'speedBar')
updateRefreshRate(obj);
setptr(obj.handles.MainFigure, 'hand');
end
end %stopDragFcn
%--------------------------------------------------------------------------
% Sets the cursor pointer and status text based on pointer location
function motionFcn(obj, varargin)
loc = get([obj.handles.TimelineAxes, obj.handles.PlaybackAxes, obj.handles.slowerAxes, obj.handles.fasterAxes, obj.handles.pauseAxes, obj.handles.repeatAxes], 'CurrentPoint');
if isInRect(loc{2}(1,1:2), ... % mouse over speed control
[(obj.RRateID - 1)/20 - 0.05, (obj.RRateID - 1)/20 + 0.05, -0.9, 0.9])
if obj.ptrID ~= 1
obj.ptrID = 1;
setptr(obj.handles.MainFigure, obj.ptrTypes{obj.ptrID});
set(obj.handles.StatusText, 'String', 'Drag to change speed');
end
elseif isInRect(loc{1}(1,1:2), [0, 1, -0.8, 0.8]) % mouse over time line bar
if obj.ptrID ~=2
obj.ptrID = 2;
setptr(obj.handles.MainFigure, obj.ptrTypes{obj.ptrID});
set(obj.handles.StatusText, 'String', 'Drag to select frame');
end
elseif isInRect(loc{3}(1,1:2), [1, 16, 1, 16]) % mouse over "slower" icon
set(obj.handles.StatusText, 'String', 'Slower');
elseif isInRect(loc{4}(1,1:2), [1, 16, 1, 16]) % mouse over "faster" icon
set(obj.handles.StatusText, 'String', 'Faster');
elseif isInRect(loc{5}(1,1:2), [1, 16, 1, 16]) % mouse over pause/play
if obj.isPlaying
set(obj.handles.StatusText, 'String', 'Pause');
else
set(obj.handles.StatusText, 'String', 'Play');
end
elseif isInRect(loc{6}(1,1:2), [1, 16, 1, 16]) % mouse over loop
if obj.isLoop
set(obj.handles.StatusText, 'String', 'Turn off Loop');
else
set(obj.handles.StatusText, 'String', 'Turn on Loop');
end
else % everywhere else
if obj.ptrID ~= 3
obj.ptrID = 3;
setptr(obj.handles.MainFigure, obj.ptrTypes{obj.ptrID});
end
set(obj.handles.StatusText, 'String', '');
end
%--------------------------------------------------------------------------
% Helper function for "motionFcn" - Checks to see if pointer is in a rectangular region
function out = isInRect(xy, rect)
out = xy(1) > rect(1) && xy(1) < rect(2) && ...
xy(2) > rect(3) && xy(2) < rect(4);
end %isInRect
end %motionFcn
%--------------------------------------------------------------------------
% Updates the timer refresh rate
function updateRefreshRate(obj)
stop(obj.tm);
obj.direction = sign(obj.RRates(obj.RRateID));
if obj.RRateID ~= 11
obj.curRateID = obj.RRateID; % store current refresh rate
obj.tm.Period = abs(obj.RRates(obj.RRateID));
end
start(obj.tm);
end %updateRefreshRate
%--------------------------------------------------------------------------
% Called when the control buttons are pressed
function speedBtnFcn(obj, varargin)
switch get(varargin{1}, 'Tag')
case 'slowerImage'
obj.RRateID = max([1, obj.RRateID - 1]);
updateRefreshRate(obj);
case 'fasterImage'
obj.RRateID = min([length(obj.RRates), obj.RRateID + 1]);
updateRefreshRate(obj);
case 'pauseImage'
if obj.isPlaying
obj.curRateID = obj.RRateID;
pauseAnimation(obj);
set(obj.handles.StatusText, 'String', 'Play');
else
obj.RRateID = obj.curRateID;
start(obj.tm);
set(obj.handles.StatusText, 'String', 'Pause');
end
case 'repeatImage'
if obj.isLoop
set(obj.handles.repeatImage, 'cdata', obj.handles.icons.norepeatCData, 'alphadata', obj.handles.icons.norepeatAlpha);
set(obj.handles.StatusText, 'String', 'Turn on Loop');
else
set(obj.handles.repeatImage, 'cdata', obj.handles.icons.repeatCData, 'alphadata', obj.handles.icons.repeatAlpha);
set(obj.handles.StatusText, 'String', 'Turn off Loop');
end
obj.isLoop = ~obj.isLoop;
return;
end
end %speedBtnFcn
%--------------------------------------------------------------------------
% Called at every timer period
function timerUpdateFcn(obj, varargin)
%disp('timer update');
if isempty(obj.x) && isempty(obj.y)
return;
end
% get the new frame
obj.frm = max([0, min([obj.numFrames + 1, obj.frm + obj.frameInterval * obj.direction])]);
% if obj.isLoop, then loop back if at the end (or first) frame
if obj.isLoop
if obj.frm < 1
obj.frm = obj.numFrames;
elseif obj.frm > obj.numFrames
obj.frm = 1;
end
end
% stop if end or speed is zero
if obj.frm < 1 || obj.frm > obj.numFrames || obj.RRateID == 11
stop(obj.tm);
obj.frm = max([1, min([obj.numFrames, obj.frm])]);
end
updateLines(obj);
end %timerUpdateFcn
%--------------------------------------------------------------------------
% Start function for the timer
function mystartFcn(obj, varargin)
% Change time line bar to green
set(obj.handles.TimelinePatch, 'CData', obj.cc1b);
obj.direction = sign(obj.RRates(obj.RRateID));
if obj.direction > 0
dd = 'Forward';
else
dd = 'Reverse';
end
pp = abs(obj.RRates(obj.RRateID));
set(obj.handles.MainFigure, 'Name', sprintf('ANIMATOR - Refresh Rate: %g sec, %s', pp, dd));
set(obj.handles.speedBar, 'XData', [(obj.RRateID - 1)/20, (obj.RRateID - 1)/20]);
obj.isPlaying = true;
set(obj.handles.pauseImage, 'CData', obj.handles.icons.pauseCData, 'alphadata', obj.handles.icons.pauseAlpha);
%set(obj.handles.StatusText, 'String', 'Pause');
end %mystartFcn
%--------------------------------------------------------------------------
% Stop function for the timer
function mystopFcn(obj, varargin)
% Change time line bar to black
set(obj.handles.TimelinePatch, 'CData', obj.cc1a);
set(obj.handles.MainFigure, 'Name', 'ANIMATOR - Paused');
obj.isPlaying = false;
set(obj.handles.pauseImage, 'CData', obj.handles.icons.playCData, 'alphadata', obj.handles.icons.playAlpha);
%set(obj.handles.StatusText, 'String', 'Play');
end %mystopFcn
%--------------------------------------------------------------------------
% Updates lines based on the signal type
function updateLines(obj)
switch obj.sigType
case 1
set(obj.handles.Lines, 'YData' , obj.y(:, 1, obj.frm) );
case 2
set(obj.handles.Lines, {'YData'} , obj.y(:, :, obj.frm)' );
case 3
set(obj.handles.Lines, {'XData', 'YData'}, [obj.x(:, :, obj.frm)', obj.y(:, :, obj.frm)']);
end
% Update time line slider bar
set(obj.handles.TimelinePatch , 'XData', [0, 0, obj.frm/obj.numFrames, obj.frm/obj.numFrames]);
% Update frame number text
set(obj.handles.FrameText, 'String', sprintf('Frame: %d/%d', obj.frm, obj.numFrames));
end %updateLines
%--------------------------------------------------------------------------
% Called when figure is closed
function figDeleteFcn(obj, varargin)
% stop and delete timer object
stop(obj.tm);
delete(obj.tm);
end %figDeleteFcn
%--------------------------------------------------------------------------
% Defines constants/default values and process input arguments
function processArguments(obj, args)
obj.aspectRatio = 'auto'; % aspectratio is AUTO
obj.xlimit = []; % no specified xlim
obj.ylimit = []; % no specified ylim
obj.titleText = ''; % no specified title
obj.xlabelText = ''; % no specified xlabel
obj.ylabelText = ''; % no specified ylabel
obj.isSmooth = 'off'; % no anti-aliasing
obj.frm = 1; % start at frame 1
obj.RRateID = 20; % default refresh rate index
obj.frameInterval = 1; % animate every frame
% Check for additional input arguments
for iVars = 1:length(args)/2
val = args{iVars*2};
switch lower(args{iVars*2-1})
case 'axis'
if ischar(val) && ismember(val, {'auto', 'equal'})
obj.aspectRatio = val;
continue;
end
case 'xlim'
if isnumeric(val) && isequal(size(val), [1 2])
obj.xlimit = val;
continue;
end
case 'ylim'
if isnumeric(val) && isequal(size(val), [1 2])
obj.ylimit = val;
continue;
end
case 'title'
if ischar(val)
obj.titleText = val;
continue;
end
case 'xlabel'
if ischar(val)
obj.xlabelText = val;
continue;
end
case 'ylabel'
if ischar(val)
obj.ylabelText = val;
continue;
end
case 'smooth'
if ischar(val) && ismember(val, {'on', 'off'})
obj.isSmooth = val;
continue;
end
case 'frame'
if isnumeric(val) && isequal(size(val), [1 1]) && val >=1 && val <= obj.numFrames
obj.frm = round(val);
continue;
end
case 'speed'
if isnumeric(val) && isequal(size(val), [1 1]) && val >=-10 && val <= 10
obj.RRateID = round(val + 11);
continue;
end
case 'framerate'
if isnumeric(val) && isequal(size(val), [1 1]) && ismember(val, [1 2 3 5 10])
obj.frameInterval = val;
continue;
end
otherwise
error('Unknown argument: %s', args{iVars*2-1});
end
error('Invalid value for ''%s''', args{iVars*2-1});
end
end %processArguments
%--------------------------------------------------------------------------
% Error checking
function errorcheck(obj)
if ~isnumeric(obj.x) || ~isnumeric(obj.y)
error('X and Y must be numeric arrays');
end
if ndims(obj.y) ~= 3
error('Y must be a 3-D array of m (elements) - n (lines) - p (frames)');
end
if isempty(obj.x) % if empty, create an index vector
obj.x = (1:size(obj.y, 1))';
end
if ndims(obj.x) == 2 %#ok<ISMAT> % X is 2D
if size(obj.x, 2) == 1 % X is a column vector
if size(obj.x, 1) == size(obj.y, 1) % X and Y has the same # of rows
if size(obj.y, 2) == 1 % Y is a column vector
obj.sigType = 1;
else % size(y, 2) > 2 % Y has multiple columns
obj.sigType = 2;
obj.y = num2cell(obj.y, 1);
end
obj.numFrames = size(obj.y, 3);
else % size(x, 1) ~= size(y, 1)
error('X and Y must have the same number of rows');
end
else % size(x, 2) > 1
error('If X is 2D, it must be a column vector');
end
elseif ndims(obj.x) == 3 % X is 3D
if size(obj.x, 1) == size(obj.y, 1) % X and Y has the same # of rows
if size(obj.x, 2) == 1 || size(obj.x, 2) == size(obj.y, 2) % X has one column or same as Y
if size(obj.x, 3) == size(obj.y, 3) % X and Y has the same # of frames
obj.sigType = 3;
obj.numFrames = size(obj.y, 3);
obj.x = num2cell(obj.x, 1);
obj.y = num2cell(obj.y, 1);
else % size(x, 3) ~= size(y, 3)
error('X and Y must have the same number of frames (3rd dimension)');
end
else
error('X must have exactly one column OR the same number of column as Y');
end
else % size(x, 1) ~= size(y, 1)
error('X and Y must have the same number of rows');
end
else % ndims(x) > 3
error('X must be 2D or 3D');
end
end %errorcheck
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Helper Functions
%--------------------------------------------------------------------------
% Dialog for loading variables from the base workspace
function [x, y] = loadVars()
% [x, y] = loadVars()
%
% UI for selecting variables from the workspace.
vars = evalin('base', 'whos');
xID = arrayfun(@(x) (numel(x.size)==2 && x.size(2)==1) || numel(x.size)==3, vars);
yID = arrayfun(@(x) numel(x.size)==3, vars);
if nnz(yID) == 0
errordlg('No data available in the base workspace', 'Error', 'modal');
x = [];
y = [];
return;
end
xVars = [{'[ ]'}, {vars(xID).name}];
yVars = {vars(yID).name};
width = 50;
height = 10;
hFig = figure(...
'Name', 'Load Data', ...
'NumberTitle', 'off', ...
'HandleVisibility', 'off', ...
'Units', 'characters', ...
'Position', [0 0 width height], ...
'Toolbar', 'none', ...
'Menu', 'none', ...
'defaultUIControlUnits', 'characters', ...
'defaultUIControlFontUnits', 'points', ...
'defaultUIControlFontSize', 11, ...
'Visible', 'off', ...
'Tag', 'LoadDataDialog');
uicontrol(...
'Parent', hFig, ...
'Style', 'text', ...
'String', 'X:', ...
'FontWeight', 'bold', ...
'BackgroundColor', get(hFig, 'Color'), ...
'HorizontalAlignment', 'right', ...
'Position', [1 height-3 15 1.5]);
hX = uicontrol(...
'Parent', hFig, ...
'Style', 'popupmenu', ...
'String', xVars, ...
'BackgroundColor', 'white', ...
'HorizontalAlignment', 'right', ...
'Position', [16.5 height-2.7 30 1.5]);
uicontrol(...
'Parent', hFig, ...
'Style', 'text', ...
'String', 'Y:', ...
'FontWeight', 'bold', ...
'BackgroundColor', get(hFig, 'Color'), ...
'HorizontalAlignment', 'right', ...
'Position', [1 height-6 15 1.5]);
hY = uicontrol(...
'Parent', hFig, ...
'Style', 'popupmenu', ...
'String', yVars, ...
'BackgroundColor', 'white', ...
'HorizontalAlignment', 'right', ...
'Position', [16.5 height-5.7 30 1.5]);
uicontrol(...
'Parent', hFig, ...
'Style', 'pushbutton', ...
'Position', [3 1 20 2], ...
'String', 'Okay', ...
'Callback', @acceptButtonFcn);
uicontrol(...
'Parent', hFig, ...
'Style', 'pushbutton', ...
'Position', [27 1 20 2], ...
'String', 'Cancel', ...
'Callback', @cancelButtonFcn);
movegui(hFig, 'center');
set(hFig, 'Visible', 'on');
uiwait(hFig);
function acceptButtonFcn(varargin)
if get(hX, 'Value') == 1
x = [];
else
x = evalin('base', xVars{get(hX, 'Value')});
end
y = evalin('base', yVars{get(hY, 'Value')});
delete(hFig);
end
function cancelButtonFcn(varargin)
x = [];
y = [];
delete(hFig);
end
end
%--------------------------------------------------------------------------
% Dialog for specifying AVI recording settings
function out = aviSettings(frameRate, frameInterval)
% Get valid profiles for this machine
profiles = VideoWriter.getProfiles();
if nargin == 0
frameRate = profiles(1).FrameRate;
frameInterval = 1;
end
str = struct('Profile', {profiles.Name});
for iStr = 1:length(str)
str(iStr).FrameInterval = frameInterval;
str(iStr).WriteableProperties.FrameRate = frameRate;
switch lower(str(iStr).Profile)
case 'archival'
case 'motion jpeg 2000'
str(iStr).WriteableProperties.CompressionRatio = profiles(iStr).CompressionRatio;
case {'motion jpeg avi', 'mpeg-4'}
str(iStr).WriteableProperties.Quality = profiles(iStr).Quality;
case 'uncompressed avi'
otherwise
error('Unknown profile name');
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%
profileNames = {str.Profile};
width = 70;
height = 15;
labelOffsetH = 1;
entryOffsetH = 31.5;
labelWidth = 30;
entryWidth = 35;
labelHeight = 1.7;
entryHeight = 1.7;
lineInterval = 3;
hOptions = [];
idx = find(strcmpi('Motion JPEG AVI', profileNames));
fh = figure(...
'Units', 'characters', ...
'Position', [0 0 width height], ...
'Toolbar', 'none', ...
'Menu', 'none', ...
'Name', 'AVI Settings', ...
'NumberTitle', 'off', ...
'CloseRequestFcn', @cancelButtonFcn, ...
'Resize', 'off', ...
'defaultUIControlUnits', 'characters', ...
'defaultUIControlFontUnits', 'points', ...
'defaultUIControlFontSize', 11, ...
'Visible', 'off');
uicontrol(...
'Parent', fh, ...
'Style', 'text', ...
'Position', [labelOffsetH height-2 labelWidth labelHeight], ...
'BackgroundColor', get(fh, 'Color'), ...
'FontWeight', 'bold', ...
'HorizontalAlignment', 'right', ...
'String', 'Profile:');
uicontrol(...
'Parent', fh, ...
'Style', 'popupmenu', ...
'Position', [entryOffsetH height-2 entryWidth entryHeight], ...
'BackgroundColor', 'white', ...
'Value', idx, ...
'String', profileNames, ...
'Callback', @changeCompressionMethod);
uicontrol(...
'Parent', fh, ...
'Style', 'text', ...
'Position', [labelOffsetH height-2-lineInterval labelWidth labelHeight], ...
'BackgroundColor', get(fh, 'Color'), ...
'FontWeight', 'bold', ...
'HorizontalAlignment', 'right', ...
'String', 'Frame Interval:');
uicontrol(...
'Parent', fh, ...
'Style', 'edit', ...
'Position', [entryOffsetH height-2-lineInterval entryWidth entryHeight], ...
'BackgroundColor', 'white', ...
'String', str(idx).FrameInterval, ...
'Tooltip', 'Integer greater than 1', ...
'Callback', {@editValidationFcn, 'FrameInterval'});
createAdditionalFields();
uicontrol(...
'Parent', fh, ...
'Style', 'pushbutton', ...
'Position', [10 1 25 2], ...
'String', 'Save...', ...
'Callback', @acceptButtonFcn);
uicontrol(...
'Parent', fh, ...
'Style', 'pushbutton', ...
'Position', [40 1 25 2], ...
'String', 'Cancel', ...
'Callback', @cancelButtonFcn);
movegui(fh, 'center');
set(fh, 'Visible', 'on');
uiwait(fh);
function createAdditionalFields()
delete(hOptions);
hOptions = [];
fields = fieldnames(str(idx).WriteableProperties);
values = struct2cell(str(idx).WriteableProperties);
for iOpt = 1:length(fields)
switch fields{iOpt}
case 'FrameRate'
tooltipstr = 'Frames per second';
textstr = 'Frame Rate:';
case 'Quality'
tooltipstr = 'Integer between 0 and 100';
textstr = 'Quality:';
case 'CompressionRatio'
tooltipstr = 'Integer greater than 1';
textstr = 'Compression Ratio:';
end
hOptions(iOpt*2 - 1) = uicontrol(...
'Parent', fh, ...
'Style', 'text', ...
'Position', [labelOffsetH height-2-(iOpt+1)*lineInterval labelWidth labelHeight], ...
'BackgroundColor', get(fh, 'Color'), ...
'FontWeight', 'bold', ...
'HorizontalAlignment', 'right', ...
'String', textstr);
hOptions(iOpt*2) = uicontrol(...
'Parent', fh, ...
'Style', 'edit', ...
'Position', [entryOffsetH height-2-(iOpt+1)*lineInterval entryWidth entryHeight], ...
'BackgroundColor', 'white', ...
'String', values{iOpt}, ...
'Tooltip', tooltipstr, ...
'Callback', {@editValidationFcn, fields{iOpt}});
end
end
function editValidationFcn(hObj, edata, propName) %#ok<INUSL>
val = str2double(get(hObj, 'String'));
switch propName
case {'FrameRate', 'CompressionRatio'}
try %#ok<TRYNC>
validateattributes(val, {'numeric'}, {'scalar', 'integer'});
assert(val >= 1); % backwards compatibility for validateattributes
str(idx).WriteableProperties.(propName) = val;
end
set(hObj, 'String', str(idx).WriteableProperties.(propName));
case 'Quality'
try %#ok<TRYNC>
validateattributes(val, {'numeric'}, {'scalar', 'integer'});
assert(val >= 0 && val <= 100); % backwards compatibility for validateattributes
str(idx).WriteableProperties.(propName) = val;
end
set(hObj, 'String', str(idx).WriteableProperties.(propName));
case 'FrameInterval'
try %#ok<TRYNC>
validateattributes(val, {'numeric'}, {'scalar', 'integer'});
assert(val >= 1); % backwards compatibility for validateattributes
str(idx).(propName) = val;
end
set(hObj, 'String', str(idx).(propName));
end
end
function changeCompressionMethod(hObj, edata) %#ok<INUSD>
idx = get(hObj, 'Value');
createAdditionalFields();
end
function acceptButtonFcn(varargin)
switch lower(str(idx).Profile)
case {'archival', 'motion jpeg 2000'}
ext = '*.mj2';
case {'motion jpeg avi', 'uncompressed avi'}
ext = '*.avi';
case 'mpeg-4'
ext = '*.mp4;*.m4v';
otherwise
error('Unknown compression method');
end
[filename, pathname] = uiputfile(ext, 'Save as...');
if isnumeric(filename)
out = [];
else
out = str(idx);
out.filename = fullfile(pathname, filename);
end
delete(fh);
end
function cancelButtonFcn(varargin)
out = [];
delete(fh);
end
end
%--------------------------------------------------------------------------
% Dialog for specifying Animated GIF settings
function out = gifSettings(frameDelay, frameInterval)
disposalMethods = {'leaveInPlace', 'restoreBG', 'restorePrevious', 'doNotSpecify'};
out = struct('filename', '', 'FrameInterval', frameInterval, ...
'FrameDelay', frameDelay, 'DisposalMethod', disposalMethods{1}, ...
'Loop', inf);
width = 70;
height = 15;
labelOffsetH = 1;
entryOffsetH = 31.5;
labelWidth = 30;
entryWidth = 35;
labelHeight = 1.7;
entryHeight = 1.7;
lineInterval = 3;
fh = figure(...
'Units', 'characters', ...
'Position', [0 0 width height], ...
'Toolbar', 'none', ...
'Menu', 'none', ...
'Name', 'GIF Settings', ...
'NumberTitle', 'off', ...
'CloseRequestFcn', @cancelButtonFcn, ...
'Resize', 'off', ...
'defaultUIControlUnits', 'characters', ...
'defaultUIControlFontUnits', 'points', ...
'defaultUIControlFontSize', 11, ...
'Visible', 'off');
uicontrol(...
'Parent', fh, ...
'Style', 'text', ...
'Position', [labelOffsetH height-2 labelWidth labelHeight], ...
'BackgroundColor', get(fh, 'Color'), ...
'FontWeight', 'bold', ...
'HorizontalAlignment', 'right', ...
'String', 'Frame Interval:');
uicontrol(...
'Parent', fh, ...
'Style', 'edit', ...
'Position', [entryOffsetH height-2 entryWidth entryHeight], ...
'BackgroundColor', 'white', ...
'String', frameInterval, ...
'Tooltip', 'Integer greater than 1', ...
'Callback', {@editValidationFcn, 'FrameInterval'});
uicontrol(...
'Parent', fh, ...
'Style', 'text', ...
'Position', [labelOffsetH height-2-lineInterval labelWidth labelHeight], ...
'BackgroundColor', get(fh, 'Color'), ...
'FontWeight', 'bold', ...
'HorizontalAlignment', 'right', ...
'String', 'Frame delay (sec):');
uicontrol(...
'Parent', fh, ...
'Style', 'edit', ...
'Position', [entryOffsetH height-2-lineInterval entryWidth entryHeight], ...
'BackgroundColor', 'white', ...
'String', frameDelay, ...
'Tooltip', 'Positive number', ...
'Callback', {@editValidationFcn, 'FrameDelay'});
uicontrol(...
'Parent', fh, ...
'Style', 'text', ...
'Position', [labelOffsetH height-2-2*lineInterval labelWidth labelHeight], ...
'BackgroundColor', get(fh, 'Color'), ...
'FontWeight', 'bold', ...
'HorizontalAlignment', 'right', ...
'String', 'Disposal Method:');
uicontrol(...
'Parent', fh, ...
'Style', 'popupmenu', ...
'Position', [entryOffsetH height-2-2*lineInterval entryWidth entryHeight], ...
'BackgroundColor', 'white', ...
'String', disposalMethods, ...
'Callback', @disposalMethodSelectionFcn);
uicontrol(...
'Parent', fh, ...
'Style', 'text', ...
'Position', [labelOffsetH height-2-3*lineInterval labelWidth labelHeight], ...
'BackgroundColor', get(fh, 'Color'), ...
'FontWeight', 'bold', ...
'HorizontalAlignment', 'right', ...
'String', 'Loop:');
uicontrol(...
'Parent', fh, ...
'Style', 'checkbox', ...
'Position', [entryOffsetH height-2-3*lineInterval entryWidth entryHeight], ...
'BackgroundColor', get(fh, 'Color'), ...
'Value', 1, ...
'Callback', @loopFcn);
uicontrol(...
'Parent', fh, ...
'Style', 'pushbutton', ...
'Position', [10 1 25 2], ...
'String', 'Save...', ...
'Callback', @acceptButtonFcn);
uicontrol(...
'Parent', fh, ...
'Style', 'pushbutton', ...
'Position', [40 1 25 2], ...
'String', 'Cancel', ...
'Callback', @cancelButtonFcn);
movegui(fh, 'center');
set(fh, 'Visible', 'on');
uiwait(fh);
function editValidationFcn(hObj, edata, propName) %#ok<INUSL>
val = str2double(get(hObj, 'String'));
switch propName
case 'FrameDelay'
try %#ok<TRYNC>
validateattributes(val, {'numeric'}, {'scalar'});
assert(val >= 0); % backwards compatibility for validateattributes
out.FrameDelay = val;
end
case 'FrameInterval'
try %#ok<TRYNC>
validateattributes(val, {'numeric'}, {'scalar', 'integer'});
assert(val >= 1); % backwards compatibility for validateattributes
out.FrameInterval = val;
end
end
set(hObj, 'String', out.(propName));
end
function disposalMethodSelectionFcn(hObj, edata) %#ok<INUSD>
out.DisposalMethod = disposalMethods{get(hObj, 'Value')};
end
function loopFcn(hObj, edata) %#ok<INUSD>
if get(hObj, 'Value')
out.Loop = Inf;
else
out.Loop = 0;
end
end
function acceptButtonFcn(varargin)
[filename, pathname] = uiputfile('*.gif', 'Save as...');
if isnumeric(filename)
out = [];
else
out.filename = fullfile(pathname, filename);
end
delete(fh);
end
function cancelButtonFcn(varargin)
out = [];
delete(fh);
end
end