Code covered by the BSD License  

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

image thumbnail

click2smooth.m v1.0 (Sep 2009)

by

 

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