function [V,BUTTON,LIST] = click2smooth(varargin)
%CLICK2SMOOTH Interactively smooths a function by interpolation.
%
% SYNTAX:
% V = click2smooth(U);
% V = click2smooth(X,U);
% V = click2smooth(...,PropValue,PropName);
% V = click2smooth(AX,...);
% [V,BUTTON] = click2smooth(AX,...);
% [V,BUTTON,LIST] = click2smooth(AX,...);
%
% INPUT:
% U - Data vector to be smoothed with/without NaNs. See NOTE
% below for empty input.
% X - Argument of U.
% DEFAULT: 1:length(U)
% PN/PV - Paired optional arguments, as follows (see NOTE for :
% --------------------------------------------------------
% NAME | DESCRIPTION | VALUES
% --------------------------------------------------------
% 'Axis' U axis 'x' or 'y'
% DEFAULT: 'y'
%
% 'Times' How many times 0, 1 or Inf
% DEFAULT: Inf
%
% 'Bezier' How many times 0, 1 or Inf
% to use Bezier DEFAULT: 0
%
% 'Shadow' Shadow of original Percentaje
% DEFAULT: 20
%
% 'ZoomOut' Extra axis limits Percentaje
% DEFAULT: 10
%
% 'InPoint' Uses selected point true or false
% inside X range? DEFAULT: false
%
% 'EdgePoint' Uses selected point true or false
% outside X range? DEFAULT: true
%
% 'RepeatAvg' Average repeated true or false
% points? DEFAULT: true
%
% 'NanInterp' Interpolates NaNs? true or false
% DEFAULT: false
%
% 'FuncInterp' Interpolator See NOTE below
% DEFAULT: @interp1
%
% 'OptInterp' Interpolator See NOTE below
% extra option(s) DEFAULT:
% {'cubic','extrap'}
% --------------------------------------------------------
% Besides of the line properties: 'Color', 'LineStyle',
% 'LineWidth', 'Marker', 'MarkerSize', 'MarkerEdgeColor' and
% 'MarkerFaceColor' and the axes properties: 'XDir', 'YDir',
% 'XAxisLocation', 'YAxisLocation', 'XGrid', 'YGrid',
% 'XLabel', 'YLabel', 'XMinorTick', 'YMinorTick', 'XScale',
% 'YScale' and 'Title'.
% DEFAULT: none
% AX - Uses specified axes inteadof current one.
% DEFAULT: gca
%
% OUTPUT:
% V - Smoothed U data.
% BUTTON - Last button pressed.
% LIST - List of actions perfomed. See NOTE below for auto smoothing.
%
% DESCRIPTION:
% This function smooths a vector of data by interaction with the user.
% By clicking to select the data to be smoothed, until ENTER is
% pressed. Besides, with the KEYBOARD some actions may be done:
% -----------------------------------------------------
% BUTTON | ACTION
% -----------------------------------------------------
% Left click zoom in (following GINPUT2)
%
% Right click/SPACEBAR select "
%
% Double click zoom out "
%
% ENTER Finish and save
%
% ESC Finish without save
%
% BACKSPACE Start it over again
%
% DELETE Ignores last action
%
% LEFTARROW/DOWNARROW Undoes last smooth
%
% RIGHTARROW/UPARROW Redo the undone.
%
% R 'RepeatAvg' menu
%
% O 'OptInterp' menu
%
% F 'FuncInterp' menu
%
% I 'InInterp' menu
%
% E 'EdgeInterp' menu
%
% B 'Bezier' menu
%
% H HELP
% -----------------------------------------------------
%
% NOTE:
% * Optional inputs use its DEFAULT value when not given or [].
% * Optional outputs may or not be called.
% * The user may give the LIST as an input after U to apply a previous
% treatment to the new data, without user interfere (TIMES ans SHADOW
% are set to 0 as default). This is useful in case the last smoothed
% data were lost, or to reproduce the smoothing into a new set of
% data of equal size. Then, for example,
% >> [V,BUTTON,LIST] = click2smooth(U); % User smoothing
% >> W = click2smooth(U,LIST); % Auto smoothing
% >> all(W==V) % This is true.
% * If U is empty, it is looked for into the current line 'UserData'.
% That is, use U=[] to continuos a previous started click2smooth
% figure.
% * X and U are forced to ve column vector. So, V is also a column
% vector.
%
% EXAMPLE:
% N = 100;
% U = rand(N,1);
% V = click2smooth(U);
% hold on
% plot(V,'ro')
% hold off
%
% SEE ALSO:
% INTERP1.
%
% ---
% MFILE: click2smooth.m
% VERSION: 1.0 (Sep 02, 2009) (<a href="matlab:web('http://www.mathworks.com/matlabcentral/fileexchange/authors/11258')">download</a>)
% MATLAB: 7.7.0.471 (R2008b)
% AUTHOR: Carlos Adrian Vargas Aguilera (MEXICO)
% CONTACT: nubeobscura@hotmail.com
% REVISIONS:
% 1.0 Released. (Sep 02, 2009)
% DISCLAIMER:
% click2smooth.m is provided "as is" without warranty of any kind, under
% the revised BSD license.
% Copyright (c) 2009 Carlos Adrian Vargas Aguilera
% INPUTS CHECK-IN
% -------------------------------------------------------------------------
% Default.
myTag = 'click2smooth';
AX = get(get(0,'CurrentFigure'),'CurrentAxes');
AXIS = 'y';
TIMES = Inf;
BEZIER = 0;
SHADOW = 20;
ZOOM = 10;
IN = false;
EDGE = true;
REPEAT = true;
NAN = false;
FUNC = @interp1;
OPT = {'cubic','extrap'};
LINE = {'-k.'};
AXES = {};
XLABEL = '';
YLABEL = '';
TITLE = '';
V = [];
BUTTON = [];
LIST = {};
% GINPUT function.
% Select a modification of GINPUT or GINPUT2 that do not generate any
% error.
if exist('ginput2.m','file')==2
INPUT = @myGinput2;
else
INPUT = @myGinput;
end
% Constants KEYs.
%DOUBLECLICK = 0;
%LEFTCLICK = 1;
%MIDDLECLICK = 2;
RIGHTCLICK = 3;
BACKSPACE = 8;
ESCAPE = 27;
LEFTARROW = 28;
RIGHTARROW = 29;
UPARROW = 30;
DOWNARROW = 31;
SPACEBAR = 32;
DELETE = 127;
% ASCII = [ ...
% 33:64 ... UP-KEYS
% 65:90 ... UP-LETTERS
% 91:96 ... LOW-KEYS
% 97:122 ... LOW-LETTERS
% 123:126 ... LOW-KEY
% 161:255 ... FOREING
% ];
% Functionalities.
% Sets the behaviour of the program after a click or key pressed.
SELECT = [RIGHTCLICK,SPACEBAR];
FINISH = [];
RETURN = ESCAPE;
START = BACKSPACE;
IGNORE = DELETE;
UNDO = [LEFTARROW,DOWNARROW];
REDO = [RIGHTARROW,UPARROW];
OPTMENU = double('Oo');
REPEATMENU = double('Rr');
FUNCMENU = double('Ff');
INMENU = double('Ii');
EDGEMENU = double('Ee');
BEZIERMENU = double('Bb');
HELPMENU = double('Hh');
% Checks number of inputs and outputs.
if nargout>3
error('CVARGAS:click2smooth:tooManyOutputs',...
'At most 3 outputs are allowed.')
end
Nargin = nargin;
% Gets AX.
% Looks for axes handles input. May be a list of different axes with data
% to be smoothed en each one (with myTag as 'Tag').
if (length(varargin)>1) && all(ishandle(varargin{1}))
AX = varargin{1};
varargin(1) = [];
Nargin = Nargin-1;
end
% Gets U and X.
% Reads U and X from inputs. X may not be given and U may be empty!
if (Nargin>1) && isnumeric(varargin{2})
% X was given.
X = varargin{1};
nx = numel(X);
% % Checks monotonic function.
% [temp,temp] = sort(unique(X));
% if (nx==1) || (length(temp)~=nx) || any(abs(diff(temp))~=1)
% error('CVARGAS:click2smooth:incorrectMonotonicFunction',...
% 'X must be an icreasing/decreasing vector. U sould be monotonic.')
% end
% clear temp
U = varargin{2};
varargin(1:2) = [];
Nargin = Nargin-2;
elseif Nargin>0
% Only U given.
X = [];
U = varargin{1};
varargin(1) = [];
Nargin = Nargin-1;
else
% None was given.
X = [];
U = [];
end
% Checks X and U.
% Both must be vectors. Will be force to be column vectors.
su = size(U); if any(su==0), su(:) = 0; end
n = prod(su);
if (n~=max(su)) || (length(su)~=2)
error('CVARGAS:click2smooth:incorrectUShape',...
'U must be a vector.')
end
if isempty(X)
X = reshape(1:n,su);
else
if nx~=n
error('CVARGAS:click2smooth:incorrectXShape',...
'X must be a vector of equal length as U.')
end
X = reshape(X,su);
end
X = X(:);
U = U(:);
clear su nx
% Checks LIST input.
% LIST may be given after the U input to apply automatically and already
% saved smoothing.
if ~isempty(varargin) && iscell(varargin{1})
LIST = varargin{1};
varargin(1) = [];
Nargin = Nargin-1;
TIMES = 0;
SHADOW = 0;
end
% Gets extra paired inputs.
% Reads paired inputs or use the defaults.
[AXIS,TIMES,BEZIER,SHADOW,ZOOM,IN,EDGE,REPEAT,NAN,FUNC,OPT,LINE,...
AXES,XLABEL,YLABEL,TITLE] = ...
parsePairInputs(AXIS,TIMES,BEZIER,SHADOW,ZOOM,IN,EDGE,REPEAT,NAN,FUNC,...
OPT,LINE,AXES,XLABEL,YLABEL,TITLE,Nargin,varargin{:});
clear varargin Nargin
% -------------------------------------------------------------------------
% MAIN
% -------------------------------------------------------------------------
% Check empty U.
% Get data from figure and smoothed line if U was not given. The line must
% have {U,V} as 'UserData' and myTag as 'Tag'. The figure must have LINE as
% 'UserData'. The correct AXIS string should be provided when calling this
% function.
if isempty(U)
if isempty(AX), return, end
LI = findobj(AX,'Tag',myTag);
if isempty(LI), return, end
W = get(LI,[AXIS 'Data']);
if strcmp(AXIS,'x')
X = get(LI,'YData');
else
X = get(LI,'XData');
end
W = W(:);
X = X(:);
V = get(LI,'UserData');
U = V{1};
V = V{2};
n = length(X);
list = get(ancestor(AX,{'figure','uipanel'}),'UserData');
if ~iscell(list)
list = {};
end
% list is used in lowers and added to LIST latter.
else
W = U;
V = U;
list = {};
end
% Interpolates NaNs.
% Eliminates NaNs on U.
inan = isnan(U);
if (n-sum(inan))<3, return, end
if NAN
% Gets ranges (ignoring NaNs).
range = ~inan;
Xtemp = X(range);
Wtemp = W(range);
% Average repetitions.
if REPEAT
range = find(range);
[temp,ii,jj] = unique(Xtemp);
nOut = length(range);
if length(temp)~=nOut
for k = 1:nOut
if ~ismember(k,ii)
ind = jj(k)==jj;
Wtemp(ind) = mean(Wtemp(ind));
end
end
end
end
% Now smooths.
W(inan) = FUNC(Xtemp,Wtemp,X(inan),OPT{:});
end
V = W;
% Apply LIST.
% Smooths U automatically following the LIST.
if ~isempty(LIST)
nl = size(LIST,1);
for l = 1:nl
switch LIST{l,1}
case 'Bezier'
% Read List.
rangeIn = LIST{l,2};
xin = LIST{l,3};
yin = LIST{l,4};
cp(1,1) = LIST{l,5};
cp(1,2) = LIST{l,6};
if length(rangeIn)~=n
error('CVARGAS:click2smooth:incorrectListLength',...
'LIST was applied to a different length data.')
end
% Gets argument.
t = X(rangeIn);
t = t-min(t); t = t/max(t);
% Apply Bezier.
if isempty(xin)
W(rangeIn) = (1-t).^2*xin(1) + t.*(1-t)*cp(1,1)*2 + t.^2*xin(2);
else
W(rangeIn) = (1-t).^2*yin(1) + t.*(1-t)*cp(1,2)*2 + t.^2*yin(2);
end
case 'Interp'
% Read List.
rangeIn = LIST{l,2};
xin = LIST{l,3};
yin = LIST{l,4};
isX = LIST{l,5};
repeat = LIST{1,6};
func = LIST{l,7};
opt = LIST{l,8};
if length(rangeIn)~=n
error('CVARGAS:click2smooth:incorrectListLength',...
'LIST was applied to a different length data.')
end
% Gets ranges (ignoring NaNs).
rangeOut = ~rangeIn & ~isnan(W);
Xtemp = X(rangeOut);
Wtemp = W(rangeOut);
% Average repetitions.
if repeat
rangeOut = find(rangeOut);
[temp,ii,jj] = unique(Xtemp);
nOut = length(rangeOut);
if length(temp)~=nOut
for k = 1:nOut
if ~ismember(k,ii)
ind = jj(k)==jj;
Wtemp(ind) = mean(Wtemp(ind));
end
end
end
end
% Now smooths.
if isX
W(rangeIn) = func([Xtemp(:); xin(:)],[Wtemp(:); yin(:)],X(rangeIn),...
opt{:});
else
W(rangeIn) = func([Xtemp(:); yin(:)],[Wtemp(:); xin(:)],X(rangeIn),...
opt{:});
end
end
end
V = W;
end
% Complete list.
LIST = [list; LIST];
% Finish?
if TIMES==0
return
end
% Sets axes.
% Customizes the axes to be used.
if isempty(AX), AX = gca; end
axes(AX(1))
set(AX,'DrawMode','fast',AXES{:})
if ~isempty(XLABEL), xlabel(AX(1),XLABEL), end
if ~isempty(YLABEL), ylabel(AX(1),YLABEL), end
if ~isempty(TITLE), title( AX(1),TITLE), end
% Gets current figure.
FI = ancestor(AX(1),{'figure','uipanel'});
% Gets axis string.
isX = strcmp(AXIS,'x');
% Plots shadow.
% Draws the original data (without any smoothing) as a shadow.
if SHADOW~=0
if isX
hs = plot(AX(1),U,X,LINE{:});
else
hs = plot(AX(1),X,U,LINE{:});
end
cs = get(hs,'Color');
if all(cs==0)
cs(:) = 1-(SHADOW/100);
else
cs = rgb2hsv(cs);
cs(:,2) = cs(:,2)*(SHADOW/100);
cs(:,3) = 1-cs(:,3)*(SHADOW/100);
cs = hsv2rgb(cs);
end
set(hs,'Color',cs)
clear hs
end
% Plots function.
% Draws the data and it will be modified by the user. For this reason the
% 'Tag' is important.
ihold = ishold(AX(1));
hold(AX(1),'on')
if isX
plot(AX(1),W,X,LINE{:},'Tag',myTag,'UserData',{U,V})
else
plot(AX(1),X,W,LINE{:},'Tag',myTag,'UserData',{U,V})
end
if ~ihold
hold(AX(1),'off')
end
% Sets limits.
% Wides the limits of the axes to be able to click outside the data limits.
limU = [min(U) max(U)];
limX = [min(X) max(X)];
if isX
if strcmp(get(AX(1),'YLimMode'),'auto')
set(AX(1),'YLim',limX+diff(limX)*(ZOOM/100)*[-1 1])
end
if strcmp(get(AX(1),'XLimMode'),'auto')
set(AX(1),'XLim',limU+diff(limU)*(ZOOM/100)*[-1 1])
end
else
if strcmp(get(AX(1),'XLimMode'),'auto')
set(AX(1),'XLim',limX+diff(limX)*(ZOOM/100)*[-1 1])
end
if strcmp(get(AX(1),'YLimMode'),'auto')
set(AX(1),'YLim',limU+diff(limU)*(ZOOM/100)*[-1 1])
end
end
% Starts the figure name.
% The figure name will be used to display information about the user
% activity.
set(FI,'Name','First click')
% MAIN LOOP. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% NOTE:
% * U is the original data
% * V is the data before smooth
% * W is the smoothed data
% Save the LIST for undo.
list = LIST;
cont = 0;
while cont<TIMES
% FIRST CLICK ------------------------------------------------------------
[x1,y1,BUTTON] = INPUT(1);
% Checks axes.
if any(~ishandle(AX)), return, end
AX1 = gca;
if ~any(AX1==AX), continue, end
% Updates line and data.
LI = findobj(AX1,'Tag',myTag);
if isempty(LI), continue, end
W = get(LI,[AXIS 'Data']);
W = W(:);
V = get(LI,'UserData');
U = V{1};
V = V{2};
% FINISH?
if isempty(BUTTON) || ismember(BUTTON,FINISH)
V = W;
set(FI,'Name','Exit and saved')
return
end
% SELECT?
if ismember(BUTTON,SELECT)
set(FI,'Name','Second click')
% Plots selection line.
ihold = ishold(AX1);
hold(AX1,'on')
if isX
H1 = plot(limX,y1*[1 1],'g:');
else
H1 = plot(x1*[1 1],limU,'g:');
end
if ~ihold
hold(AX1,'off')
end
while 1
% SECOND CLICK ---------------------------------------------------------
[x2,y2,BUTTON] = INPUT(1);
% Checks axes.
if any(~ishandle(AX)), return, end
if gca~=AX1, continue, end
% FINISH?
if isempty(BUTTON) || ismember(BUTTON,FINISH)
V = W;
set(FI,'Name','Exit and saved')
return
end
% SELECT?
if ismember(BUTTON,SELECT)
% Clears selection line.
delete(H1)
% Selected range out and in.
nnan = ~isnan(W);
if isX
rangeIn = nnan;
rangeIn(rangeIn) = (X(rangeIn)>=min(y1,y2));
rangeIn(rangeIn) = (X(rangeIn)<=max(y1,y2));
else
rangeIn = nnan;
rangeIn(rangeIn) = (X(rangeIn)>=min(x1,x2));
rangeIn(rangeIn) = (X(rangeIn)<=max(x1,x2));
end
rangeOut = ~rangeIn & nnan;
if (sum(rangeOut)<=2) || (sum(rangeOut)==n) || (sum(rangeIn)==0)
break
end
% Checks if should include the selected points.
xin1 = []; xin2 = [];
yin1 = []; yin2 = [];
out1 = (x1<limX(1)) | (x1>limX(2));
out2 = (x2<limX(1)) | (x2>limX(2));
if (EDGE && out1) || (~out1 && IN)
xin1 = x1;
yin1 = y1;
end
if (EDGE && out2) || (~out2 && IN)
xin2 = x2;
yin2 = y2;
end
xin = [xin1 xin2];
yin = [yin1 yin2];
% Do beizer?
if BEZIER~=0
set(FI,'Name','Third click. Bezier')
t = X(rangeIn);
t = t-min(t); t = t/max(t);
if isX
yin = [];
if isempty(xin1), xin1 = W(rangeIn); xin1 = xin1(1); end
if isempty(xin2), xin2 = W(rangeIn); xin2 = xin2(end); end
xin = [xin1 xin2];
else
xin = [];
if isempty(yin1), yin1 = W(rangeIn); yin1 = yin1(1); end
if isempty(yin2), yin2 = W(rangeIn); yin2 = yin2(end); end
yin = [yin1 yin2];
end
set(FI,'WindowButtonMotionFcn',...
@(x,y)myWinButMotFun(AX1,LI,rangeIn,t,W,xin,yin))
set(FI,'WindowButtonDownFcn',...
@(x,y)myWinButDowFun(FI,AX1,LI,rangeIn,t,W,xin,yin))
myWinButMotFun(AX1,LI,rangeIn,t,W,xin,yin)
try
keydown = waitforbuttonpress;
catch
keydown = [];
end
if ~ishandle(FI), return, end
if ~isempty(keydown) && (get(0,'CurrentFigure')==FI) && ...
(get(FI,'CurrentAxes')==AX1)
current_char = double(get(FI,'CurrentCharacter'));
if ~(keydown && (current_char==3)) || ~keydown % control+C or click
% Updates data.
V = W;
set(LI,'UserData',{U,V})
%W = get(LI,[AXIS 'Data']);
cont = cont+1;
if BEZIER==1
BEZIER = 0;
end
cp = get(gca,'CurrentPoint');
LIST(end+1,:) = {'Bezier',rangeIn,xin,yin,cp(1,1),cp(1,2),[],[]};
set(FI,'UserData',LIST)
end
end
set(FI,'Name','First click')
set(FI,'WindowButtonMotionFcn',[])
set(FI,'WindowButtonDownFcn',[])
break
% Bezier used
end
% Uses interpolation.
set(FI,'Name','First click')
% Gets ranges (ignoring NaNs).
Xtemp = X(rangeOut);
Wtemp = W(rangeOut);
% Average repetitions.
if REPEAT
rangeOut = find(rangeOut);
[temp,ii,jj] = unique(Xtemp);
nOut = length(rangeOut);
if length(temp)~=nOut
for k = 1:nOut
if ~ismember(k,ii)
ind = jj(k)==jj;
Wtemp(ind) = mean(Wtemp(ind));
end
end
end
end
% Now smooths.
V = W;
set(LI,'UserData',{U,V})
if isX
if length([Xtemp; xin])<2, break, end
W(rangeIn) = FUNC([Xtemp(:); xin(:)],[Wtemp(:); yin(:)],X(rangeIn),...
OPT{:});
else
if length([Xtemp; yin])<2, break, end
W(rangeIn) = FUNC([Xtemp(:); yin(:)],[Wtemp(:); xin(:)],X(rangeIn),...
OPT{:});
end
% Updates figure.
set(LI,[AXIS 'Data'],W)
drawnow
cont = cont+1;
LIST(end+1,:) = {'Interp',rangeIn,xin,yin,isX,REPEAT,FUNC,OPT};
set(FI,'UserData',LIST)
break
end
% RETURN?
if ismember(BUTTON,RETURN)
V = U;
W = V;
set(LI,[AXIS 'Data'],W,'UserData',{U,V})
LIST = {};
set(FI,'UserData',LIST)
delete(H1)
set(FI,'Name','Exit without saving')
return
end
% START?
if ismember(BUTTON,START)
V = W;
if NAN
% Gets ranges (ignoring NaNs).
range = ~inan;
Xtemp = X(range);
Wtemp = W(range);
% Average repetitions.
if REPEAT
range = find(range);
[temp,ii,jj] = unique(Xtemp);
nOut = length(range);
if length(temp)~=nOut
for k = 1:nOut
if ~ismember(k,ii)
ind = jj(k)==jj;
Wtemp(ind) = mean(Wtemp(ind));
end
end
end
end
% Now smooths.
W(inan) = FUNC(Xtemp,Wtemp,X(inan),OPT{:});
end
set(LI,[AXIS 'Data'],W,'UserData',{U,V})
list = LIST;
LIST = {};
set(FI,'UserData',LIST)
delete(H1)
set(FI,'Name','First click (started it over)')
break
end
% IGNORE?
if ismember(BUTTON,IGNORE)
delete(H1)
set(FI,'Name','First click (ignored last click)')
break
end
% UNDO?
if ismember(BUTTON,UNDO)
temp = W;
W = V;
V = temp;
set(LI,[AXIS 'Data'],W,'UserData',{U,V})
list = LIST;
if ~isempty(LIST)
LIST(end,:) = [];
end
set(FI,'UserData',LIST)
delete(H1)
set(FI,'Name','First click (undo last interpolation)')
break
end
% REDO?
if ismember(BUTTON,REDO)
temp = V;
V = W;
W = temp;
set(LI,[AXIS 'Data'],W,'UserData',{U,V})
if ~isempty(list)
LIST(end+1,:) = list(end,:);
list(end,:) = [];
end
set(FI,'UserData',LIST)
delete(H1)
set(FI,'Name','First click (redo last interpolation)')
break
end
% FUNCMENU?
if ismember(BUTTON,FUNCMENU)
[FUNC,OPT] = changeFunc(FUNC,OPT);
continue
end
% OPTMENU?
if ismember(BUTTON,OPTMENU)
OPT = changeOpt(OPT,FUNC);
continue
end
% INMENU?
if ismember(BUTTON,INMENU)
IN = changeIn(IN);
continue
end
% EDGEMENU?
if ismember(BUTTON,EDGEMENU)
EDGE = changeEdge(EDGE);
continue
end
% REPEATMENU?
if ismember(BUTTON,REPEATMENU)
REPEAT = changeRepeat(REPEAT);
continue
end
% BEZIERMENU?
if ismember(BUTTON,BEZIERMENU)
BEZIER = changeBezier(BEZIER);
continue
end
% HELPMENU?
if ismember(BUTTON,HELPMENU)
giveSomeHelp
continue
end
end
continue
end
% RETURN?
if ismember(BUTTON,RETURN)
V = U;
W = V;
set(LI,[AXIS 'Data'],W,'UserData',{U,V})
LIST = {};
set(FI,'UserData',LIST)
set(FI,'Name','Exit without saving')
return
end
% START?
if ismember(BUTTON,START)
V = W;
if NAN
% Gets ranges (ignoring NaNs).
range = ~isnan(W);
Xtemp = X(range);
Wtemp = W(range);
% Average repetitions.
if REPEAT
range = find(range);
[temp,ii,jj] = unique(Xtemp);
nOut = length(range);
if length(temp)~=nOut
for k = 1:nOut
if ~ismember(k,ii)
ind = jj(k)==jj;
Wtemp(ind) = mean(Wtemp(ind));
end
end
end
end
% Now smooths.
W(inan) = FUNC(Xtemp,Wtemp,X(inan),OPT{:});
end
set(LI,[AXIS 'Data'],W,'UserData',{U,V})
list = LIST;
LIST = {};
set(FI,'UserData',LIST)
set(FI,'Name','First click (started it over)')
continue
end
% IGNORE?
if ismember(BUTTON,IGNORE)
set(FI,'Name','First click')
continue
end
% UNDO?
if ismember(BUTTON,UNDO)
temp = W;
W = V;
V = temp;
set(LI,[AXIS 'Data'],W,'UserData',{U,V})
list = LIST;
if ~isempty(LIST)
LIST(end,:) = [];
end
set(FI,'UserData',LIST)
set(FI,'Name','First click (undo last interpolation)')
continue
end
% REDO?
if ismember(BUTTON,REDO)
temp = V;
V = W;
W = temp;
set(LI,[AXIS 'Data'],W,'UserData',{U,V})
if ~isempty(list)
LIST(end+1,:) = list(end,:);
list(end,:) = [];
end
set(FI,'UserData',LIST)
set(FI,'Name','First click (redo last interpolation)')
continue
end
% FUNCMENU?
if ismember(BUTTON,FUNCMENU)
[FUNC,OPT] = changeFunc(FUNC,OPT);
continue
end
% OPTMENU?
if ismember(BUTTON,OPTMENU)
OPT = changeOpt(OPT,FUNC);
continue
end
% INMENU?
if ismember(BUTTON,INMENU)
IN = changeIn(IN);
continue
end
% EDGEMENU?
if ismember(BUTTON,EDGEMENU)
EDGE = changeEdge(EDGE);
continue
end
% REPEATMENU?
if ismember(BUTTON,REPEATMENU)
REPEAT = changeRepeat(REPEAT);
continue
end
% BEZIERMENU?
if ismember(BUTTON,BEZIERMENU)
BEZIER = changeBezier(BEZIER);
continue
end
% HELPMENU?
if ismember(BUTTON,HELPMENU)
giveSomeHelp
continue
end
end
% =========================================================================
% SUBFUNCTIONS
% -------------------------------------------------------------------------
function giveSomeHelp
% Give some help.
helpdlg(...
{'---------------------------------------------------------------------------'
' BUTTON ACTION'
'---------------------------------------------------------------------------'
' Right click/[Spacebar] Select'
' Left click Zoom in (in GINPUT2)'
' Double click Zoom out'
' [Enter] Finish and save'
' [Escape] Finish without saving'
' [Backspace] Start it over again'
' [Delete] Ignores last action'
' [LeftArrow]/[DownArrow] Undoes last smooth'
' [RightArrow]/[UpArrow] Redo the undone'
' R ''RepeatAvg'' menu'
' O ''OptInterp'' menu'
' F ''FuncInterp'' menu'
' I ''InInterp'' menu'
' E ''EdgeInterp'' menu'
' B ''Bezier'' menu'
' H HELP'
'---------------------------------------------------------------------------'},...
'CLICK2SMOOTH Help')
function [x,y,BUTTON] = myGinput(N)
% My customized GINPUT.
x = []; y = []; BUTTON = [];
try
[x,y,BUTTON] = ginput(N);
end
function [x,y,BUTTON] = myGinput2(N)
% My customized GINPUT2.
temp = warning('off','CVARGAS:ginput2:executionError');
[x,y,BUTTON] = ginput2(N);
warning(temp.state,'CVARGAS:ginput2:executionError')
function myWinButMotFun(AX,LI,rangeIn,t,W,xin,yin)
% WindowButtonMotionFcn.
if ishandle(AX)
cp = get(gca,'CurrentPoint');
if AX==gca
% Apply Bezier.
if ~isempty(xin)
W(rangeIn) = (1-t).^2*xin(1) + t.*(1-t)*cp(1,1)*2 + t.^2*xin(2);
set(LI,'XData',W)
else
W(rangeIn) = (1-t).^2*yin(1) + t.*(1-t)*cp(1,2)*2 + t.^2*yin(2);
set(LI,'YData',W)
end
drawnow
end
end
function myWinButDowFun(FI,AX,LI,rangeIn,t,W,xin,yin)
% WindowButtonDownFcn.
if ~ishandle(AX), return, end
myWinButMotFun(AX,LI,rangeIn,t,W,xin,yin)
set(FI,'WindowButtonMotionFcn',[])
set(FI,'WindowButtonDownFcn',[])
function BEZIER = changeBezier(BEZIER)
% Bezier menu.
dialog = { 'Change ''Bezier'' interpolation'; ...
['(from "' num2str(BEZIER) '" to):']};
temp = menu(dialog,'0','1','Inf');
switch temp
case 1
BEZIER = 0;
case 2
BEZIER = 1;
case 3
BEZIER = Inf;
end
function REPEAT = changeRepeat(REPEAT)
% Repeat menu.
if REPEAT
val = 'true';
else
val = 'false';
end
dialog = {'Change ''RepeatAvg'' treatment'; ['(from "' val '" to):']};
temp = menu(dialog,'true','false');
switch temp
case 1
REPEAT = true;
case 2
REPEAT = false;
end
function EDGE = changeEdge(EDGE)
% Edge menu.
if EDGE
val = 'true';
else
val = 'false';
end
dialog = {'Change ''EdgePoint'' treatment'; ['(from "' val '" to):']};
temp = menu(dialog,'true','false');
switch temp
case 1
EDGE = true;
case 2
EDGE = false;
end
function IN = changeIn(IN)
% In menu.
if IN
val = 'true';
else
val = 'false';
end
dialog = {'Change ''InPoint'' treatment'; ['(from "' val '" to):']};
temp = menu(dialog,'true','false');
switch temp
case 1
IN = true;
case 2
IN = false;
end
function [FUNC,OPT] = changeFunc(FUNC,OPT)
% Func menu.
if strcmp(func2str(FUNC),'interp1')
dialog = {'Change interpolator function to:','and its extra options to:'};
if iscellstr(OPT)
nopt = length(OPT);
if nopt==1
list = ['''' OPT{1} ''''];
else
list = reshape([ repmat({''''},nopt,1) OPT' ...
{repmat(''',',nopt-1,1); ''''}]',1,[]);
list = [list{:}];
end
else
list = '';
end
defaultanswer = {['@' func2str(FUNC)],list};
options.Resize = 'on';
options.WindowStyle = 'normal';
answer = inputdlg(dialog,'INPUT',1,defaultanswer,options);
if isempty(answer), return, end
FUNC = eval(answer{1});
if ischar(FUNC), FUNC = str2func(FUNC); end
answer(1) = [];
OPT = {};
if ~isempty(answer)
answer = answer{1};
ind = [0 strfind(answer,',') length(answer)+1];
for k = 1:length(ind)-1
OPT{end+1} = answer(ind(k)+1:ind(k+1)-1);
try
OPT{end} = eval(OPT{end});
end
end
end
end
function OPT = changeOpt(OPT,FUNC)
% Opt menu.
if strcmp(func2str(FUNC),'interp1')
dialog = {'Change INTERP1 method'; ['(from ''' OPT{1} ''' to):']};
temp = menu(dialog,'nearest','linear','spline','cubic');
switch temp
case 1
OPT{1} = 'nearest';
case 2
OPT{1} = 'linear';
case 3
OPT{1} = 'spline';
case 4
OPT{1} = 'cubic';
end
else
dialog = {'Change interpolator options to:'};
if iscellstr(OPT)
nopt = length(OPT);
if nopt==1
list = ['''' OPT{1} ''''];
else
list = reshape([ repmat({''''},nopt,1) OPT' ...
{repmat(''',',nopt-1,1); ''''}]',1,[]);
list = [list{:}];
end
else
list = '';
end
defaultanswer = {list};
options.Resize = 'on';
options.WindowStyle = 'normal';
answer = inputdlg(dialog,'INPUT',1,defaultanswer,options);
OPT = {};
if ~isempty(answer)
answer = answer{1};
ind = [0 strfind(answer,',') length(answer)+1];
for k = 1:length(ind)-1
OPT{end+1} = answer(ind(k)+1:ind(k+1)-1);
try
OPT{end} = eval(OPT{end});
end
end
end
end
function [AXIS,TIMES,BEZIER,SHADOW,ZOOM,IN,EDGE,REPEAT,NAN,FUNC,OPT,...
LINE,AXES,XLABEL,YLABEL,TITLE] = ...
parsePairInputs(AXIS,TIMES,BEZIER,SHADOW,ZOOM,IN,EDGE,REPEAT,NAN,FUNC,...
OPT,LINE,AXES,XLABEL,YLABEL,TITLE,Nargin,varargin)
% Reads extra inputs.
% Checks parity
if rem(Nargin,2)~=0
error('CVARGAS:click2smooth:incorrectPairInput',...
'Optional inputs must be pairs.')
end
for k = 1:2:Nargin
if isempty(varargin{k}) || ~ischar(varargin{k})
error('CVARGAS:click2smooth:invalidPropertyName',...
'Invalid property name.')
else
switch lower(varargin{k})
case {'axis','axi','ax','a'}
AXIS = lower(varargin{k+1});
if ~isX || ~strcmp(AXIS,'y')
error('CVARGAS:click2smooth:incorrectAxisValue',...
'''Axis'' value must be one of ''x'' or ''y''.')
end
case {'times','time','tim','ti','t'}
TIMES = varargin{k+1};
if ~isnumeric(TIMES) || ~(length(TIMES)==1) || (TIMES~=0) || ...
(TIMES~=1) || ~isinf(TIMES)
error('CVARGAS:click2smooth:incorrectTimesValue',...
'''Times'' value must be a 0, 1 or Inf.')
end
case {'bezier','bezie','bezi','bez','be','b'}
BEZIER = varargin{k+1};
if ~isnumeric(BEZIER) || ~(length(BEZIER)==1) || (BEZIER~=0) || ...
(BEZIER~=1) || ~isinf(BEZIER)
error('CVARGAS:click2smooth:incorrectBezierValue',...
'''Bezier'' value must be 0, 1 or Inf.')
end
case {'shadow','shado','shad','sha','sh','s'}
SHADOW = varargin{k+1};
if ~isnumeric(SHADOW) || (SHADOW<0) || (SHADOW>100)
error('CVARGAS:click2smooth:incorrectShadowValue',...
'''Shadow'' value must be within 0 and 100.')
end
case {'zoomout','zoomou','','zoomo','zoom','zoo','zo','z'}
ZOOM = varargin{k+1};
if ~isnumeric(ZOOM) || (ZOOM<0) || (ZOOM>100)
error('CVARGAS:click2smooth:incorrectZoomoutValue',...
'''Zoomout'' value must be within 0 and 100.')
end
case {'inpoint','inpoin','inpoi','inpo','inp','in','i'}
IN = varargin{k+1};
if (IN~=true) || (IN~=false)
error('CVARGAS:click2smooth:incorrectInpointValue',...
'''InPoint'' value must be one of true or false.')
end
case {'edgepoint','edgepoin','edgepoi','edgepo','edgep','edge','edg',...
'ed','e'}
EDGE = varargin{k+1};
if (EDGE~=true) || (EDGE~=false)
error('CVARGAS:click2smooth:incorrectEdgepointValue',...
'''EdgePoint'' value must be one of true or false.')
end
case {'repeatavg','repeatav','repeata','repeat','repea','repe','rep',...
're','r'}
REPEAT = varargin{k+1};
if (REPEAT~=true) || (REPEAT~=false)
error('CVARGAS:click2smooth:incorrectRepeatavgValue',...
'''RepeatAvg'' value must be one of true or false.')
end
case {'naninterp','naninter','naninte','nanint','nanin','nani','nan',...
'na','n'}
NAN = varargin{k+1};
if (NAN~=true) || (NAN~=false)
error('CVARGAS:click2smooth:incorrectNaninterpValue',...
'''NanInterp'' value must be one of true or false.')
end
case {'funcinterp','funcinter','funcinte','funcint','funcin','funci',...
'func','fun','fu','f'}
FUNC = varargin{k+1};
if ischar(FUNC), FUNC = str2func(FUNC); end
if ~ishandle(FUNC)
error('CVARGAS:click2smooth:incorrectFuncinterpValue',...
['"' func2str(FUNC) '" is nod a valid function.'])
end
case {'optinterp','optinter','optinte','optint','optin','opti','opt',...
'op','o'}
OPT = varargin{k+1};
if ~iscell(OPT)
OPT = {OPT};
end
case {'color','colo','col','co',...
'linestyle','linestyl','linesty','linest','linest','lines',...
'line','lin','li','l',...
'linewidth','linewidt','linewid','linewi','linew',...
'marker',...
'markersize','markersiz','markersi','markers',...
'markeredgecolor','markeredgecolo','markeredgecol',...
'markeredgeco','markeredgec','markeredge','markeredg',...
'markered','markere',...
'markerfacecolor','markerfacecolo','markerfacecol',...
'markerfaceco','markerfacec','markerface','markerfac',...
'markerfa','markerf'}
LINE = [LINE varargin(k:k+1)];
case {'xdir','xdi','xd',...
'ydir','ydi','yd',...
'xaxislocation','xaxislocatio','xaxislocati','xaxislocat',...
'xaxisloca','xaxisloc','xaxislo','xaxisl','xaxis','xaxi',...
'xax','xa',...
'yaxislocation','yaxislocatio','yaxislocati','yaxislocat',...
'yaxisloca','yaxisloc','yaxislo','yaxisl','yaxis','yaxi',...
'yax','ya',...
'xgrid','xgri','xgr','xg',...
'ygrid','ygri','ygr','yg',...
'xminortick','xminortic','xminorti','xminort',...
'yminortick','yminortic','yminorti','yminort',...
'xscale','xscal','xsca','xsc','xs',...
'yscale','yscal','ysca','ysc','ys'}
AXES = [AXES varargin(k:k+1)];
case {'xlabel','xlabe','xlab','xla'}
XLABEL = varargin{k+1};
case {'ylabel','ylabe','ylab','yla'}
YLABEL = varargin{k+1};
case {'title','titl','tit'}
TITLE = varargin{k+1};
otherwise
error('CVARGAS:click2smooth:unrecognizedPropertyName',...
['Unrecognized ''' varargin{k} ''' property name.'])
end
end
end
% [EOF] click2smooth.m