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. 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.
RSS: