Code covered by the BSD License  

Highlights from
click2smooth.m v1.0 (Sep 2009)

image thumbnail
from click2smooth.m v1.0 (Sep 2009) by Carlos Adrian Vargas Aguilera
Smooths data by clicking on the figure.

click2smooth(varargin)
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

Contact us at files@mathworks.com