function he=ellipseselect(varargin)
%ELLIPSESELECT An interactive ellipse selection tool
%
% HE = ELLIPSESELECT('init', A, B, X0, Y0, PHI, STYLE)
% Creates the ellipse and initialzes callbacks. Returns the handle of the
% ellipse HE.
% Ellipse parameters:
% A Length of the semi-major axis
% B Length of the semi-minor axis
% X0 Abscissa of the center of the ellipse
% Y0 Ordinate of the center of the ellipse
% PHI Angle (in radians) between x-axis and the major axis
% STYLE Definition of the plotted line style
% Usage:
% Once created, the ellipse shape can the adjusted interactively using the mouse.
% The center can be moved by dragging the 'x' at the center of the
% ellipse. The lengths of the axes can be adjusted by dragging on the red
% and black control points on the ellipse. The ellipse can be rotated
% through an arbitrary angle by left clicking and dragging on the
% ellipse boundary.
% The ellipse parameters are saved in the ellipse handle's userdata
% property and can be obtained using:
% PARAMS = ELLIPSESELECT('params', HE), where PARAMS is a structure
% with the ellipse parameters.
% The ellipse can be deleted by right clicking on the ellipse boundary
% and selecting 'delete' from the context-menu. Alternatively, the ellipse
% can be deleted programmatically by using the following call to ELLIPSESELECT.
% STATUS = ELLIPSESELECT('deletehandle',HE)
% STATUS returns 0 if the delete operation was successful, -1 otherwise.
%
% Example:
% %generate some data
% x=randn(500,2).*repmat([0.1,0.05],500,1);
% phi=pi/4; rotmat = [cos(phi),sin(phi);-sin(phi),cos(phi)];
% y=rotmat*x';
% scatter(y(1,:),y(2,:),'c.'); hold on
% axis([-1,1,-1,1])
% %create an ellipse
% he = ellipseselect('init',1,1,0,0,pi/4,'r-.');
% % modify ellipse line style
% set(he,'LineWidth',2);
% % get current ellipse parameters
% params = ellipseselect('params',he);
% % delete the ellipse
% status = ellipseselect('deletehandle',he);
% Known issues: overwrites userdata of figure
% Author: Rajiv Narayan, <askrajiv@gmail.com>
% Dept. of Biomedical Engineering, Boston University.
% Last Revision: 8-Oct-2006.
% Copyright (c)2006, Rajiv Narayan
% Ellipse plotting via ellipsedraw (Copyright (c)2003, Lei Wang
% <WangLeiBox@hotmail.com>)
nin=nargin;
if(~nin)
error('no action specified');
else
action=varargin{1};
end
switch(action)
case 'init'
%get ellipse params
ud.a=varargin{2};
ud.b=varargin{3};
ud.x0=varargin{4};
ud.y0=varargin{5};
ud.phi=varargin{6};
ud.style=varargin{7};
%draw ellipse and mark center
[x,y]=ellipse(ud.a,ud.b,ud.x0,ud.y0,ud.phi);
he=plot(x,y,ud.style,'linewidth',2);
lincolor=get(he,'color');
hold on;
hc=plot(ud.x0,ud.y0,'marker','x','color','k','linewidth',2);
%ellipse control pts
ha=plot(ud.x0+cos(ud.phi)*ud.a,ud.y0+sin(ud.phi)*ud.a,'marker','o','color','k','linewidth',2,'markerfacecolor','r');
hb=plot(ud.x0-sin(ud.phi)*ud.b,ud.y0+cos(ud.phi)*ud.b,'marker','o','color','k','linewidth',2,'markerfacecolor','k');
%setup callbacks
cbk=cbkstr(he, 'down');
set(he,'buttondownfcn',cbk);
cbk=cbkstr(ha, 'majordown');
set(ha,'buttondownfcn',cbk);
cbk=cbkstr(hb, 'minordown');
set(hb,'buttondownfcn',cbk);
cbk=cbkstr(hc, 'origindown');
set(hc,'buttondownfcn',cbk);
%setup contextmenu
cmenu=uicontextmenu;
set(he,'uicontextmenu',cmenu);
cbk=cbkstr(he, 'delete');
item=uimenu(cmenu, 'label', 'delete', 'callback', cbk);
ud.axis=axis;
ud.he=he;
ud.hc=hc;
ud.ha=ha;
ud.hb=hb;
%save userdata
set(he,'userdata',ud)
set(hc,'userdata',he);
set(ha,'userdata',he);
set(hb,'userdata',he);
set(gcf,'userdata',ud);
%mouse click on ellipse boundary
case 'down'
which_button = get(gcf,'selectiontype');
switch which_button
case 'normal' %left click
cbk=cbkstr(gco, 'move');
ud=get(gco,'userdata');
set(gco, 'linewidth', 2);
set (gcf, 'windowbuttonmotionfcn' , cbk);
cbk=cbkstr(gco, 'up');
%also save current mouse position
pt=get(gca, 'currentpoint');
ud.oldpt=pt(1,1:2);
set (gcf, 'windowbuttonupfcn' , cbk, 'doublebuffer', 'on','userdata',ud);
end
%Rotate ellipse
case 'move'
pt=get(gca, 'currentpoint');
pt=pt(1,1:2);
ud=get(gcf,'userdata');
ax=ud.a*cos(ud.phi);
ay=ud.a*sin(ud.phi);
dphi = atan2(((pt(2)-ud.y0)),((pt(1)-ud.x0))) - atan2(((ud.oldpt(2)-ud.y0)),((ud.oldpt(1)-ud.x0))) ;
ud.phi =ud.phi +dphi;
ud.oldpt=pt;
[x,y]=ellipse(ud.a,ud.b,ud.x0,ud.y0,ud.phi);
set(gco,'xdata',x,'ydata',y);
set(ud.ha,'xdata',ud.x0+cos(ud.phi)*ud.a,'ydata',ud.y0+ ...
sin(ud.phi)*ud.a);
set(ud.hb,'xdata',ud.x0-sin(ud.phi)*ud.b,'ydata',ud.y0+cos(ud.phi)*ud.b);
axis(ud.axis)
set(gcf,'userdata',ud);
% Done rotation
case 'up'
ud=get(gcf,'userdata');
set(ud.he,'userdata',ud);
set(gcf,'windowbuttonupfcn','', 'windowbuttonmotionfcn', '', 'doublebuffer', 'off');
set(gco, 'linewidth', 2);
% mouse click on center
case 'origindown'
he=get(gco,'userdata');
ud=get(he,'userdata');
cbk=cbkstr(gco, 'originmove');
set(gco, 'linewidth', 3);
set (gcf, 'windowbuttonmotionfcn' , cbk);
cbk=cbkstr(gco, 'originup');
set (gcf, 'windowbuttonupfcn' , cbk, 'doublebuffer', 'on','userdata',ud);
%move origin
case 'originmove'
pt=get(gca, 'currentpoint');
pt=pt(1,1:2);
ud=get(gcf,'userdata');
ud.x0=pt(1);
ud.y0=pt(2);
[x,y]=ellipse(ud.a,ud.b,ud.x0,ud.y0,ud.phi);
set(ud.he,'xdata',x,'ydata',y);
set(ud.hc,'xdata',ud.x0,'ydata',ud.y0);
set(ud.ha,'xdata',ud.x0+cos(ud.phi)*ud.a,'ydata',ud.y0+ ...
sin(ud.phi)*ud.a);
set(ud.hb,'xdata',ud.x0-sin(ud.phi)*ud.b,'ydata',ud.y0+ ...
cos(ud.phi)*ud.b);
axis(ud.axis)
set(gcf, 'userdata',ud);
% Done move origin
case 'originup'
ud=get(gcf,'userdata');
set(ud.he,'userdata',ud);
set(gcf,'windowbuttonupfcn','', 'windowbuttonmotionfcn', '', 'doublebuffer', 'off');
set(gco, 'linewidth', 2);
% major axis operations
case 'majordown'
he=get(gco,'userdata');
ud=get(he,'userdata');
cbk=cbkstr(gco, 'majormove');
set(gco, 'linewidth', 3);
set (gcf, 'windowbuttonmotionfcn' , cbk);
cbk=cbkstr(gco, 'majorup');
set (gcf, 'windowbuttonupfcn' , cbk, 'doublebuffer', 'on','userdata',ud);
case 'majormove'
ud=get(gcf,'userdata');
pt=get(gca, 'currentpoint');
pt=pt(1,1:2);
%dist from center
ud.a=sqrt((ud.x0-pt(1))^2+(ud.y0-pt(2))^2);
[x,y]=ellipse(ud.a,ud.b,ud.x0,ud.y0,ud.phi);
set(ud.he,'xdata',x,'ydata',y);
set(ud.hc,'xdata',ud.x0,'ydata',ud.y0);
set(ud.ha,'xdata',ud.x0+cos(ud.phi)*ud.a,'ydata',ud.y0+ ...
sin(ud.phi)*ud.a);
set(ud.hb,'xdata',ud.x0-sin(ud.phi)*ud.b,'ydata',ud.y0+ ...
cos(ud.phi)*ud.b);
axis(ud.axis)
set(gcf, 'userdata',ud);
case 'majorup'
ud=get(gcf,'userdata');
set(ud.he,'userdata',ud);
set(gcf,'windowbuttonupfcn','', 'windowbuttonmotionfcn', '', 'doublebuffer', 'off');
set(gco, 'linewidth', 2);
%minor axis ops
case 'minordown'
he=get(gco,'userdata');
ud=get(he,'userdata');
cbk=cbkstr(gco, 'minormove');
set(gco, 'linewidth', 3);
set (gcf, 'windowbuttonmotionfcn' , cbk);
cbk=cbkstr(gco, 'majorup');
set (gcf, 'windowbuttonupfcn' , cbk, 'doublebuffer', 'on','userdata',ud);
case 'minormove'
ud=get(gcf,'userdata');
pt=get(gca, 'currentpoint');
pt=pt(1,1:2);
%dist from center
d=sqrt((ud.x0-pt(1))^2+(ud.y0-pt(2))^2);
ud.b=d;
[x,y]=ellipse(ud.a,ud.b,ud.x0,ud.y0,ud.phi);
set(ud.he,'xdata',x,'ydata',y);
set(ud.hc,'xdata',ud.x0,'ydata',ud.y0);
set(ud.ha,'xdata',ud.x0+cos(ud.phi)*ud.a,'ydata',ud.y0+ ...
sin(ud.phi)*ud.a);
set(ud.hb,'xdata',ud.x0-sin(ud.phi)*ud.b,'ydata',ud.y0+ ...
cos(ud.phi)*ud.b);
axis(ud.axis)
set(gcf, 'userdata',ud);
case 'minorup'
ud=get(gcf,'userdata');
set(ud.he,'userdata',ud);
set(gcf,'windowbuttonupfcn','', 'windowbuttonmotionfcn', '', 'doublebuffer', 'off');
set(gco, 'linewidth', 2);
% delete ellipse callback
case 'delete'
ud=get(gco,'userdata');
delete(ud.hc);
delete(ud.he);
delete (ud.ha);
delete (ud.hb);
hold off
% delete handle programmatically
case 'deletehandle'
if (ishandle(varargin{2}))
ud = get(varargin{2},'userdata');
delete(ud.hc);
delete(ud.he);
delete(ud.ha);
delete(ud.hb);
hold off;
he = 0;
else
he=-1;
end
%return ellipse parameters
case 'params'
if (ishandle(varargin{2}))
he = get(varargin{2},'userdata');
else
he = [];
end
otherwise
disp (action);
end
function s = cbkstr(hnd, action)
s=sprintf ('cbkswitch(''%s'' , ''%s'', %s)',mfilename, action, num2str(hnd));