Implementing the dip_snake class
In the previous post I showed how to implement active contours (a snake). I included the link to
a set of files with a complete snake implementation to plug into DIPimage
[Note: the code in the ZIP file requires DIPimage 2, and is not compatible with DIPimage 3].
This implementations uses a class called dip_snake
. In this post I wanted to show how this class is defined using
MATLAB’s new 1-file-style of class definitions. Well, “new” is not completely accurate, this feature has been around for
a few releases already, but I’m slow to adapt… You’ll also learn how to make a class whose objects automatically
create or update a figure window, much like an object of type dip_image
does.
Note that it’s not my intent to teach about object-oriented programming in general, just how to create a new class with MATLAB’s 1-file system. If you don’t know what a class is, or what methods are, I suggest you start elsewhere.
The following is our basic class definition file. The file is called dip_snake.m and should live in a directory on the path. Because this code was meant to be part of DIPimage, the dip/common/dipimage/ directory is a good choice.
%DIP_SNAKE Creates an object of class DIP_SNAKE
% <help text>
classdef dip_snake
properties
x = []
y = []
imsz = [];
end
methods
% DIP_SNAKE Constructor metod
function s = dip_snake(x)
if nargin~=0
x = double(x);
s.x = x(:,1);
s.y = x(:,2);
s.imsz = ceil(max(x)+1);
end
end
% More method definitions here...
end
end
Our class has 3 internal variables. x
and y
will contain the x and y coordinates for the control points of the
snake. This is really all we need. The imsz
variable will hold information on the size of the image that the snake was
trained on, and will be used when creating a binary image representing the segmentation defined by the snake. These
variables are initialized using an input argument to the constructor. Note that it is necessary to test for an empty
input in the constructor, since MATLAB will call the constructor without input arguments at times. Other than the
strictly necessary, I’ve left out all of the statements that test for error situations. These are not helpful in
understanding the principles.
We can now do
s = dip_snake(1:10,1:10);
to create a simple snake. Of course, the object doesn’t do anything yet! Next we will add several more methods. These
should go after the definition of the dip_snake
function, and before the final two end
statements. That is, they
should be between the methods
keyword and the corresponding end
keyword. We’ll start with a few useful methods that
every class should have: double
, size
, length
, subsref
and end
. Some of these functions might not be familiar
to MATLAB users that have never created their own class. subsref
is called when indexing into the object. For
example, s(1)
generates the call subsref(s,ind)
, with ind(1).type == '()'
and ind(1).subs == 1
. It can also
handle the dot operator .
and the brace indexing {}
. The end
method is used in indexing, and simply returns the
index of the last element.
% DOUBLE Overloaded method, returns coordinate array
function o = double(s)
o = [s.x,s.y];
end
% SIZE Overloaded method, returns size of snake
function l = size(s)
l = [length(s.x),2];
end
% LENGTH Overloaded method, returns length of snake
function l = length(s)
l = length(s.x);
end
% SUBSREF Overloaded method, allows indexing
function o = subsref(s,ii)
switch ii.type
case '()'
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)
ii = length(s.x);
end
I believe all that code is pretty straight-forward. We can now do things like
s.x
s(5:end)
length(s)
Now let’s look at the function disp
, another common method for objects. It is used to display the contents of a
variable, and we will overload it to plot the snake over an image. Again, this function is mainly straight MATLAB handle
graphics. If you are not familiar with handle graphics, all you need to know is that the code below takes a handle to a
figure window, an axes or an image object, finds the appropriate axes to draw in, then creates a line object in those
axes. The function line
is similar to plot
.
% DISP Plots the snake on top of an image
function oh = disp(s,h)
if nargin<2
h = gcf;
end
if ~strcmp(get(h,'type'),'image')
h = findobj(h,'type','image');
end
h = get(h,'parent'); % axes handle
lh = line([s.x;s.x(1)],[s.y;s.y(1)],'color',[0,0,0.8],...
'parent',h);
if nargout>0
oh = lh;
end
end
The function disp
allows us to actually look at the snake:
readim
disp(s)
But what about the automatic display I mentioned earlier? The one that dip_image
objects have? When we typed readim
above, the resulting image was automatically drawn to a figure window. This behavior is suppressed by adding a
semicolon (;
) to the command. When the semicolon is left off, MATLAB calls the display
method. If we overload that
method to call disp
, our display function will be automatically called for our objects!
% DISPLAY Overloaded method, calls DISP
function display(s)
h = disp(s);
h = get(get(h,'parent'),'parent');
disp(['Displayed in figure ',num2str(h)])
end
So now we don’t need to call the function disp
explicitly any more:
readim
s
Finally, we’ll create a method to convert the snake into a (binary) mask image. After fitting the snake to some edges in
the image, we will want to get the snake in a form that we can use for further processing. The dip_image
method convhull
stood as model for this code. We simply draw lines from one point to the next, and, because the snake
is assumed to be a closed contour, draw a line from the last point to the first. Next we use a propagation algorithm to
fill the image from the boundary inwards. The inverse of this is the area enclosed by the snake. We call this
function dip_image
. This is now an overloaded version of the constructor of the image class, which is the typical way
MATLAB uses to convert objects from one class to another. This is identical to the overloaded double
we created
earlier. dip_image(s)
will create an image with the information in the dip_snake
object s
just like dip_image(A)
creates an image with the information in the matrix A
, for example.
% DIP_IMAGE Overloaded method, returns binary image
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);
end
Note that the function bresenham2
would be defined as a sub-function in this same file. I haven’t included that code
here because this post is already too long as it is.
To see this class in action, look at the function snakeminimize
. You can get
it right here [Note: the code in the ZIP file requires DIPimage 2, and is
not compatible with DIPimage 3].