function [xout,yout,indices] = snare(xin,yin,select_type)
% snare - perform basic manipulation on 2D subsets of a data set
%
% [xout,yout] = snare(xin,yin,select_type)
%
% Given a vector of x- and y-coordinates of a data set, allows the user to
% select a subset of the data with the mouse and change the coordinates of
% that subset much as he/she moves icons around a desktop. There are three
% modes of selecting the data - 'lasso-ing' the data much like in MS Paint,
% a rectangular selection much like the desktop icon selection function,
% and by adding points to a path surrounding the data one point at a time.
% Snare.m also returns the indices of the data set that are selected last.
%
% Once the user selects a subset of points (methods described in detail
% below), he/she may press the left mouse button anywhere within the path
% and drag the points to a new location. The relative location of the
% points selected remains constant.
%
% Snare.m automatically pulls up a new window displaying the data and all
% operations to select and manipulate the data are performed with the
% mouse. The function is exited by closing the figure window. The indices
% returned are the indices of the last set of data to be selected.
%
% Inputs:
% xin - the x-coordinates of the data set to be manipulated
% yin - the y-coordinates of the data set to be manipulated
% select_type - a flag detailing the type of selection to be used
% 0 - lasso-type selection. The user holds the left mouse button and
% traces the path around the subset. When the button is lifted, the
% path is completed to the beginning point.
% 1 - rectangular-type selection. The user holds the left mouse
% button and selects the data by drawing a rectangle around the
% subset. The original button-press point and the current mouse-point
% are opposite corners of the rectangle.
% 2 - single-point selection. The user draws a path around the subset
% by adding a single point to the path at a time by pressing the left
% mouse button. When all path-points are added, the user presses the
% right mouse-button to complete the path.
%
% Outputs:
% xout - the x-coordinates of the data set after the user is finished
% yout - the y-coordinates of the data set after the user is finished
% indices - the last-selected subset of data
%
%
% James Roberts
% jamescroberts@gmail.com
% 2009/10/13
global snare_struct
% Check inputs
if nargin > 3
% Too many inputs
clear global snare_struct
error('Too many inputs');
elseif nargin < 2
% Too few inputs
clear global snare_struct
error('Too few inputs');
else
if nargin == 3
if isnan(select_type) || select_type > 2 || select_type < 0
error('Invalid input for the selection method');
else
% 0 - draw an arbitrary shape.
% 1 - rectangle select.
% 2 - click-to-add
snare_struct.select_type = select_type;
end
else
snare_struct.select_type = select_type;
end
if size(xin,2) > size(xin,1), xin = xin'; end
if size(yin,2) > size(yin,1), yin = yin'; end
if isempty(xin) || isempty(yin) || ...
size(xin,2) > 1 || size(yin,2) > 1 || ...
size(xin,1) ~= size(yin,1)
clear global snare_struct
error('Invalid data inputs. Data must be vectors of equal length');
else
snare_struct.data = [xin, yin];
end
end
% set the snare_struct structure variables
snare_struct.mouseFlag = 0; % type of mouse click.
% 0 - no drawing will occur
% 1 - drawing of a boundary line will occur
% 2 - data points wihtin boundary will be moved
snare_struct.clickFlag = 0; % 0 after left click, 1 after right click
snare_struct.hull = []; % array of xy-points of path around data
snare_struct.indices = []; % indices of the data that are surrounded
snare_struct.diffData = []; % xy-array of difference b/t mouse click and subset
snare_struct.diffHull = []; % xy-array of difference b/t mouse click and hull
% Set the figure and axis properties
handles.figure1 = figure;
handles.axes1 = axes;
set(handles.figure1,...
'Units','Normalized',...
'NumberTitle','off',...
'WindowButtonDownFcn',{@DownFcn,handles},...
'WindowButtonUpFcn',{@UpFcn,handles},...
'WindowButtonMotionFcn',{@MotionFcn,handles});
set(handles.axes1,...
'NextPlot','add',...
'Units','Normalized');
% Plot some random snare_struct.data
snare_struct.plots.data = plot(snare_struct.data(:,1),snare_struct.data(:,2),'r.');
snare_struct.plots.hull = plot(0,0,'b-');
set(snare_struct.plots.hull,'Visible','off');
% Wait so the user can make his selections and continue when the figure is
% closed
uiwait(handles.figure1)
% get the final data for output and clean up the global variables
xout = snare_struct.data(:,1);
yout = snare_struct.data(:,2);
if isempty(snare_struct.hull)
indices = [];
else
indices = find(inpolygon(xout,yout,snare_struct.hull(:,1),snare_struct.hull(:,2)));
end
% Clear the global variables
clear global snare_struct
function DownFcn(hObject,event_data,handles)
global snare_struct
% Gather data from the axes and determine where the mouse click occured
set(snare_struct.plots.hull,'Visible','on');
xLims = get(handles.axes1,'XLim');
yLims = get(handles.axes1,'YLim');
axesSize = get(handles.axes1,'Position');
mousePos = get(handles.figure1,'CurrentPoint');
xPercent = (mousePos(1) - axesSize(1)) / axesSize(3);
yPercent = (mousePos(2) - axesSize(2)) / axesSize(4);
if xPercent >= 0 && xPercent <= 1 && yPercent >= 0 && yPercent <= 1
% if click was within the axes, calc the coords of it
xClick = xPercent*(xLims(2)-xLims(1)) + xLims(1);
yClick = yPercent*(yLims(2)-yLims(1)) + yLims(1);
if ~isempty(snare_struct.hull) && ...
strcmp(get(gcf,'SelectionType'),'normal') && ...
inpolygon(xClick,yClick,snare_struct.hull(:,1),snare_struct.hull(:,2))
% User is moving a subset of the data by clicking and dragging
snare_struct.mouseFlag = 2;
snare_struct.diffData = snare_struct.data(snare_struct.indices,:) - ...
repmat([xClick,yClick],sum(snare_struct.indices),1);
snare_struct.diffHull = snare_struct.hull - ...
repmat([xClick,yClick],size(snare_struct.hull,1),1);
elseif snare_struct.select_type == 2
% adding points one by one to the area lasso
if snare_struct.clickFlag
% last click was a right-click so clear the previous hull
snare_struct.hull = [xClick,yClick];
set(snare_struct.plots.hull,...
'XData',snare_struct.hull(:,1),...
'YData',snare_struct.hull(:,2));
end
if strcmp(get(gcf,'SelectionType'),'normal')
% add another point to the single-click path
snare_struct.clickFlag = 0;
snare_struct.indices = [];
snare_struct.mouseFlag = 1;
snare_struct.hull = [snare_struct.hull; xClick,yClick];
set(snare_struct.plots.hull,...
'XData',snare_struct.hull(:,1),...
'YData',snare_struct.hull(:,2));
elseif ~isempty(snare_struct.hull)
% complete the single-click path
snare_struct.clickFlag = 1;
snare_struct.mouseFlag = 0;
snare_struct.hull(end+1,:) = snare_struct.hull(1,:);
snare_struct.indices = inpolygon(snare_struct.data(:,1),...
snare_struct.data(:,2),snare_struct.hull(:,1),snare_struct.hull(:,2));
set(snare_struct.plots.hull,...
'XData',snare_struct.hull(:,1),...
'YData',snare_struct.hull(:,2));
end
elseif strcmp(get(gcf,'SelectionType'),'normal')
% User wants to draw a new box with either the rectangle or lasso
snare_struct.indices = [];
snare_struct.mouseFlag = 1;
snare_struct.hull = [xClick,yClick];
set(snare_struct.plots.hull,...
'XData',snare_struct.hull(:,1),...
'YData',snare_struct.hull(:,2));
end
end
function UpFcn(hObject,event_data,handles)
global snare_struct
if snare_struct.mouseFlag == 1
% finish the box that's being drawn
if snare_struct.select_type
% rectangular selection
snare_struct.hull = [get(snare_struct.plots.hull,'XData')',...
get(snare_struct.plots.hull,'YData')'];
else
% lasso selection. close off the loop
snare_struct.hull(end+1,:) = snare_struct.hull(1,:);
set(snare_struct.plots.hull,...
'XData',snare_struct.hull(:,1),...
'YData',snare_struct.hull(:,2));
end
% find the indices of the points that are within the path
snare_struct.indices = inpolygon(snare_struct.data(:,1),...
snare_struct.data(:,2),snare_struct.hull(:,1),snare_struct.hull(:,2));
end
snare_struct.mouseFlag = 0;
function MotionFcn(hObject,event_data,handles)
global snare_struct
% Gather data from the axes and determine where the mouse is currently
guidata(handles.figure1,handles);
xLims = get(handles.axes1,'XLim');
yLims = get(handles.axes1,'YLim');
axesSize = get(handles.axes1,'Position');
mousePos = get(handles.figure1,'CurrentPoint');
xPercent = (mousePos(1) - axesSize(1)) / axesSize(3);
yPercent = (mousePos(2) - axesSize(2)) / axesSize(4);
% If the mouse is within the axes, continue
if xPercent >= 0 && xPercent <= 1 && yPercent >= 0 && yPercent <= 1
% Put the mouse click into the coords of the axes
xClick = xPercent*(xLims(2)-xLims(1)) + xLims(1);
yClick = yPercent*(yLims(2)-yLims(1)) + yLims(1);
if snare_struct.mouseFlag == 1 && snare_struct.select_type ~= 2
if snare_struct.select_type
tmp = [snare_struct.hull; ...
xClick, snare_struct.hull(2);...
xClick, yClick;...
snare_struct.hull(1), yClick;...
snare_struct.hull];
set(snare_struct.plots.hull,...
'XData',tmp(:,1),...
'YData',tmp(:,2));
else
% drawing a lasso path
snare_struct.hull = [snare_struct.hull; xClick,yClick];
set(snare_struct.plots.hull,...
'XData',snare_struct.hull(:,1),...
'YData',snare_struct.hull(:,2));
end
elseif snare_struct.mouseFlag == 2
% picking up and moving data points
snare_struct.data(snare_struct.indices,1) = xClick + snare_struct.diffData(:,1);
snare_struct.data(snare_struct.indices,2) = yClick + snare_struct.diffData(:,2);
set(snare_struct.plots.data,...
'XData',snare_struct.data(:,1),...
'YData',snare_struct.data(:,2));
snare_struct.hull(:,1) = xClick + snare_struct.diffHull(:,1);
snare_struct.hull(:,2) = yClick + snare_struct.diffHull(:,2);
set(snare_struct.plots.hull,...
'XData',snare_struct.hull(:,1),...
'YData',snare_struct.hull(:,2));
end
end