Code covered by the BSD License  

Highlights from
snare

image thumbnail
from snare by James Roberts
perform basic manipulation on 2D subsets of a data set

snare(xin,yin,select_type)
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

Contact us at files@mathworks.com