%DIP_SNAKE Creates an object of class DIP_SNAKE
% S = DIP_SNAKE(X) creats a DIP_SNAKE object from the
% Nx2 array X. X(ii,:) are the coordinates of control
% point ii. The snake S is always a closed contour.
% N must be at least 5!
%
% S, by itself, plots the contour over the current
% figure. DISP(S,H) displays the contour over the image
% in the figure with handle H. H defaults to GCF.
% If H is the handle to a line object, it updates the
% line rather than drawing a new one.
% H = DISP(S) returns the handle to the line object.
%
% S = RESAMPLE(S,D,MODE) re-samples the contour with a
% sample distance D. MODE can be 'linear' or 'cubic',
% corresponding to the interpolation methods used by
% INTERP1.
%
% S(ii) returns the coordinates for control point ii.
% S.X returns the x-coordinate for all control points.
% S.Y returns the y-coordinate for all control points.
%
% DIP_IMAGE(S) returns a binary image with the region
% demarkated by the snake.
%
% NOTE:
% This object requires at least MATLAB version 7.6!
% (C) Copyright 2009, All rights reserved.
% Cris Luengo, Uppsala, 18 September 2009.
classdef dip_snake
properties
x = []
y = []
imsz = [];
end
methods
% DIP_SNAKE Constructor metod
function s = dip_snake(x)
if nargin~=0
if ~isnumeric(x)
error('Numeric inputs expected.')
end
if size(x,2)~=2 || size(x,1) < 5
error('The snake must be initialized with a Nx2 array, N>=5.')
end
x = double(x);
s.x = x(:,1);
s.y = x(:,2);
s.imsz = ceil(max(x)+1);
end
end
% RESAMPLE Resamples the snake
function s = resample(s,d,mode)
if nargin<2
d = 1;
end
if nargin<3
mode = 'cubic';
else
mode = lower(mode);
if ~any(strcmp(mode,{'linear','cubic'}))
error('Illegal interpolation mode.');
end
end
if strcmp(mode,'linear')
r = 1; % for linear interpolation, replicate one value
else
r = 3; % for cubic spline interpolation, replicate 3 values
end
if length(s.x)<3
error('Snake has too few control points.')
end
x = s.x; x = [x(end-r+1:end);x;x(1:r)];
y = s.y; y = [y(end-r+1:end);y;y(1:r)];
% p is the parameter along the snake
p = [0;cumsum(sqrt( diff(x).^2 + diff(y).^2 ))];
% the first control point should be at p=0
p = p-p(r+1);
% resample snake between first and last+1 control points
x = interp1(p,x,0:d:p(end-r+1),mode);
y = interp1(p,y,0:d:p(end-r+1),mode);
% if the last new point is too close to the first one, remove it
if norm([x(end),y(end)]-[x(1),y(1)]) < d/2
x(end) = [];
y(end) = [];
end
% ensure column vectors
s.x = x(:);
s.y = y(:);
if length(s.x)<3
error('Snake has become too small!')
end
end
% DISP Plots the snake on top of an image
function oh = disp(s,h)
if nargin<2
h = gcf;
end
if ~ishandle(h)
error('Figure does not exist!')
end
if strcmp(get(h,'type'),'line')
lh = h;
set(lh,'xdata',[s.x;s.x(1)],'ydata',[s.y;s.y(1)]);
else
if ~strcmp(get(h,'type'),'image')
h = findobj(h,'type','image');
if length(h)~=1
error('Cannot find an image in the figure.')
end
end
h = get(h,'parent'); % axes handle
os = get(h,'nextplot');
set(h,'nextplot','add');
lh = line([s.x;s.x(1)],[s.y;s.y(1)],'color',[0,0.8,0],'parent',h,'linewidth',1);
set(h,'nextplot',os);
end
if nargout>0
oh = lh;
end
end
% DISPLAY Overloaded method, calls DISP
function display(s)
if strcmp(dipgetpref('DisplayToFigure'),'on')
h = disp(s);
h = get(get(h,'parent'),'parent');
disp(['Displayed in figure ',num2str(h)])
else
if isequal(get(0,'FormatSpacing'),'compact')
disp([inputname(1),' ='])
disp(['dip_snake object with ',num2str(length(s.x)),' points.'])
else
disp(' ')
disp([inputname(1),' ='])
disp(' ')
disp(['dip_snake object with ',num2str(length(s.x)),' points.'])
disp(' ')
end
end
end
% LENGTH Overloaded method, returns length of snake
function l = length(s)
l = length(s.x);
end
% SIZE Overloaded method, returns size of snake
function l = size(s)
l = [length(s.x),2];
end
% SUBSREF Overloaded method, allows indexing
function o = subsref(s,ii)
if length(ii)~=1
error('Illegal indexing into object of class dip_snake.')
end
switch ii.type
case '()'
if length(ii.subs)~=1
error('Illegal indexing.')
end
o = [s.x(ii.subs{:}),s.y(ii.subs{:})];
case '.'
switch ii.subs
case 'x'
o = s.x;
case 'y'
o = s.y;
case 'imsz'
o = s.imsz;
otherwise
error(['No appropriate method, property, or field ',ii.subs,' for class dip_snake.']);
end
otherwise
error('Illegal indexing into object of class dip_snake.')
end
end
% END Overloaded method, returns index of last point
function ii = end(s,k,n)
if k~=1 || n~=1
error('Illegal indexing.');
end
ii = length(s.x);
end
% DOUBLE Overloaded method, returns coordinate array
function o = double(s)
o = [s.x,s.y];
end
% DIP_IMAGE Overloaded method, returns binary image
% (based on stuff in @dip_image/convhull.m)
function o = dip_image(s)
o = newim(s.imsz,'bin');
strides = [s.imsz(2);1];
indx = bresenham2([s.x(end),s.y(end)],[s.x(1),s.y(1)])*strides;
for ii = 2:length(s)
indx = [indx;bresenham2([s.x(ii-1),s.y(ii-1)],[s.x(ii),s.y(ii)])*strides];
end
indx = unique(indx);
o(indx) = 1;
o = ~dip_binarypropagation(o&0,~o,1,0,1); % Propagation from border, low connect.
end
end
end
%
% computes coordinates of points along line from pt1 to pt2
% (copied from @dip_image/convhull.m)
function line = bresenham2(pt1,pt2)
point = pt2-pt1;
N = max(abs(point)); % the number of pixels needed.
if N==0
line = pt1;
return;
end
ii = (0:N)'; % this is better than (0:N)/N, because of round-off errors.
x = ii*(point(1)/N)+pt1(1);
y = ii*(point(2)/N)+pt1(2);
line = round([x,y]);
end