Cris’ Image Analysis Blog

theory, methods, algorithms, applications

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].