An Introduction To Programming For Medical Image Analysis With The Visualization Toolkit Version 2 (Updated For VTK 5.2)
An Introduction To Programming For Medical Image Analysis With The Visualization Toolkit Version 2 (Updated For VTK 5.2)
, T
2
: x
y, or y = T
2
(T
1
(x)) (13.2)
This style of concatenation is termed post-multiply in VTK because of the order in which, in the case of linear
transformations, the transformation matrices are multiplied. In the two classes which allow for concatenation of
multiple transformations, namely vtkTransform and vtkGeneralTransform, the default order of concatenation is
pre-multiply. In my own work, I have never found this to be useful, and I switch these to post-multiply mode
using their PostMultiply methods.
13.2 Homogeneous Linear Transformations
Representation: Linear transformations in VTK are represented internally as 44 matrices. This enables the
use of a single operation to capture both a translation as well as a combination of rotation/shear/scale. Ordinarily,
we would write such a transformation in two parts as:
y = Ax +b (13.3)
where A is a 3 3 matrix that performs a combinations of rotation, scale and shear and b is a 3 1 vector
specifying the translation. A more compact representation is to use homogeneous coordinates. To accomplish
104
CHAPTER 13. TRANSFORMING SURFACES AND RESLICING IMAGES Draft Sep. 2009
this, we write each point as a 4-vector (x
1
, x
2
, x
3
, 1), and apply the transformation as follows:
_
_
y
1
y
2
y
3
1
_
_
=
_
_
A
11
A
12
A
13
b
1
A
21
A
22
A
13
b
2
A
31
A
32
A
13
b
3
0 0 0 1
_
_
x
1
x
2
x
3
1
_
_
(13.4)
This method can be used to transform all linear transformations into linear algebra operations on 4 4 matrices.
This enables easy concatenation (matrix multiplication) and inversion (matrix inversion). Note also that a linear
transformation can have at most 12-free parameters. There are 3 general types of linear transformations as follows:
1. Rigid these have six parameters (3 rotations and 3 translations)
2. Similarity these have seven parameters, rigid + overall scale factor
3. Ane this is the general linear transformation group and has 12 parameters.
The vtkMatrix4x4 Class: The class hierarchy for all linear transformations is shown in Figure 13.2. A key
helper class is the vtkMatrix4x4 class which is used to store the 4 4 matrices. This class has a number of
methods, the most important of which are:
1. void SetElement (int i, int j, double value)
2. double GetElement (int i, int j) const
3. void Zero ()
4. void Identity ()
5. void DeepCopy (vtkMatrix4x4 *source)
6. double Determinant ()
7. void MultiplyPoint (const oat in[4], oat out[4])
An example of directly using vtkMatrix4x4 is presented in the script below (script13-1.tcl)
wm withdraw .
set mat [ vtkMatrix4x4 New ]
$mat Identity
$mat SetElement 0 0 0
$mat SetElement 0 1 1.0
$mat SetElement 1 1 0.0
$mat SetElement 1 0 1.0
for { set i 0 } { $i <= 3 } { incr i } {
puts -nonewline stdout "\["
for { set j 0 } { $j <= 3 } { incr j } {
puts -nonewline stdout "[ $mat GetElement $i $j] "
}
puts stdout "\]"
}
This script creates the matrix, modies a small part and prints its contents.
The vtkTransform Class: This complex class has a variety of functionality for implementing linear trans-
formations. It has a vtkMatrix4x4 member which stores the current matrix, and allows for the concatenation of
various operations, either in pre-multiply or post-multiply order. The most important of its methods are:
105
CHAPTER 13. TRANSFORMING SURFACES AND RESLICING IMAGES Draft Sep. 2009
void Identity ()
void PostMultiply ()
void Inverse ()
vtkLinearTransform * GetLinearInverse ()
void SetMatrix (vtkMatrix4x4 *matrix)
void Concatenate (vtkMatrix4x4 *matrix)
void Concatenate (vtkLinearTransform *transform)
void Translate (double x, double y, double z)
void RotateWXYZ (double angle, double x, double y, double z)
void RotateX (double angle)
void RotateY (double angle)
void RotateZ (double angle)
void Scale (double x, double y, double z)
Typically, when using vtkTransform, we rst set it to Identity and then invoke the PostMultiply method. Then a
series of transformations can be concatenated to yield a single compound linear transformations. This concate-
nations can be either explicit (using the Concatenate methods) or implicit (using the Rotate, Translate and Scale
methods). An example is shown in the script below (script13-2.tcl):
set tr [ vtkTransform New ]
$tr Identity
$tr PostMultiply
$tr Scale 2.0 2.0 1.0
$tr Translate 1.0 0.0 -3
set out [ $tr TransformPoint 4 2 1 ]
puts stdout "(4 2 1) --> ($out)"
The TransformPoint method is the most basic method of all transformation classes. It is originally dened in
vtkAbstactTransform and overridden as needed by derived classes.
The vtkLandmarkTransform class: This extremely useful class contains functionality for the least squares
estimation of a linear transformation from two sets of corresponding points. Given two point sets, each having n
points: X = (x
1
, x
2
, . . . , x
n
) and Y = (y
1
, y
2
, . . . , y
n
) the operation implemented by this class can be written
mathematically as:
T =
arg min
T
n
i=1
[x
i
y
i
[
2
(13.5)
where T is a linear transformation. The key methods of vtkLandmarkTransform are:
void SetSourceLandmarks (vtkPoints *points)
void SetTargetLandmarks (vtkPoints *points)
void SetModeToRigidBody ()
void SetModeToSimilarity ()
void SetModeToAne ()
The last three specify the exact form of T which can be restricted to be rigid, similarity or the full ane
transformation. Its usage is illustrated in the following script (script13-3.tcl). First we dene the two sets of
points:
set pts1 [ vtkPoints New ]
106
CHAPTER 13. TRANSFORMING SURFACES AND RESLICING IMAGES Draft Sep. 2009
$pts1 SetNumberOfPoints 5
$pts1 SetPoint 0 0.0 0.0 0.0; $pts1 SetPoint 1 1.0 0.0 0.0
$pts1 SetPoint 2 0.0 1.0 0.0; $pts1 SetPoint 3 0.0 0.0 1.0
$pts1 SetPoint 4 1.0 1.0 1.0
set pts2 [ vtkPoints New ]
$pts2 SetNumberOfPoints 5
$pts2 SetPoint 0 0.0 0.1 0.0; $pts2 SetPoint 1 2.0 0.1 0.0
$pts2 SetPoint 2 0.0 1.1 0.0; $pts2 SetPoint 3 0.0 0.1 3.0
$pts2 SetPoint 4 2.0 1.1 3.0
Next we create a vtkLandmarkTransform object, set the two point sets and the transformation type and invoke
the Update method to compute the transformation:
set land [ vtkLandmarkTransform New ]
$land SetSourceLandmarks $pts1
$land SetTargetLandmarks $pts2
$land SetModeToAffine
$land Update
Once this is done, we print the output, as before:
set mat [ $land GetMatrix ]
puts stderr "Fitting Output is:"
for { set i 0 } { $i <= 3 } { incr i } {
puts -nonewline stdout "\["
for { set j 0 } { $j <= 3 } { incr j } {
puts -nonewline stdout "[ format "%+5.2f" [ $mat GetElement $i $j]] "
}
puts stdout "\]"
}
13.3 Non-Linear Transformations
Non-linear transformations are loosely dened as those transformations which can not be expressed as a 4 4
matrix. The family of nonlinear transformations dened in VTK is shown in Figure 13.3. The most useful ones
are vtkGridTransform and vtkThinPlateSplineTransform.
vtkThinPlateSplineTransform: Kernel-based transformations, such as the thin-plate spline transform, are
based on (i) two sets of corresponding points (landmarks) which the transformation maps exactly, and (ii) an
interpolation rule (kernel) which is used to sensibly interpolate between these landmark points. The thin-plate
spline transform, popularized by Bookstein in the early 90s, is one of the most common forms of this type of
transformation. Its most useful methods are:
void SetBasisToR ()
107
CHAPTER 13. TRANSFORMING SURFACES AND RESLICING IMAGES Draft Sep. 2009
void SetBasisToR2LogR ()
void SetSourceLandmarks (vtkPoints *source)
void SetTargetLandmarks (vtkPoints *target)
The general usage the syntax is very similar to vtkLandmarkTransform takes the form:
set tps [ vtkThinPlateSplineTransform New ]
$tps SetSourceLandmarks $pts1
$tps SetTargetLandmarks $pts2
$tps SetBasisToR
$tps Update
For 2D transformations use the SetBasisToR2LogR method instead, to select the appropriate basis function
(kernel).
vtkGridTransform: The most general type of non-linear transformation is one explicitly specied by a dense
displacement eld. VTK provides the vtkGridTransform class for this purpose. Its key methods are:
virtual void SetDisplacementGrid (vtkImageData *)
virtual void SetDisplacementScale (double)
virtual void SetDisplacementShift (double)
void SetInterpolationModeToNearestNeighbor ()
void SetInterpolationModeToLinear ()
void SetInterpolationModeToCubic ()
The displacements are specied on a grid stored in a vtkImageData structure this must have three components
(frames) which store the displacements in x, y, and z respectively. These displacements can be, optionally, scaled by
a scale factor (specied using SetDisplacementScale) and shifted by a translation term (SetDisplacementShift).
The only additional parameter is the interpolation mode which species how this displacement eld is to be
interpreted. (An internal extension also provides for a B-Spline tensor grid interpolation method). The following
snippet describes the basic usage:
set img [ vtkImageData New ]
$img SetDimensions 10 10 10
$img SetOrigin 0.0 0.0 0.0
$img SetSpacing 10.0 10.0 10.0
$img SetNumberOfScalarComponents 3
$img SetScalarTypeToDouble
$img AllocateScalars
set data [ [ $img GetPointData ] GetScalars ]
$data FillComponent 0 0.0
$data FillComponent 1 0.0
$data FillComponent 2 0.0
# Set some displacements much like image intensities
# Create Grid
set grid [ vtkGridTransform New ]
$grid SetInterpolationModeToLinear
108
CHAPTER 13. TRANSFORMING SURFACES AND RESLICING IMAGES Draft Sep. 2009
$grid SetDisplacementGrid $img
$img Delete
The image is created and manipulated as usual. Each voxel contains three components (its x,y and z displacement).
Next, the grid transform is created and the displacement eld image is attached to it.
13.4 The vtkGeneralTransform
The vtkGeneralTransform class allows for the concatenation of a number of transformations into a single transfor-
mation. These transformations (unlike similar functionality in vtkTransform) can be both linear and/or non-linear.
The syntax is very simple as illustrated in the following code snippet:
set gen [ vtkGeneralTransform New ]
$gen PostMultiply
$gen Concatenate $t1
$gen Concatenate $t2
$gen Concatenate $t3
where $t1,$t2 and $t3 are existing transformations.
13.5 Transforming Surfaces
Surfaces are most easily transformed using the vtkPolyDataFilter. A short code snippet illustrates its use:
set tr [ vtkTransform New ]
$tr Translate 10 5 0
set tf vtkTransformPolyDataFilter New ]
$tf SetInput $poly
$tf SetTransform $tr
$tf Update
set output [ vtkPolyData New ]
$output ShallowCopy [ $tf GetOutput ]
$tf Delete
The vtkTransformPolyDataFilter class takes two inputs: (i) an input surface and (ii) a transformation. In this
case, the script assumes an input surface stored in the variable $poly. While a linear transformation is created,
a non-linear transformation can also be specied. The output is also of type vtkPolyData. The lter essentially
transforms the points of the input surface one-by-one and stores the result in the output surface.
109
CHAPTER 13. TRANSFORMING SURFACES AND RESLICING IMAGES Draft Sep. 2009
13.6 Reslicing Images
Reslicing images is at the heart of most image registration procedures. While transforming surfaces is intuitive,
and can be summarized in the three steps (i) take point, (ii) transform point and (iii) store point, image reslicing
is somewhat counter-intuitive.
We will explain the process with reference to gure 13.4. In a typical registration process we have a Reference
image and a Target image. The registration estimates the transformation FROM the Reference image TO the
target image. This transformation can then be used in an image-reslicing operation to warp the Target image
BACK to the Reference image, i.e. make the target look like the reference. In this way, while the transformation
is forward the image moves BACKWARDS.
The process can be described by the following recipe, once the transformation exists:
Create an empty image (often having the same geometry as a Reference Image).
For each point (voxel) r in the empty image:
1. Compute its corresponding point in the Target image r
, T : r r
.
2. Interpolate the target image to nd the image intensity I at position r
n
i=1
[T(x
i
) c
k
i
[
2
(20.2)
In the correspondence estimation step, we take each point x
i
X, and transform it using the current estimate
of the transformation T
k
. Then we look for the point in Y that is closest to T
k
(x
i
). We label this point as the
corresponding point, at iteration k, c
k
i
. This results in a sets of pairs of correspoding points (x
i
, c
k
i
).
In the transformation estimation step, we look for the transformation T that best explains (or describes) this set
of correspondences. T can be either a linear or a non-linear transformation, we will focus on linear transformations
in this chapter.
Once a better version of T is estimated we can use it to estimate a better set of correspondences and so on
until convergence. Note that ICP needs to be initialized fairly close to the true transformation for convergence.
Alternative methods, e.g. the Robust Point Matching method [?] have superior capture range and accuracy.
However, ICP is a simple algorithm which works OK and is a great classroom example.
202
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
20.2 Implementation Design
While there is already an existing implementation of ICP in VTK 4.4 vtkIterativeClosestPointTransform
from which some of the code used in our design is based, we will describe a dierent approach here. The goal
of this approach, is to develop both a generic framework for point-based registration methods as well as an
implementation for ICP itself. This is a useful exercise as it demonstrates how object-oriented methodology can
be used to implement a family of algorithms in a clean way.
Generic vs Specic Functionality: In designing our implementation, we can list the following methods and
variables that need to be specied/implemented:
1. Source and Target Data sets
2. Parameters such as the maximum number of iterations, convergence threshold, maximum number of points.
3. Methods for invoking the algorithm and returning the output transformation in an algorithm-independent
way (so the rest of the program can use dierent algorithms with no major changes.)
4. Utility methods for sampling a data-set (to reduce the number of points), estimating the centroid of a data
set and translating points.
5. Parameters specic to the Linear ICP algorithm, such as the exact type of the transformation (rigid,
similarity or ane), the initial step (matching centroids).
6. The method for the main loop of the ICP algorithm.
If we look at this list, one way to break it up is that the rst four items refer to functionality that is generally
useful for point-based registrations, whereas the last two describe linear-ICP specic functionality. A good way to
break up the design, is then to implement rst an abstract class that incorporates items 14 and then derive from
it an ICP implementation that denes 56 and overrides the methods in 3. We discuss both of these class next:
The abstract parent class vtkAbstractPointBasedRegistration
Formally speaking, an abstract class in C++, is one which can never be instantiated. It is simply there as a place
to derive functional classes from. Abstract classes are extremely useful for dening a common interface to more
concrete children classes, such that other parts of the program may interact with any number of these derived
classes using a similar interface.
The Interface: The interface le (vtkAbstractPointBasedRegistration.h) has the following form. We derive
this class from vtkProcessObject which is a fairly lightweight VTK object
1
that does not impose too many
constraints.
class vtkAbstractPointBasedRegistration : public vtkProcessObject
{
public:
vtkTypeRevisionMacro(vtkAbstractPointBasedRegistration,vtkProcessObject);
void PrintSelf(ostream& os, vtkIndent indent);
Next comes the specication of the two data-sets, the source dataset (X) and the target data-set (Y):
1
Perhaps such a class could be also be derived from the new vtkAlgorithm class.
203
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
vtkGetObjectMacro(Source, vtkDataSet);
vtkGetObjectMacro(Target, vtkDataSet);
vtkSetObjectMacro(Source, vtkDataSet);
vtkSetObjectMacro(Target, vtkDataSet);
The GetObjectMacro simply creates functions of the form:
vtkDataSet* GetSource() { return this->Source; }
The SetObjectMacro is more interesting. In full this generates a function (we abbreviate somewhat here) that
takes the form:
virtual void SetSource(vtkDataSet* ds) {
if (this->Source!=NULL)
this->Source->UnRegister(this);
this->Source = ds;
if (this->Source != NULL)
this->Source->Register(this);
this->Modified();
}
The Register/UnRegister pair increment and decrement, respectively, the reference count on the objects. In
setting the new source object, we rst decrement the reference count of the previous object used as source. Next
we set the new source object and and then increment its reference counter!
Next come a set of methods for setting and getting the value of various parameters:
vtkSetMacro(MaximumNumberOfIterations, int);
vtkGetMacro(MaximumNumberOfIterations, int);
vtkSetMacro(MaximumNumberOfPoints, int);
vtkGetMacro(MaximumNumberOfPoints, int);
vtkSetMacro(Epsilon, double);
vtkGetMacro(Epsilon, double);
Finally, two extremely important functions, the GetTransformation() and the Run() methods. These are
dened as pure virtual methods (signied by the =0;) at the end, which means two things: (i) this class can
not be instatiated and (ii) any derived class from this class must dene these methods in order to be able to be
instantiated. This is a formal way, in C++, of dening a pure interface for derived classes to implement:
// Get the output transformation. This is purposefully an abstract
// transformation so That derived classes can return different types of transformations
virtual vtkAbstractTransform* GetTransformation() = 0;
// The Run Method Computes the Registration
virtual int Run() = 0;
204
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
The rest of the header is fairly trivial:
protected:
vtkAbstractPointBasedRegistration();
virtual ~vtkAbstractPointBasedRegistration();
// Data Members
vtkDataSet* Source,*Target;
int MaximumNumberOfIterations, MaximumNumberOfPoints;
double Epsilon;
// Utility Method
virtual vtkPoints* SampleDataSet(vtkDataSet* input,int NumberOfPoints);
virtual void GetCentroid(vtkDataSet* input,double centroid[3]);
virtual void ShiftPoints(vtkPoints* input,double shift[3],double scale=1.0);
private:
vtkAbstractPointBasedRegistration(const vtkAbstractPointBasedRegistration&);
void operator=(const vtkAbstractPointBasedRegistration&);
};
The Implementation: We next highlight some key points in the implementation (vtkAbstratPointBasedReg-
istration.cpp). First the constructor, which sets the default values for all the parameters (including the critical
NULL settings for all pointer input variables.)
vtkAbstractPointBasedRegistration::vtkAbstractPointBasedRegistration() : vtkProcessObject()
{
this->Source = NULL; this->Target = NULL;
this->MaximumNumberOfIterations = 50;
this->MaximumNumberOfPoints = 200; this->Epsilon=0.001;
}
The destructor simply sets the input pointers to NULL, this is a way of decrementing the reference count of any
objects used as a Source and Target up to this point:
vtkAbstractPointBasedRegistration::~vtkAbstractPointBasedRegistration() {
this->SetSource(NULL); this->SetTarget(NULL);
}
The rest of the code is fairly straightforward and should be easy to follow.
The concrete derived class vtkLinearICPRegistration:
This class implements a form of the ICP algorithm that estimates a linear transformation.
The Interface: The header le (vtkLinearICPRegistration.h) has the form. The rst part is straight-forward:
205
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
class vtkLinearICPRegistration : public vtkAbstractPointBasedRegistration
{
public:
static vtkLinearICPRegistration *New();
vtkTypeRevisionMacro(vtkLinearICPRegistration,vtkAbstractPointBasedRegistration);
void PrintSelf(ostream& os, vtkIndent indent);
// Starts the process by translating source centroid to target centroid.
vtkSetMacro(StartByMatchingCentroids, int);
vtkGetMacro(StartByMatchingCentroids, int);
vtkBooleanMacro(StartByMatchingCentroids, int);
The specication of the transformation type is marginally more interesting, in that we dene three more conve-
nience methods. These are useful for making the code more readable to a human!
// Transformation Type, Rigid, Similarity, Affine
vtkGetMacro(TransformationType,int);
vtkSetClampMacro(TransformationType,int,0,2);
// Three convenience methods
virtual void SetTransformationTypeToRigid() { this->SetTransformationType(0);}
virtual void SetTransformationTypeToSimilarity() { this->SetTransformationType(1);}
virtual void SetTransformationTypeToAffine() { this->SetTransformationType(2);}
Next we explicitly override the two pure virtual methods; this is the heart of the new functionality.
virtual vtkAbstractTransform* GetTransformation();
virtual int Run();
The rest of the denition is fairly straight-forward. Our algorithm will store the transformation in an instance of
vtkTransform
protected:
vtkLinearICPRegistration();
virtual ~vtkLinearICPRegistration();
// Data Members
int StartByMatchingCentroids, TransformationType;
vtkTransform *OutputTransformation;
private:
vtkLinearICPRegistration(const vtkLinearICPRegistration&); // Not implemented.
void operator=(const vtkLinearICPRegistration&); // Not implemented.
};
206
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
The Implementation: First the constructor/destructor pair. There is one pointer data member that is
allocated in the constructor and deleted in the destructor. (This symmetry is a useful check, make sure that
anything created in the constructor is deleted in the destructor!)
vtkLinearICPRegistration::vtkLinearICPRegistration() : vtkAbstractPointBasedRegistration(){
this->StartByMatchingCentroids=1; this->TransformationType=2;
this->OutputTransformation=vtkTransform::New();
}
vtkLinearICPRegistration::~vtkLinearICPRegistration(){
this->OutputTransformation->Delete();
}
Next we dene one of the two originally pure-virtual methods, the GetTransformation method. We simply return
the member variable OutputTransformation. Here, we have an example of what is known in C++ as polymorphism.
The returned variable (OutputTransformation) is not an instance of vtkAbstractTransform, but an instance of
vtkTransform which is a descendent of vtkAbstractTransform. You can always safely return an instance of derived
class in the place of an instance of the specied class. (Naturally the reverse is not possible, one cannot return a
vtkAbstractTransform when a vtkTransform is requested!)
vtkAbstractTransform* vtkLinearICPRegistration::GetTransformation() {
return this->OutputTransformation;
}
The heart of the implementation is the Run() method. We will omit some of the code here. We will use
the notation // OMITTED: code to do something where this takes place. First we get our act together by
checking that the input data sets exist. Then we initialize two key objects: (i) the Locator object for quickly
nding nearest points and (ii) the LandmarkTransform for estimating transformations from sets of corresponding
points. Next we get the OutputTransformation in shape (note the PostMultiply call!) and sample the input
data-set to reduce the number of points for computational reasons (The SampledSourcePoints are the xs of
the equations). Finally, we allocate the CorrespondingPoints (the cs of the equations):
int vtkLinearICPRegistration::Run() {
// OMITTED: Code to check that inputs are OK
// Create locator
vtkPointLocator* Locator = vtkPointLocator::New();
Locator->SetDataSet(this->Target); Locator->BuildLocator();
// Get The Landmark Transform All Set
vtkLandmarkTransform* LandmarkTransform=vtkLandmarkTransform::New();
switch(this->TransformationType) {
case 0: LandmarkTransform->SetModeToRigidBody(); break;
case 1: LandmarkTransform->SetModeToSimilarity(); break;
case 2: LandmarkTransform->SetModeToAffine(); break;
}
this->OutputTransformation->Identity(); this->OutputTransformation->PostMultiply();
// Get The Point Sets Ready
207
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
vtkPoints* SampledSourcePoints=this->SampleDataSet(this->Source,
this->MaximumNumberOfPoints);
vtkPoints* CorrespondingPoints=vtkPoints::New();
CorrespondingPoints->SetNumberOfPoints(SampledSourcePoints->GetNumberOfPoints());
The next step performs some initial alignment by matching the centroids, this can oftentimes be helpful as a
crude estimate of the overall translation:
double offset[3] = { 0.0,0.0,0.0};
if (this->StartByMatchingCentroids) {
double source_centroid[3]; this->GetCentroid(this->Source,source_centroid);
double target_centroid[3]; this->GetCentroid(this->Target,target_centroid);
for (int ia=0;ia<=2;ia++)
offset[ia]=target_centroid[ia]-source_centroid[ia];
this->ShiftPoints(SampledSourcePoints,offset,1.0);
}
int NumberOfLandmarks=SampledSourcePoints->GetNumberOfPoints();
int NumberOfIterations = 1;
With the preliminaries out of the way we now move on to the alternating estimation itself. The UpdateProgress
method is used to provide feedback to the GUI we will see how to respond to this from the Tcl-based graphical
user interface later.
// Provide Feedback to GUI
this->UpdateProgress(0.0);
// Begin Alternating Estimation
double previousmaxdist=0.0;
while (NumberOfIterations <= this->MaximumNumberOfIterations) {
The rst part is the correspondence estimation. Note the use of the Locator object which makes a complex
search problem trivial!
// 1. Find Correspondences
for(int i = 0; i < NumberOfLandmarks;i++) {
// Get a Point
double x[3]; SampledSourcePoints->GetPoint(i,x);
// Find where this point was at the last estimate!
double tx[3]; LandmarkTransform->TransformPoint(x,tx);
// Find Corresponding point using locator
int id=Locator->FindClosestPoint(tx);
CorrespondingPoints->SetPoint(i,this->Target->GetPoint(id));
}
208
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
Next we estimate the transformation using an instance of vtkLandmarkTransform.
LandmarkTransform->SetSourceLandmarks(SampledSourcePoints);
LandmarkTransform->SetTargetLandmarks(CorrespondingPoints);
LandmarkTransform->Update();
We concatenate this with any initial oset (from pre-aligning the centroids) to get the estimate of the complete
transformation, and call UpdateProgress to notify the user interface that we have news:
this->OutputTransformation->Identity();
this->OutputTransformation->Translate(offset);
this->OutputTransformation->Concatenate(LandmarkTransform);
double progress=double(NumberOfIterations)/double(this->MaximumNumberOfIterations);
this->UpdateProgress(progress);
Next comes some code for checking for convergence. When we are done, we clean up any temporary objects
allocated in the Run method.
// OMITTED: Code to check for convergence
vtkDebugMacro(<<"End of Iteration " << NumberOfIterations <<"\n");
++NumberOfIterations;
}
Locator->Delete(); SampledSourcePoints->Delete();
CorrespondingPoints->Delete(); LandmarkTransform->Delete();
this->UpdateProgress(1.0);
return 1;
}
Compiling using CMake and Loading into Tcl
We use a fairly straight-forward CMakeLists.txt. The only new point here, is that we need to explicitly tell the
VTK Tcl Wrappers that vtkAbstractPointBasedRegistration as an abstract class (i.e. it cannot be instantiated).
This is accomplished using the SET_SOURCE_FILES_PROPERTIES command as shown below:
SET(LIBRARY_SRCS
vtkAbstractPointBasedRegistration.cpp
vtkLinearICPRegistration.cpp
)
SET_SOURCE_FILES_PROPERTIES(
vtkAbstractPointBasedRegistration.cpp
ABSTRACT
209
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
Figure 20.1:
The GUI
for the
ICP al-
gorithm.
)
The end result of the compilation is a shared library that can be accessed from our Tcl user-interface code. This
is loaded in the usual way as demonstrated by the package le (loadpointreg.tcl) below:
package provide loadpointreg 1.0
package require mylibload 1.0
mylibload vtkPointRegTCL
On MS-Windows, you should change the debug to release (in mylibload), if you compile the release version
of the DLL. There is often a substantial speed up upon switching from debug to release versions.
20.3 Designing the Graphical User Interface
One of the goals of this class is to provide you with the skills to implement a complete application. This includes
both the algorithms and the graphical user interface which makes using the algorithms easier.
The overall GUI is shown in Figure 20.1. It consists of a viewer on the right and a set of controls for setting
parameters etc. on the left. The GUI is implemented using two simple [ Incr] Tcl Classes, the SimpleSurfaceViewer
class (simplesurfaceviewer.tcl) which captures the functionality of the viewer (on the right) and the master class
PointBasedRegistration (pointbased.tcl) which is the overall control that contains the viewer.
We will not go through this code line-by-line. Instead, we will focus on some key elements.
The Simple Surface Viewer
We will use the vtkTkRenderWidget class to embed a vtk Viewer into our Tcl-based GUI. This has some issues
with the standard vtk Interactors, a workaround is provided by the vtkinteraction Tcl package this is part of the
VTK distribution. The header includes all the usual good stu, including the request for vtkinteraction.
210
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
package provide SimpleSurfaceViewer 1.0
lappend auto_path [ file dirname [ info script ]]
package require vtk
package require vtkinteraction
package require Itcl
package require Iwidgets
The class denition follows. Essentially we keep handles to the following objects: (i) referencesurface and tar-
getsurface these are vtkPolyData objects of the two surfaces to be aligned, (ii) the transformlter, a vtkTrans-
formPolyData lter for transforming the surface, (iii) the transformation, (iv) the two actors used to display the
surfaces and (v) the renderer and the renderwidget. These are dened rst, followed by the constructor and the
destructor:
itcl::class SimpleSurfaceViewer {
protected variable referencesurface [ vtkPolyData New ]
protected variable transformfilter [ vtkTransformPolyDataFilter New ]
protected variable targetsurface [ vtkPolyData New ]
protected variable transformation 0
protected variable referenceactor [ vtkActor New ]
protected variable targetactor [ vtkActor New ]
protected variable renderwidget 0
protected variable renderer 0
protected variable basewidget 0
private common thisparam
constructor { base } { set basewidget $base
set thisparam($this,referencemode) "Wireframe"
set thisparam($this,targetmode) "Wireframe"
$this Initialize }
destructor {
$referencesurface Delete; $transformfilter Delete
$targetsurface Delete;
$referenceactor Delete $targetactor Delete
$renderer Delete; $renderwidget Delete }
The rest of the header denes some interface and implementation methods. The rst method, is the method
SetSurfacesAndTransformation, which will be used by the main GUI class to provide updates as to the current
state of the registration.
public method SetSurfacesAndTransformation { reference target transform }
public method SetSurfaceMode { md }
public method ResetRenderer { }
public method UpdateDisplay { }
public method GetThisPointer { } { return $this }
protected method Initialize { }
protected method CreatePipelines { }
protected method AddInteractor { renderwidget renwin }
211
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
};
The Implementation: We describe the implementation below, highlighting key points:
itcl::body SimpleSurfaceViewer::CreatePipelines { } {
# Omitted code to generate the two actors and their pipelines
# Good idea to use a simple object such as a sphere source
# to initialize the surfaces with default shapes until
# the user properly specifies them!
}
The Initialize method demonstrates how to create and add a vtkTkRenderWidget.
itcl::body SimpleSurfaceViewer::Initialize { } {
iwidgets::Labeledframe $basewidget -labelpos nw -labeltext "Surface Viewer"
pack $basewidget -side top -expand true -fill both
set labelframe [ $basewidget childsite ]
set buttonbar [ frame $labelframe.a1 ]
pack $buttonbar -side bottom -expand false -fill x -pady 2
# Create the Viewer First and add the actors to it
$this CreatePipelines
set renderwidget [ vtkTkRenderWidget $labelframe.a2 ]
pack $renderwidget -side top -expand true -fill both
set renwin [ $renderwidget GetRenderWindow ]
set renderer [ vtkRenderer New ]
$renwin AddRenderer $renderer
$renderer AddActor $referenceactor; $renderer AddActor $targetactor
# Create the Buttons Next
# Omited code -- see the file for details
# Key function to properly add interaction -- see below
$this AddInteractor $renderwidget $renwin
}
The interaction is added to the vtkTkRenderWidget using a call to the Tcl function ::vtk::bind_tk_widget
as shown below:
itcl::body SimpleSurfaceViewer::AddInteractor { renderwidget renwin } {
::vtk::bind_tk_widget $renderwidget $renwin
update idletasks
[ $renwin GetInteractor ] ExposeEvent
}
212
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
The rest of the world communicates with this viewer primarily using the method below. It can be used to provide
the triad of (i) the reference surface, (ii) the target surface and (iii) the current state of the transformation (or 0
to use an identity). Note the use of the ShallowCopy method to properly and eciently create a link to the input
surfaces.
itcl::body SimpleSurfaceViewer::SetSurfacesAndTransformation { reference
target transformation } {
$referencesurface ShallowCopy $reference; $targetsurface ShallowCopy $target
if { $transformation != 0 } {
$transformfilter SetTransform $transformation; $transformfilter Update
} else {
set temp [ vtkIdentityTransform New ];
$transformfilter SetTransform $temp; $temp Delete
}
}
The rest of the code provides additional display functionality:
itcl::body SimpleSurfaceViewer::SetSurfaceMode { md } {
# Omitted code to switch surface display from surface to wireframe or points
}
itcl::body SimpleSurfaceViewer::ResetRenderer { } {
# Reset the camera to show all objects
$renderer ResetCamera; $renderer ResetCameraClippingRange
$this UpdateDisplay
}
itcl::body SimpleSurfaceViewer::UpdateDisplay { } {
# Force a render update
[ $renderer GetRenderWindow ] Render
}
The Master Class PointBasedRegistration
This class leverages the viewer class dened above to provide a complete application around our ICP implemen-
tation. We use a notebook widget to break up the user interface into three tabs: (i) The Input tab where
we dene the input surfaces, (ii) the Compute tab where we specify algorithm parameters and execute the
algorithm and (iii) the Output tab where we can see and save the output matrix.
The script header contains the usual package require statements. The last two statements require the Surface
Viewer and our newly compiled shared library which includes the ICP code. This is where the C++ code is loaded
into the Tcl script.
package provide PointBasedRegistration 1.0
lappend auto_path [ file dirname [ info script ]]
# Omitted standard stuff
package require SimpleSurfaceViewer
213
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
package require loadpointreg
The Class Interface: The class interface has the usual mix of methods and variables. We highlight the
ExitCommand which is invoked by the exit button. This contains code to do proper cleanup on MS-Windows.
The Initialize command creates the user interface one tab at a time:
itcl::class PointBasedRegistration {
protected variable referencesurface [ vtkPolyData New ]
protected variable targetsurface [ vtkPolyData New ]
protected variable currentregistration 0
protected variable simplesurfaceviewer 0
protected variable notebook 0
private common thisparam
protected variable basewidget 0
protected variable outputbox 0
protected variable outputmatrix "1 0 0 0\n 0 1 0 0\n0 0 1 0\n0 0 0 1"
constructor { base } { set basewidget $base; $this Initialize }
destructor { $referencesurface Delete; $targetsurface Delete
catch [ $currentregistration Delete ]
catch [ itcl::delete obj $simplesurfaceviewer ] }
# Omitted method definitions
public method ExitCommand { }
protected method Initialize { }
};
The Initialize method (see below) creates the Graphical User Interface (GUI). This consists of three widgets along
the top, with the viewer on the right, a notebook widget on the left and a divider along the middle. Each tab on
the notebook is created using a separate helper method. Finally we create the viewer frame.
Creating the GUI:
itcl::body PointBasedRegistration::Initialize { } {
$this ResetParameters
toplevel $basewidget; wm geometry $basewidget 600x400
wm title $basewidget "Point Based Registration"; update
set notebook [ iwidgets::tabnotebook $basewidget.left -tabpos n -width 250 ]
set middle [ frame $basewidget.middle -width 5 -bg black ]
set viewframe [ frame $basewidget.right -width 500 ]
pack $notebook $middle -side left -expand false -fill y
pack $viewframe -side right -expand true -fill both
# Create Notebook Tabs
$this CreateInputGUI [ $notebook add -label "Input" ]
214
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
$this CreateComputeGUI [ $notebook add -label "Compute" ]
$this CreateOutputGUI [ $notebook add -label "Output" ]
$notebook view "Input"
# The Viewer Frame
frame $viewframe.2; pack $viewframe.2 -side bottom -expand false -fill x
eval "button $viewframe.2.2 -text Exit -command { $this ExitCommand }"
pack $viewframe.2.2 -side right -padx 1 -expand false
iwidgets::entryfield $viewframe.2.1 -textvariable \
[ itcl::scope thisparam($this,icp_status) ] -width 50 -labeltext "Status:"
pack $viewframe.2.1 -side left -padx 1 -expand true -fill x
set simplesurfaceviewer [ CreateSurfaceViewer $viewframe.1 ]
eval "wm protocol $basewidget WM_DELETE_WINDOW { $this ExitCommand }"
}
The following methods create the individual tab-GUIs. These are shown here in a highly abbreviated form:
itcl::body PointBasedRegistration::CreateSurfaceViewer { par } {
return [ [ SimpleSurfaceViewer \#auto $par ] GetThisPointer ]
}
itcl::body PointBasedRegistration::ResetParameters { } {
# Omitted Code to set default values for various parameters
}
itcl::body PointBasedRegistration::CreateInputGUI { base } {
# Omitted Code to create the input GUI for loading the
# two surfaces
}
itcl::body PointBasedRegistration::CreateComputeGUI { base } {
eval "button $base.bot -text \"Compute Registration\" \
-command { $this ComputeRegistration }"
pack $base.bot -side bottom -expand false -fill x
iwidgets::Labeledframe $base.top -labelpos nw -labeltext "Parameters" -relief ridge
pack $base.top -side top -expand true -fill both -pady 2
set w [ $base.top childsite ]
set k 0
iwidgets::entryfield $w.$k -labeltext "Max Num Points:" -width 5
-validate integer \
-textvariable [ itcl::scope thisparam($this,icp_numberofpoints) ] -relief sunken
pack $w.$k; incr k
# Omitted code to add more entryfields for the rest of the ICP Parameters
}
itcl::body PointBasedRegistration::CreateOutputGUI { base } {
# Omitted code to create the GUI for displaying and saving the matrix
}
215
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
Finally, the methods for loading and saving objects:
itcl::body PointBasedRegistration::LoadSurface { mode inputfilename } {
# Omitted Code to load the surface from a .vtk file
}
itcl::body PointBasedRegistration::SaveMatrix { } {
# Omitted Trivial code for saving the matrix
}
Invoking and Interacting with vtkLinearICPRegistration: The heart of the PointBasedRegistration
class is the
ComputeRegistration and RegistrationProgressUpdate methods that follow:
itcl::body PointBasedRegistration::ComputeRegistration { } {
# Omitted code that checks that both surfaces are OK
# Initialize the Display
$simplesurfaceviewer SetSurfacesAndTransformation $referencesurface $targetsurface 0
$simplesurfaceviewer UpdateDisplay
# Create the ICP Registration Class
set icp [ vtkLinearICPRegistration New ]
set currentregistration $icp
$icp SetSource $referencesurface; $icp SetTarget $targetsurface
$icp SetMaximumNumberOfIterations $thisparam($this,icp_numberofiterations)
$icp SetMaximumNumberOfPoints $thisparam($this,icp_numberofpoints)
$icp SetStartByMatchingCentroids $thisparam($this,icp_alignoriginsfirst)
if { $thisparam($this,icp_transformationmode) == "Rigid" } {
$icp SetTransformationTypeToRigid
} elseif { $thisparam($this,icp_transformationmode) == "Similarity" } {
$icp SetTransformationTypeToSimilarity
} else {
$icp SetTransformationTypeToAffine
}
Next we attach an observer to the class. This results in the function RegistrationProgressUpdate being called
each time the C++ code invokes the update progress method (this generates the ProgressEvent event), or when
it is done. Finally the registration is invoked using the Run method:
# Observer Stuff
eval "$icp AddObserver ProgressEvent { $this RegistrationProgressUpdate }"
eval "$icp AddObserver EndEvent { $this RegistrationProgressUpdate }"
# Execute
$icp Run
216
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
# Omitted code to capture the matrix etc
# Finally switch to the output pane
set thisparam($this,icp_status) "Done with Registration"
$notebook view "Output"
}
The Progress Update method rst updates the viewer with the current transformation estimate (currentregistration
= icp, see above). Next we update the display and the status label on the bottom:
itcl::body PointBasedRegistration::RegistrationProgressUpdate { } {
$simplesurfaceviewer SetSurfacesAndTransformation \
$referencesurface $targetsurface [ $currentregistration GetTransformation ]
$simplesurfaceviewer UpdateDisplay
set thisparam($this,icp_status) [ format "Registration Progress %.2f "\
[ expr 100.0 * [ $currentregistration GetProgress ] ] ]
update idletasks
}
The Exit Command: Finally a clean exit command to avoid those annoying crash-on-exit issues in MS-
Windows:
itcl::body PointBasedRegistration::ExitCommand { } {
vtkCommand DeleteAllObjects
exit
}
Invoking: An example is shown in script20-2.tcl. This includes loading two sphere surfaces by default:
lappend auto_path [ file dirname [ info script ]]
package require PointBasedRegistration
wm withdraw . ; update
set pb [ PointBasedRegistration \#auto .a ]
$pb LoadSurface Reference sphere1.vtk
$pb LoadSurface Target sphere2.vtk
217
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
20.4 An Alternative Implementation Using BioImage Suite Interactors
Instead of using the standard vtk interactors via the ::vtk::bind_tk_widget command, we can use our custom
BioImage Suite interactors via the use of the vtkpxGUIRenderer class. This provides a rich GUI for manipulating
the viewer, as shown in Figure 20.2.
The use of the BioImage Suite viewers is described in two classes (i) BioImageSuiteSimpleSurfaceViewer this
derives from SimpleSurfaceViewer and replaces some of the functionality there and (ii) BioImageSuitePointBase-
dRegistration this similarly derives from PointBasedRegistration and replaces some of the functionality there.
The heart of the new functionality is in three methods in BioImageSuiteSimpleSurfaceViewer. First the new
AddInteractor method creates a vtkpxGUIRenderer class (its GUI is accessed by pressing the shift key while
clicking with the right mouse button in the viewer.)
itcl::body BioImageSuiteSimpleSurfaceViewer::AddInteractor { renderwidget renwin } {
update idletasks; set bioimagesuite_viewer [ vtkpxGUIRenderer New ]
$bioimagesuite_viewer BindMouseEvents $renderwidget \
"$this HandleMouseEvent" "$this HandleUpdateEvent"
$bioimagesuite_viewer Initialize $renderwidget $renderer 0
set renderwindow $renwin
}
This requires two additional helper methods for processing expose and mouse events:
itcl::body BioImageSuiteSimpleSurfaceViewer::HandleUpdateEvent { args } {
if {$renderwindow != 0 } { $renderwindow Render }
}
itcl::body BioImageSuiteSimpleSurfaceViewer::HandleMouseEvent { mouse stat \
x1 x2 widgetname args} {
if { $bioimagesuite_viewer == 0 } { return }
# Need to flip y-axis vtk counts from bottom tk counts from top !!!
set wd [ $renderwindow GetSize ]
set x2 [ expr [lindex $wd 1 ] - $x2 ]
$bioimagesuite_viewer HandleMouseButtonEvent $mouse $stat $x1 $x2
}
The BioImageSuitePointBasedRegistration overrides a total of two short methods from its parent class: (i) The
CreateSurfaceViewer method which creates the instance of the BioImageSuiteSimpleSurfaceViewer. (ii) A slightly
Figure 20.2: The helper GUI of the vtkpxGUIRenderer class.
218
CHAPTER 20. IMPLEMENTING POINT-BASED REGISTRATION Draft Sep. 2009
dierent ExitCommand to handle properly the GUI constructs in BioImage Suite.
itcl::body BioImageSuitePointBasedRegistration::CreateSurfaceViewer { par } {
return [ [ BioImageSuiteSimpleSurfaceViewer \#auto $par ] GetThisPointer ]
}
itcl::body BioImageSuitePointBasedRegistration::ExitCommand { } {
destroy $basewidget ; update idletasks; exit
}
The use of these classes is illustrated in script20-3.tcl.
219
Draft Sep. 2009
Chapter 21
Implementing Intensity Based
Segmentation
In this chapter, we will discuss the implementation of three common image segmentation, or labeling, algorithms.
For the purposes of this chapter, segmentation is the process of assigning a label to each voxel that denes the
class (often equivalent to tissue type) it belongs in. For example, in MRI brain images the goal is to classify each
voxel as belonging to white matter, gray matter, cerebrospinal uid (CSF) or background (often the last two are
combined into a single class). The three algorithms are (i) a manual multiple threshold segmentation method, (ii)
an automated k-means clustering based approach [?] and (iii) an automated method that uses a Markov Random
Field model for image smoothness [?, ?]. A BioImage Suite component for accessing these algorithms is also
described.
21.1 Introduction The Three Algorithms
The Multiple Threshold Algorithm
This is a fairly trivial method that may be useful for initialization purposes. Given (a) an input image I which is
a collection of voxels i(x), where x is the voxel index, and (b) a set of M thresholds t
i
, where i = 0 : M 1
and t
i
> t
i1
we output a label image L (similarly a collection of voxels l(x) which takes values 0 : M. The
segmentation uses the following simple rule:
l(x) =
_
_
_
0 : i(x) t
0
r : t
r1
i(x) < t
r
, r (1, M 1)
M : i(x) > t
M1
This type of algorithm is often useful for CT images where the intensities are calibrated.
The K-means Clustering Algorithm
The K-means clustering algorithm (see [?]) can be used to essentially estimate the thresholds above. In our
implementation we assume that the intensity in each tissue class can be modeled as having a normal distribution
with mean and standard deviation . We will use the notation p(i[l = c) = ^(
c
,
c
) to dene this, where l
is the label and c is the class number. The goal of the version of the k-means algorithm that we will describe is
to estimate the class parameters (, ) for all classes and then to assign each voxel to the class that maximizes
the function:
220
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
l(x) =
arg max
l
p(i(x)[l(x) = c)
=
arg max
l
1
_
2
2
c
e
(i(x)c)
2
2
2
c
(21.1)
A simpler form of the algorithm assumes that all
c
s have the same value. This reduces the problem to estimating
the means only. The procedure can be described in recipe form as:
1. Decide on the number of classes M
2. Assign class centroids
c
and optionally standard deviations
c
for each class. The most common way to
do this is to equally space the
i
s and set all
i
s to some constant value.
3. Estimate the labels l(x) using equation 21.1. This is an exhaustive optimization compute p(i[l = c) for
all ls and pick the l that maximizes this probability.
4. Estimate a new set of
c
s and
c
s by computing the mean and standard deviation of the intensities of
the voxels labeled as having class c.
5. Repeat steps 3-4 until the parameters
c
and
c
converge.
Note that, since the spatial position of the voxels x does not enter into the calculation, a quick way to implement
this method is by rst forming the image histogram and performing all operations on the histogram itself. This
can speed up the iterations by a factor of 15000 or so in the case of an 128 128 128 image whose histogram
can be described by 128 bins.
Imposing Local Smoothness using Markov Random Fields
The key weakness of the previous method is that, as noted, the spatial position of each voxel is not used during the
segmentation. Spatial homogeneity is often a powerful constraint that can be used to improve the segmentation
in the presence of image noise. This can be loosely thought of as nding the label at each voxel that is an optimal
constraint between (i) the intensity at that voxel and (ii) the labels assigned to its neighboring voxels.
Markov Random Fields: The probabilistic structure used, most frequently, to capture such homogeneity is
to model the label (classication) image as a Markov Random Field. This (and we are skipping a lot of math here)
basically reduces to describing the probability of each voxel belonging to class l, as having a Gibbs distribution of
the form:
p(l(x)) = k
1
exp(W(L(
x
), l)) (21.2)
where k
1
is a normalization constant, L(
x
) is the set of labels of the neighboring voxels and W is a positive
semi-denite function. This is a way of converting an energy-function like smoothness term into a probability
distribution for integration into a probabilistic framework. The function W can take many forms, the one we will
use here is[?]:
W(l(X
n
) =
Rx
(l(x
) l(x)) (21.3)
This essentially counts the number of voxels in the neighborhood of the voxel at location x that have labels
dierent from it.
Overall Formulation using the Expectation-Minimization Framework: We rst dene the vector
as the collection of the means and standard deviations of all C classes, i.e. = [
0
,
0
, . . . ,
c1
,
c1
]. The
goal of the segmentation is to estimate both the set of labels L and the class parameters , given the image I.
We can express this mathematically as:
L,
=
arg max
L,
p(L, [I) (21.4)
221
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
As is commonly done, this can be solved iteratively in the same spirit as the EM-framework as:
E-Step:
k
=
arg max
p([I, L
k1
), M-Step: L
k
=
arg max
L
p(L[I,
k
) (21.5)
where k is the iteration number. In the E-Step we estimate a new set of parameters
k
given the current
classication L
k1
. In the M-Step, using the newly estimated
k
we estimate a new classication L
k
.
E-Step: This is straightforward. For each class i we estimate the mean and standard deviation of the intensities
I of all the voxels where M = i. This is identical to the procedure used in the k-means algorithm above.
M-Step: This takes the form of a Bayesian a-posterior maximization. First we express
l(x) =
arg max
l
log p(l(x)[i(x),
k
, L(
x
)
k
1
+ log p(i(x),
k
[l(x))
. .
Data Adherence Term
+ logp(l[L(
x
))
. .
MRF Smoothness Term
(21.6)
where k
1
is a constant. This equation is easily maximized by a greedy search strategy as M can only take values
of 1 . . . C. The prior term on the classication, p(L), can be dened by modeling L as a Markov random eld
(see discussion above and equation 21.3). We express the likelihood (or data-adherence) term for each possible
value of l(x) = c as:
p(i(x),
k
[l(x) = c) = p(i(x)[
k
, l(x) = c) (21.7)
which is similar to the model previously used in equation 21.1.
Hopefully the math will become easier to follow with a quick look at the code implementing it.
21.2 Algorithm Implementation
All three algorithms are implemented as classes deriving from the vtkSimpleImageToImageFilter class. The scope
is similar to material described in chapter 17, and you are strongly urged to re-read the notes from that chapter
again.
The Multi Threshold Method
The header le of this method (vtkMultiThresholdSegmentation.h) is fairly standard. In addition to an input
image the user needs to specify an array of M thresholds that will results in M + 1 output classes.
class vtkMultiThresholdSegmentation : public vtkSimpleImageToImageFilter
{
public:
static vtkMultiThresholdSegmentation *New();
vtkTypeMacro(vtkMultiThresholdSegmentation,vtkSimpleImageToImageFilter);
// The Input here is N Thresholds resulting in N+1 output classes
vtkSetObjectMacro(Thresholds,vtkDoubleArray);
vtkGetObjectMacro(Thresholds,vtkDoubleArray);
protected:
vtkMultiThresholdSegmentation();
virtual ~vtkMultiThresholdSegmentation();
virtual int RequestInformation(vtkInformation *vtkNotUsed(request), vtkInformationVector **inputVector,
222
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
vtkInformationVector *outputVector);
virtual void SimpleExecute(vtkImageData* in,vtkImageData* out);
vtkDoubleArray* Thresholds;
};
The implementation (vtkMultiThresholdSegmentation.cpp) is fairly straightforward. First we dene the RequestIn-
formation method to explicitly specify that the output image will always have type short and a single frame
regardless of what the input image is like:
223
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
int vtkMultiThresholdSegmentation::RequestInformation(
vtkInformation *request,
vtkInformationVector **inputVector,
vtkInformationVector *outputVector)
{
vtkDataObject::SetPointDataActiveScalarInfo(outputVector->GetInformationObject(0), -1, 1);
vtkDataObject::SetPointDataActiveScalarInfo(outputVector->GetInformationObject(0), VTK_SHORT, -1);
}
The thresholding takes place in the SimpleExecute method. This is fairly trivial:
void vtkMultiThresholdSegmentation::SimpleExecute(vtkImageData* input,vtkImageData* output )
{
// Omitted Code to check that inputs are OK
int num=this->Thresholds->GetNumberOfTuples();
int NumberOfClasses=num+1;
double* thr=new double[num];
double* outthr=new double[num];
for (int ia=0;ia<num;ia++)
thr[ia]=this->Thresholds->GetComponent(ia,0);
// Omitted code that sorts thresholds and places them into array outthr[]
vtkDataArray* inpdata=input->GetPointData()->GetScalars();
vtkDataArray* outdata=output->GetPointData()->GetScalars();
outdata->FillComponent(0,0.0); int numvoxels=inpdata->GetNumberOfTuples();
for (int voxel=0;voxel<numvoxels;voxel++) {
int c=0,done=0; double v=inpdata->GetComponent(voxel,0);
while (c<num && done==0) {
if (v<outthr[c]) {
outdata->SetComponent(voxel,0,c);
done=1;
}
else
c++;
}
if (done==0)
outdata->SetComponent(voxel,0,NumberOfClasses-1);
}
delete [] thr; delete [] outthr;
}
The K-Means Method
This is a more interesting class. It takes a number of arguments, namely (i) Number of Class this species the
number of unique class labels, (ii) Iterations the maximum number of iterations, (iii) Number Of Bins this
denes the resolution of the histogram, (iv) Convergence this sets the thereshold that if the maximum change
in the the estimation of the class falls below which, the algorithm is set to have converged and (v) MaxSigmaRatio
which constrains the range of the values of the standard deviations. Setting this to 1 ensures that all standard
deviations are equal. Setting this to 0 results in an unconstrained estimation of the standard deviations.
224
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
class vtkKMeansSegmentation : public vtkSimpleImageToImageFilter
{
public:
static vtkKMeansSegmentation *New();
vtkTypeMacro(vtkKMeansSegmentation,vtkSimpleImageToImageFilter);
// Omitted Code
// Set and Get Macros for the five input parameters
// NumberOfClasses, Iterations, NumberOfBins, Convergence, MaxSigmaRatio
// Get The Histogram and the class Parameters if desired
vtkGetObjectMacro(Histogram,vtkImageData);
vtkGetObjectMacro(Parameters,vtkDoubleArray);
protected:
vtkKMeansSegmentation();
virtual ~vtkKMeansSegmentation();
virtual int RequestInformation(vtkInformation *vtkNotUsed(request), vtkInformationVector **inputVector,
vtkInformationVector *outputVector);
virtual void SimpleExecute(vtkImageData* in,vtkImageData* out);
// Helper Methods
virtual double Metric(double x,double m,double sigma2);
virtual vtkImageData* CreateHistogram(vtkImageData* input,int NumBins);
virtual int InitializeParameters(vtkImageData* histogram,int numclasses,
vtkDoubleArray* params);
// Omitted class parameter definition
};
Implementation: This is found in vtkKMeansSegmentation.cpp. We will only highlight key pieces of code
here.
The rst interesting point is that we dene the Gaussian distribution in a virtual method called Metric. If we
wanted to replace this with a dierent distribution, we could derive a new class from vtkKMeansSegmentation
and simply override the Metric method.
double vtkKMeansSegmentation::Metric(double x,double m,double sigma2) {
return (x-m)*(x-m)/(-2.0*sigma2)* 1.0/sqrt(2.0*vtkMath::Pi()*sigma2);
}
The next interesting point is the creation of the histogram. This uses the vtkImageAccumulate lter. Note that,
the vtkImageAccumulate Filter is designed to generate a histogram for color images (RGB). It assumes that the
input image will have upto three components and produces a histogram for each. We specify the position, spacing
and number of bins using the SetComponentOrigin, SetComponentSpacing and SetComponentExtent methods
of the vtkImageAccumulate class respectively. Since we are dealing with only grayscale images we set the extent
for components 2 and 3 (which do not exist) to have a single bin.
vtkImageData* vtkKMeansSegmentation::CreateHistogram(vtkImageData* input,int NumBins) {
if (input==NULL) return NULL; if (NumBins<4) NumBins=4; double range[2];
input->GetPointData()->GetScalars()->GetRange(range); double minv=range[0],
225
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
maxv=range[1]; this->HistogramOrigin=minv; this->HistogramSpacing=1.0;
this->NumberOfBins=NumBins; int drange=int(maxv-minv+1); if (drange<
this->NumberOfBins) this->NumberOfBins=drange; while(drange>
this->NumberOfBins*this->HistogramSpacing) this->HistogramSpacing+=1.0;
this->NumberOfBins=int(drange/this->HistogramSpacing+0.5);
vtkImageAccumulate* accumulate=vtkImageAccumulate::New();
accumulate->SetInput(input);
accumulate->SetComponentOrigin(this->HistogramOrigin,0.0,0.0);
accumulate->SetComponentSpacing(this->HistogramSpacing,1.0,1.0);
accumulate->SetComponentExtent(0,this->NumberOfBins-1,0,0,0,0);
accumulate->Update();
vtkImageData* out=vtkImageData::New(); out->ShallowCopy(accumulate->GetOutput());
accumulate->Delete();
return out;
}
The class parameters are initialized in the method InitializeParameters. The means are evenly spaced in the
intensity range and the standard deviation is set to be constant. The parameters are stored in a 3-component
double array which has one tuple for each class. The tuples form the vector [ NumVoxels, Mean, Standard
Deviation ] where NumVoxels is the number of voxels having this class label.
Finally the segmentation itself is implemented in the SimpleExecute method as usual. The rst part deals with
initialization issues:
void vtkKMeansSegmentation::SimpleExecute(vtkImageData* input,vtkImageData* output )
{
// Omitted code: check for valid input image
this->Histogram=this->CreateHistogram(input,this->NumberOfBins);
this->Parameters=vtkDoubleArray::New();
this->InitializeParameters(this->Histogram,this->NumberOfClasses,this->Parameters);
this->UpdateProgress(0.05);
int dim[3]; this->Histogram->GetDimensions(dim);
vtkDataArray* data=this->Histogram->GetPointData()->GetScalars();
double* mean =new double[this->NumberOfClasses], *sigma2=new double[this->NumberOfClasses];
double* sum =new double[this->NumberOfClasses], *sum2 =new double[this->NumberOfClasses];
double* num =new double[this->NumberOfClasses],
for (int j=0;j<this->NumberOfClasses;j++) {
sigma2[j]=this->Parameters->GetComponent(j,2); sigma2[j]*=sigma2[j];
mean[j] =this->Parameters->GetComponent(j,1);
}
Next we launch into the iterative estimation of labels and class parameters. Note that, rather than iterating on
a voxel-by-voxel basis, we operate instead on the image histogram for eciency.
int iter=1; double error=this->Convergence+1.0;
226
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
while (iter <= this->Iterations && error > this->Convergence)
{
error=0.0; double totalnum=0.0;
for (int i=0;i<this->NumberOfClasses;i++) { sum[i]=0.0;sum2[i]=0.0;num[i]=0.0;}
For each bin, we compute the actual intensity v and then nd which class has the best Metric i.e. the highest
likelihood. We assign the bin to this class and use it to form the sums needed to compute the class mean and
standard deviation:
for (int bin=0;bin<dim[0];bin++) {
double v=this->HistogramOrigin+double(bin)*this->HistogramSpacing;
double numv=data->GetComponent(bin,0);
double bestp=0.0; int bestc=0;
for (int c=0;c<this->NumberOfClasses;c++) {
double p=this->Metric(v,mean[c],sigma2[c]);
if (p>bestp) { bestp=p; bestc=c; }
}
num[bestc]+=numv; sum[bestc]+=v*numv; sum2[bestc]+=v*v*numv;
totalnum+=numv;
}
Once the sums are complete we compute the class means and standard deviations. We also check for convergence.
If the means remain close to their previous values, this implies that the algorithm has converged and we need no
more iterations. An interesting twist here (omiited from this code) is that we compute the maximum standard
deviation of all the classes and can enforce all other classes to have standard deviations at least as big as a certain
fraction (MaxSigmaRatio) of this. This type of constraint adds stability to the estimation:
for (int c=0;c<this->NumberOfClasses;c++) {
double m=sum[c]/num[c];
if (fabs(m-mean[c])>error) error=fabs(m-mean[c]);
mean[c]=m; sigma2[c]=(sum2[c]/num[c]-mean[c]*mean[c]);
double s=sqrt(sigma2[c]);
}
++iter;
}
Finally, once the iterative estimation of class parameters has converged, we re-estimate class labels, this time on
a voxel-by-voxel basis so that we can create the nal segmentation map:
// Omitted code sort classes to have ascending means
vtkDataArray* inpdata=input->GetPointData()->GetScalars();
vtkDataArray* outdata=output->GetPointData()->GetScalars();
outdata->FillComponent(0,-1.0);
int numvoxels=inpdata->GetNumberOfTuples();
for (int voxel=0;voxel<numvoxels;voxel++) {
227
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
double bestp=0.0; int bestc=0;
double v=inpdata->GetComponent(voxel,0);
for (int c=0;c<this->NumberOfClasses;c++) {
double p=this->Metric(v,mean[c],sigma2[c]);
if (p>bestp || c==0) { bestp=p; bestc=c; }
}
outdata->SetComponent(voxel,0,bestc);
}
delete [] mean; delete [] sigma2; delete [] sum; delete [] sum2; delete [] num;
}
The MRF Segmentation Method
Things will begin to appear to get more complex here. There is, however, nothing particularly complex about any
of the code following, but it might be a little longer than can be easily described in a handout.
The class denition (vtkMRFSegmentation.h) is fairly straight-forward. The class takes a number of key pa-
rameters namely (i) NumberOfIterations, (ii) NumberOfClasses (iii) Smoothness the weight of the MRF term.
Secondary parameters include ConvergencePercentage and MRFIterations (the number of iterations in the M-
Step). Finally, we also use an ImageNoise term that adds some stability to the estimation more on this later.
class vtkMRFSegmentation : public vtkSimpleImageToImageFilter {
public:
static vtkMRFSegmentation *New();
vtkTypeMacro(vtkMRFSegmentation,vtkSimpleImageToImageFilter);
// Omitted Code, Set/Get Macros for Class Parameters such as
// NumberOfIterations, MRFIterations, NumberOfClasses, Smoothness,ImageNoisePercentage
// ConvergencePercentage
// Initial Segmentation Map as Image
vtkSetObjectMacro(InitialSegmentation,vtkImageData);
vtkGetObjectMacro(InitialSegmentation,vtkImageData);
// Initial Segmentation Map as Image
vtkGetObjectMacro(Parameters,vtkDoubleArray);
protected:
vtkMRFSegmentation();
virtual ~vtkMRFSegmentation();
virtual int RequestInformation(vtkInformation *vtkNotUsed(request), vtkInformationVector **inputVector,
vtkInformationVector *outputVector);
virtual void SimpleExecute(vtkImageData* in,vtkImageData* out);
// E-Step Is Easy for the most_part
virtual void DoExpectationStep(vtkImageData* intensities,vtkImageData* classification,
vtkDoubleArray* params,int numclasses);
// M-Step (...) indicates lots of parameters!
virtual double Metric(double x,double m,double sigma2);
virtual double ComputeLogLikelihoodProbability(...)
virtual double ComputeLogMRFPrior(...)
virtual double ComputeTotalMinusLogProbability(...)
virtual int UpdateVoxel(int eveni,int evenj,int evenk,int pass);
228
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
virtual int ComputeMRFIncrementsAndWeights(vtkImageData* img,int incr[6],double wgt[6]);
virtual int ClassifyVoxel(....)
virtual double DoMaximizationStep(vtkImageData* intensity_image, vtkImageData* label_image,
vtkDoubleArray* params, int numclasses,
double smoothness, int maxiter);
//Omitted Code -- definition of class variables
};
The Implemetation: This consists of about 400 lines of code in total which we will not describe in huge
detail. Essentially the ow goes as follows: SimpleExecute initializes the process and calls the Expectation and
Maximization procedures in an alternating fashion until convergence:
void vtkMRFSegmentation::SimpleExecute(vtkImageData* input,vtkImageData* output)
{
# Omitted Code to check that inputs are valid
# Compute Image Noise Variance
double range[2]; input->GetPointData()->GetScalars()->GetRange(range);
this->ImageNoiseVariance=pow(range[0]+
this->ImageNoisePercentage*0.01*(range[1]-range[0]),2.0);
# Create Ouput Image
vtkImageData* classification=vtkImageData::New();
classification->CopyStructure(output);
classification->AllocateScalars();
classification->GetPointData()->GetScalars()->CopyComponent(0,
this->InitialSegmentation->GetPointData()->GetScalars(),0);
int iterations=1;
while(iterations<=this->NumberOfIterations)
{
this->DoExpectationStep(input,classification,this->Parameters,this->NumberOfClasses);
double changed=this->DoMaximizationStep(input,classification,this->Parameters,
this->NumberOfClasses,this->Smoothness,this->MRFIterations);
if (changed<this->ConvergencePercentage)
iterations=this->NumberOfIterations+1;
++iterations;
}
output->ShallowCopy(classification); classification->Delete();
this->UpdateProgress(1.0);
}
The Expectation Step: The expectation step is also fairly straight-forward and similar in scope to the code in
vtkKMeansSegmentation for estimating parameters.
void vtkMRFSegmentation::DoExpectationStep(vtkImageData* intensities,
vtkImageData* classification,
vtkDoubleArray* params,int numclasses)
229
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
{
vtkDataArray* intens=intensities->GetPointData()->GetScalars();
vtkDataArray* labels=classification->GetPointData()->GetScalars();
double* sum =new double[numclasses],*sum2 =new double[numclasses];
int* count=new int[numclasses];
for (int ia=0;ia<numclasses;ia++) {
sum[ia]=0.0; sum2[ia]=0.0; count[ia]=0; }
int numvoxels=intens->GetNumberOfTuples();
for (int i=0;i<numvoxels;i++) {
double v=intens->GetComponent(i,0);
int l=(int)labels->GetComponent(i,0);
if (l>=0 && l < numclasses) {
sum[l]+=v; sum2[l]+=v*v; count[l]+=1; }
}
double totalnp=double(numvoxels);
for (int i=0;i<numclasses;i++) {
double numv=count[i]; params->SetComponent(i,0,numv);
double mean=sum[i]/numv; params->SetComponent(i,1,mean);
double mean2=sum2[i]/numv; params->SetComponent(i,2,sqrt(mean2-mean*mean));
}
}
The Maximization Step: The M-Step is a little bit more involved. We rst note that we are using rst-order
neighborhoods for the MRF, i.e. the voxels immediately adjacent in the x,y and z-directions. Hence we can
update the labels in two passes by thinking of the image as a chess-board. We rst update all the black squares
(keeping the labels of the white squares xed) and then update all the white squares (keeping the black squares
xed). In addition, we randomize the order in which we start.
This method (DoMaximizationStep) can be simply thought of as a smart way to call the ClassifyVoxel method
(described next) in the appropriate order.
double vtkMRFSegmentation::DoMaximizationStep(vtkImageData* intensity_image,
vtkImageData* label_image,
vtkDoubleArray* params, int numclasses,
double smoothness,int maxiter)
{
vtkDataArray* intensities=intensity_image->GetPointData()->GetScalars();
vtkDataArray* labels=label_image->GetPointData()->GetScalars();
int nt=intensities->GetNumberOfTuples();
int dim[3]; intensity_image->GetDimensions(dim);
int incr[6]; double weights[6];
this->ComputeMRFIncrementsAndWeights(intensity_image,incr,weights);
int done=0,iter=0; int tenth=nt/11;
double sumchanged=0.0;
while (done==0 && iter<maxiter) {
double total=0.0, changed=0.0;
int order=(vtkMath::Random()>0.5);
for (int pass=0;pass<=1;pass++) {
int realpass=pass;
230
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
if (order==1) realpass=1-pass;
// Omitted Code, loop over k (z-axis), j (y-axis)
// Compute voxel index
int vox=k*incr[5]+j*incr[3]+1;
for (int i=1;i<dim[0]-1;i++) {
// Check for voxel color (i.e. black square or white square)
// If yes update
if ( this->UpdateVoxel(eveni,evenj,evenk,pass)==1) {
changed+=this->ClassifyVoxel(vox,intensities,labels,params,
numclasses,incr,weights,smoothness);
++total;
}
++vox;
....
}
changed=100.0*changed/total; sumchanged+=changed;
if (changed<this->ConvergencePercentage) done=1;
++iter;
}
return sumchanged;
}
Computing the Posterior: The hard work of the M-Step is done by the ClassifyVoxel method. This takes
one voxel, specied by voxel_index and computes its posterior probability (equation 21.6). for each possible
class assignment. Then, we set the class label for this voxel to be the one that corresponds to the class that
maximimizes the posterior probability:
int vtkMRFSegmentation::ClassifyVoxel(int voxel_index,vtkDataArray* intensities,
vtkDataArray* labels,vtkDoubleArray* params,
int numclasses,int incr[6],double wgth[6],
double smoothness)
{
int current_label=(int)labels->GetComponent(voxel_index,0);
int bestclass=0;
double bestprob=0.0;
double v=intensities->GetComponent(voxel_index,0);
for (int cl=0;cl< numclasses;cl++) {
double prob=this->ComputeTotalMinusLogProbability(v,voxel_index,cl,labels,
params,numclasses,incr,
wgth,smoothness);
if (cl==0 || prob<bestprob) {
bestprob=prob; bestclass=cl;
}
}
if (bestclass!=current_label) {
labels->SetComponent(voxel_index,0,bestclass); return 1;
}
return 0;
}
231
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
The probability is computed by the ComputeTotalMinusLogProbability method. This is also straightforward
as it delegates all the work to two other functions (i) ComputeLogLikelihoodProability the data term and (ii)
ComputeLogMRFPrior the smoothness term. We examine these, in order, next:
double vtkMRFSegmentation::ComputeTotalMinusLogProbability(double intensity,
int current_voxel, int current_label,
vtkDataArray* labels,vtkDoubleArray* parameters,
int numclasses, int incr[6],double wgth[6],double smoothness)
{
double mlterm=this->ComputeLogLikelihoodProbability(intensity,current_label,parameters);
double pmrf =smoothness*this->ComputeLogMRFPrior(labels,current_voxel,
current_label,numclasses,incr,wgth);
return -mlterm+pmrf;
}
Computing the Log Likelihood: The log likelihood probability has an interesting twist. We model each voxel as
having intensity y = x + n, where x is the true intensity and n is the image noise (zero mean, gaussian). The
distribution of x depends on its current label l and has mean
l
and standard deviation
l
. The distribution of
y has mean
l
and variance
2
l
+
2
n
where
n
is the standard deviation of the noise. This added term is useful
because it adds stability to the estimation, i.e. all classes will have standard deviation at least equal to sigma
n
.
Finally, as before, the distribution is implemented in a separate function, Metric, so that it can easily be changed.
double vtkMRFSegmentation::ComputeLogLikelihoodProbability(double intensity,
int current_label,vtkDoubleArray* params)
{
double totalprob=0.0;
double mean=params->GetComponent(current_label,1);
double sigma=params->GetComponent(current_label,2);
double variance=sigma*sigma+this->ImageNoiseVariance;
totalprob=log(this->Metric(intensity,mean,variance));
return totalprob;
}
Computing the MRF Term: This implements the MRF model desribed in equation 21.3. The only added twist is
that we weight the eect of each voxel based on its distance from the current voxel. (If the images are isotropic
all weights will take value=1).
double vtkMRFSegmentation::ComputeLogMRFPrior(vtkDataArray* labels,int vox,
int current_label,int numclasses,
int incr[6],double wgth[6])
{
double sum=0.0;
for (int i=0;i<=5;i++) {
int l=(int)labels->GetComponent(vox+incr[i],0);
if (l!=current_label) sum+=wgth[i];
}
232
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
}
return sum;
}
Other Functions: The Metric function implements the Gaussian distribution. The UpdateVoxel method determines
whether a voxel is on the right color of the checkerboard and hence whether it should be updated (this is
called from within the DoMaximizationStep method). Finally the ComputeMRFIncrementsAndWeights method
computes both the osets (in raster-voxel order) and the weights of neighboring voxels for use in the MRF model
computation.
Compiling using CMAKE
These classes are placed in a library called MRFSegm. The CMakeLists.txt le is straight-forward and will not be
described here. The libraries can be loaded into Tcl using the script loadmrfsegm.tcl shown below:
package provide loadmrfsegm 1.0
if { $tcl_platform(platform) == "windows" } {
set name debug/vtkMRFSegmTCL.dll
} else {
set name libvtkMRFSegmTCL.so
}
load $name;unset name
21.3 A Complete Intensity-Based Segmentation Application
We implement this as a BioImage Suite application, shown in Figure 21.1, following the guidelines in chapter
14. The implementation consists of two les, the main script (segmtool.tcl) and the segmentation control class
(mrfutility.tcl). We examine these in turn next.
The Main Application
This is dened in the le segmtool.tcl and is similar to the mytool.tcl script described in chapter 14. There
is nothing exciting here, other than the fact that we will use the Objectmap viewer so that we can overlay the
results of the segmentation on the original image, if we choose to.
lappend auto_path [ file dirname [ info script ]]
package require loadbioimagesuite 1.0
package require pxitclbaseimageviewer 1.0
package require mrfutility 1.0
# Eliminate the default tk window
wm withdraw .
# Initialize a base application with some default settings
233
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
Figure 21.1: The Main application with the added MRFSegmentation menu.
# See bioimagesuite/main/pxitclbaseimageviewer.tcl for all the options
set baseviewer [ pxitclbaseimageviewer \#auto 0 ]
$baseviewer configure -appname "BioImage Suite::Intensity Segmentation Tool"
# Omitted code .. define default choices of controls
$baseviewer configure -show_standard_images 1
....
# Create an Objectmap Viewer
$baseviewer InitializeObjectmapViewer .[ pxvtable::vnewobj ] 1
# Add a submenu for our Segmentation Control
set menubase [ $baseviewer cget -menubase ]
set mb [ menu $menubase.[pxvtable::vnewobj] -tearoff 0 ]
$menubase add cascade -label "MRFSegmentation" -menu $mb -underline 0
# Create the segmentation control, add it to the menu and register it with
# the viewer (addcontrol)
set myutil [ mrfutility \#auto $baseviewer ]
$myutil Initialize [ $baseviewer GetBaseWidget ].[ pxvtable::vnewobj ]
$myutil AddToMenuButton $mb
$baseviewer AddControl $myutil
# Omitted code -- the rest is similar to mytool.tcl (chapter16)
234
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
Figure 21.2: The three tabs of the Segmentation Control.
The Segmentation Control
The Segmentation Control is implemented as an [ Incr ] Tcl class deriving from pxitclbaseimagecontrol. This
consists of three tabs each having controls for setting the parameters for one of the three algorithms described
earlier. The three tabs are shown in Figure 21.2
In the class header there is a statement:
package require loadmrfsegm
for loading our newly implemented methods into the Tcl interpreter.
The class denition is as follows. It consists of essentially ve types of methods, (i) the constructor/destructor
pair, (ii) initialization methods (iii) methods for generating the GUI for the individual methods called by the
initialization methods, (iii) a method to add this control the menu and (iv) methods to perform the segmentation
and deal with the results.
itcl::class mrfutility {
inherit pxitclbaseimagecontrol
protected common thisparam
constructor { par } { pxitclbaseimagecontrol::constructor $par } { InitializeControl }
destructor { }
# initialization methods
public method InitializeControl { }
public method Initialize { inpwidg }
# interface creation methods
protected method CreateOutputImageGUIControl { guiname name widget }
protected method CreateMultiThresholdControl { name }
protected method CreateKMeansControl { name }
protected method CreateMRFControl { name }
# Add this control to a Menu Button
public method AddToMenuButton { mb args }
# Computational Utility Stuff
public method CheckImage { image name operation verbosity }
public method ComputeMultiThresholdSegmentation { }
public method ComputeKMeansSegmentation { }
public method ComputeMRFSegmentation { }
235
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
# Deal with Results
public method ProcessResult { image guiname opname }
}
Initialization Code: Nothing special here. The Initialize function creates a toplevel widget, packs a notebook
in it and then calls three helper methods to add the controls for each tab.
itcl::body mrfutility::InitializeControl { } {
# Omitted Code which sets some default parameters
}
itcl::body mrfutility::Initialize { widget } {
if { $initialized == 1 } { return $basewidget }
set basewidget [toplevel $widget ]; wm geometry $basewidget 610x450
set notebook $basewidget.notebook; iwidgets::tabnotebook $notebook -tabpos w
CreateMultiThresholdControl [ $notebook add -label "MultiThreshold" ]
CreateKMeansControl [ $notebook add -label "KMeans" ]
CreateMRFControl [ $notebook add -label "MRF" ]
pack $notebook -side top -fill both -expand t -padx 5
set initialized 1; SetTitle "Intensity Segmentation Tool"
eval "wm protocol $basewidget WM_DELETE_WINDOW { wm withdraw $basewidget }"
return $basewidget
}
Creating the GUI for each Method: Three very similar methods (CreateMultiThresholdControl, CreateK-
MeansControl and CreateMRFControl) create the interface for each algorithm. We will look at one of them in
detail CreateKMeansControl, the others are very similar.
This control consists of three parts. At the top there is a series of iwidgets::entryfield widgets for setting
the values of the input parameters. In the middle there is a button (base.but) for executing the segmentation,
and at the bottom there is a pxitclimageGUI control for storing the output segmentation.
itcl::body mrfutility::CreateKMeansControl { base } {
iwidgets::labeledframe $base.frame0 -labelpos nw \
-labeltext "KMeans Segmentation"
pack $base.frame0 -fill both -expand f -pady 5
set w [ $base.frame0 childsite ]; set k 0
iwidgets::entryfield $w.$k -labeltext "Classes:" \
-textvariable [ itcl::scope thisparam($this,kmeans_numclasses) ] \
-width 2 -validate integer
pack $w.$k -side top -expand true -fill x; incr k
# Omitted code for entryfileds for Iterations, Bins etc.
236
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
# Create a compute button
eval "button $base.but -text \"Compute K-Means Segmentation\" \
-command { $this ComputeKMeansSegmentation }"
pack $base.but -side top -expand t -fill x
# Create a pxitclimageGUI to store output -- more later
set widg [ $this CreateOutputImageGUIControl "kmeansoutput" "K-Means Output" $base.bot ]
pack $widg -side bottom -expand f -fill x
}
The Image GUIs are created by the CreateOutputImageGUIControl Function below. We add two functions, one
to display the segmentation as an image (Display) and one to display it as an overlay (Display Mask).
itcl::body mrfutility::CreateOutputImageGUIControl { guiname name widget } {
set thisparam($this,$guiname) [ [ pxitclimageGUI \#auto ] GetThisPointer ]
$thisparam($this,$guiname) configure -description $name
$thisparam($this,$guiname) Initialize $widget
set bbut [ $thisparam($this,$guiname) cget -browsebutton ]; pack forget $bbut
$thisparam($this,$guiname) AddFunction "$parent SetResultsFromObject" "Display" "$this"
$thisparam($this,$guiname) AddFunction "$parent SetMaskFromObject" "Display Mask" "$this"
return $widget
}
Computational Code: The three algorithms are executed using the methods ComputeMultiThresholdSeg-
mentation, ComputeKMeansSegmentation and method ComputeMRFSegmentation respectively. As before, we
will look at one of these in detail, ComputeKMeansSegmentation. We rst check that the current image of the
viewer (stored in the currentimage variable) exists. If it does, we create the vtkKMeansSegmentation object
and set its parameters. The segmentation is invoked using the Update method. The result is then handled using
the ProcessResult method. Nothing complicated here:
itcl::body mrfutility::ComputeKMeansSegmentation { } {
# Omitted code to check that currentimage exists
WatchOn
set segm [ vtkKMeansSegmentation New ]
$segm SetInput [ $currentimage GetImage ]
$segm SetNumberOfBins $thisparam($this,kmeans_numbins)
$segm SetNumberOfClasses $thisparam($this,kmeans_numclasses)
# ...
$segm Update
WatchOff
$this ProcessResult [ $segm GetOutput ] "kmeansoutput" "km"
$segm Delete
}
The fancy coding is in the ProcessResult method. This does three things: (i) it copies the segmentation result
237
CHAPTER 21. IMPLEMENTING INTENSITY BASED SEGMENTATION Draft Sep. 2009
into the image currentresults, (ii) it sends the segmentation result to the viewers mask image (the overlay) and
(iii) it stores it into the appropriated pxitclimageGUI for later use.
itcl::body mrfutility::ProcessResult { image guiname opname } {
# Step 1
$currentresults ShallowCopyImage $image
$currentresults configure -filename [ AddPrefix [ $currentimage cget
-filename ] $opname ]
# Step 2
$parent SetMaskFromObject $currentresults $this
# Step 3
set gui $thisparam($this,$guiname)
[ $gui GetObject ] ShallowCopy $currentresults; $gui Update
}
238
Draft Sep. 2009
Chapter 22
More Advanced Image Algorithm/Filters
and Templated Execution
In the rst part of this chapter we discuss the overall algorithm/lter pipeline for image processing in VTK
working our way from vtkAlgorithm to vtkImageAlgorithm and certain specic examples. Next we describe
the implementation of multi-threaded templated image-to-image lter implementation. Such implementations,
while somewhat more complex, can oer substantial speed improvements over the more simple lters previously
described.
22.1 Introduction
In previous chapters we have considered simple image-to-image lters deriving from the class vtkSimpleImage-
ToImageFilter. A common aspect of all the lters we discussed was the reliance on convenience methods such
as GetComponent, SetComponent, SetScalarComponentFromDouble and GetScalarComponentAsDouble for
manipulating images. These convenience methods hide the actual underlying data type of the image. They, at
least to the programmer, treat images the same regardless of whether the image has type short or oat.
1
This convenience often comes at the cost of computational overhead. Ultimately the fastest way to access data
is by direct pointer manipulation. This makes the programming arcane and practically returns the programmer
to the age of assembly language. However, such operations can dramatically improve the computational speed of
many operations. The usual path is to implement a class using the convenience methods and if it becomes too
critical in a large system, re-implement it carefully, once and for all, with lower level operations.
Modern C++ is beginning to provide an additional mechanism to encapsulate pointer operations with less over-
head, using a type of class called iterators. Many iterators are dened in the Standard Template Library (STL),
which unfortunately we will not have time to get into this semester. However, VTK denes two simple iterators
that we will explore in the context of the example to follow.
Iterators leverage templating for execution speed. An iterator is programmed in a generic (not type specic)
setup, unlike the Get/Set methods above which take double inputs. Templated implementations are used to
generate specic versions of the iterators for specic types such as oats, ints etc.
In this chapter we will explore the VTK pipeline in more depth using concrete VTK classes as examples.
1
Naturally in the Set methods if the value specied is not valid for the type, we have unexpected results. For example,
consider setting the value of a voxel in a short image to 10.2. This will most likely be truncated to 10 as short images do
not handle decimal points. More interesting artifacts occur when the value is outside the range of the type. For example,
consider the setting the value of a voxel in an 1-byte image to 300.
239
CHAPTER 22. MORE ADVANCED IMAGE ALGORITHM/FILTERS AND TEMPLATED
EXECUTION Draft Sep. 2009
22.2 The VTK Object Hierarchy
22.2.1 The vtkAlgorithmClass and its Helpers
As stated in the VTK manual, vtkAlgorithm is the parent class for all sources, lters, and sinks in VTK. It
denes a generalized interface for executing data processing algorithms. Pipeline connections are associated with
input and output ports that are independent of the type of data passing through the connections. Instances may
be used independently or within pipelines with a variety of architectures and update mechanisms. Pipelines are
controlled by instances of vtkExecutive. Every vtkAlgorithm instance has an associated vtkExecutive when it is
used in a pipeline. The executive is responsible for data ow.
A general vtkAlgorithm can have an arbitrary number of inputs and outputs of varying types (e.g. vtkImageData,
vtkPolyData etc). In general on both the input and output sides of the algorithm, the algorithm has ports (i.e.
InputPorts and OutputPorts) which are used to connect this to the rest of the pipeline. These enable the setting
of inputs and the getting of the outputs of the algorithm. The algorithm has as many input ports as it has inputs
and as many output ports as it has outputs. For example a lter that takes two images as input, adds them and
outputs the sum as an output will have 2 input ports and 1 output port.
vtkInformation: The information about the data types is store in a vtkInformation object (which we saw 3
chapters back briey). This contains things, in the case of images, such as the potential output dimensions,
output spacing etc. of the algorithm.
vtkInformation objects are stored together in vtkInformationVector objects. Each vtkAlgorithm has two arrays of
these namely:
vtkInformationVector* InputPortInformation;
vtkInformationVector* OutputPortInformation;
These arrays contain one vtkInformationVector for each port (either input, or output respectively). Each vtkIn-
formationVector in turn contains a number of vtkInformation objects (one per connection to the port).
Lets refer back now to chapter 19, as per the code below:
int vtkImageExtractVOI::RequestInformation(vtkInformation *request,
vtkInformationVector **inputVector,
vtkInformationVector *outputVector)
{
vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
vtkInformation *outInfo = outputVector->GetInformationObject(0);
Here inInfo is the vtkInformation object for input port 0, connection 0. Similarly outInfo is the equivalent for
output port 0, connection 0. In many cases, (e.g. classes derived from vtkImageAlgorithm such as vtkSimpleIm-
ageToImageFilter), we have by default a single input port and a single output port. This makes life simpler.
One we have a pointer to the appropriate vtkInformation object, then the lter can make appropriate decisions,
provide necessary information about what it will do. The best documentation for this, unfortunately, is in the
source code.
240
CHAPTER 22. MORE ADVANCED IMAGE ALGORITHM/FILTERS AND TEMPLATED
EXECUTION Draft Sep. 2009
Accessing and Manipulating the Ports: Key aspects of the interface of vtkAlgorithm for communicating
through the ports involves the methods (abbreviated from vtkAlgorithm.h) below:
1. vtkInformation* GetInputPortInformation(int port); this returns the information object asso-
ciated with an input port. There is one input port per kind of input to the algorithm. Each input port tells
executives what kind of data and downstream requests this algorithm can handle for that input.
2. vtkInformation* GetOutputPortInformation(int port); Get the information object associated
with an output port. There is one output port per output from the algorithm. Each output port tells
executives what kind of upstream requests this algorithm can handle for that output.
3. int GetNumberOfInputPorts(); and int GetNumberOfOutputPorts(); These output the number
of input and output ports:
4. vtkDataObject* GetOutputDataObject(int port); Get the data object that will contain the algo-
rithm output for the given port.
5. vtkDataObject *GetInputDataObject(int port, int connection); Get the data object that
will contain the algorithm input for the given port and given connection.
6. virtual void SetInputConnection(int port, vtkAlgorithmOutput* input); and
void SetInputConnection(vtkAlgorithmOutput* input); These methods represent the new way
to set the inputs of a lter. The old style SetInput methods are still supported in many derived classes.
2
7. vtkAlgorithmOutput* GetOutputPort(int index); and vtkAlgorithmOutput* GetOutputPort().
These return the output of the lter. Note that these are not vtkDataObjects but richer structures con-
taining more pipeline info.
8. vtkDataObject* GetOutputDataObject(int port) this method can be used to get the actual output
vtkDataObject. Many derived class also denes the more legacy style GetOutput function.
9. vtkDataObject *GetInputDataObject(int port, int connection); this is the input equivalent
of the previous method.
22.2.2 Imaging Filters and the VTK Pipeline
Almost all Image-related algorithms derive directly or indirectly from the vtkImageAlgorithm class. The exception
to this rule is old-style legacy code (from VTK 4.4) which derives from vtkImageToImageFilter (whose ancestry
is vtkAlgoritm vtkProcessObject vtkSource vtkImageSource vtkImageToImageFilter!)
Implementing an Image-to-image lter or perhaps an image reader or writer class (e.g. reading in Analyze les)
is most easily done by deriving from one of the following classes.
vtkImageAlgorithm this is the generic parent class for everything else.
vtkImageReader2 this is a good parent class for classes that read in images.
vtkImageWriter this is a good parent class for classes that write images to le.
vtkSimpleImageToImageFilter this is the simplest way to do this. It operates on the whole image (i.e. no
multithreading) we have seen examples of this before.
vtkThreadedImageAlgorithm This can handle threaded execution where the image is split into pieces and
the processing done in parallel on separated threads this is ideal for processing where each pixel is more
less treated independently.
Some Special Functionality in vtkImageAlgorithm and Handling the Pipeline: vtkImageAlgorithm
is more specialized subclass of vtkAlgorithm, where the inputs and outputs are assumed to be of type vtkImageData
(i.e. images!). Some key new methods here are (as dened in vtkImageAlgorithm.h):
vtkImageData* GetOutput();
vtkImageData* GetOutput(int);
virtual void SetOutput(vtkDataObject* d); Get the output data object for a port on this algorithm.
2
As best as I can tell, if you are creating your vtkDataObjects manually and then using them as inputs to classes, you
still need to use SetInput type methods.
241
CHAPTER 22. MORE ADVANCED IMAGE ALGORITHM/FILTERS AND TEMPLATED
EXECUTION Draft Sep. 2009
void SetInput(vtkDataObject *);
void SetInput(int, vtkDataObject*);
Set an input of this algorithm.
The pipeline interface is dened by the three methods:
int RequestInformation(vtkInformation* request,
vtkInformationVector** inputVector, vtkInformationVector* outputVector);
int RequestUpdateExtent(vtkInformation*,vtkInformationVector**,vtkInformationVector*);
int RequestData(vtkInformation *request,
vtkInformationVector** inputVector,vtkInformationVector* outputVector);
Essentially these three methods handle the pipeline update process.
Consider the case of a pipeline consisting of three algorithms A B C.
1. The rst step in the pipeline the so called Request Information pass i.e. the downstream lter inquires
of the upstream object as to what it can/will produce. In this case C calls the RequestInformation method
of B and similarly B calls the method of A.
2. The second step in the pipeline is the so called RequestUpdateExtent pass where the upstream object
tells the downstream object now requests a specic piece of the output (if threaded execution is used).
Again C asks for a piece of data from B which then asks for the necessary piece of data from A etc.
3. Finally, in the third step the RequestData step, the data is produced (i.e. the actual ltering operation!)
and fed forward from A to B to C.
For a lter to connect to the pipeline properly it must be able to handle all three of these steps. vtkImageAlgorithm
denes a basic implementation for these which is then overridden in derived classes.
22.2.3 A concrete example from vtkSimpleImageToImageFilter
1. RequestInformation This is actually dened in vtkImageAlgorithm. It assumes that the output image
will have exactly the same properties as the input image, hence things just get copied over!
int vtkImageAlgorithm::RequestInformation(
vtkInformation* request,
vtkInformationVector** inputVector,
vtkInformationVector* outputVector)
{
// do nothing except copy scalar type info
this->CopyInputArrayAttributesToOutput(request,inputVector,outputVector);
return 1;
}
2. RequestUpdateExtent this lter can only produce 1 piece (i.e. the whole image, no threading is
available), so we tell the down stream lter that this is all it can get!
int vtkSimpleImageToImageFilter::RequestUpdateExtent (
vtkInformation * vtkNotUsed(request),
vtkInformationVector **inputVector,
vtkInformationVector *vtkNotUsed( outputVector ))
242
CHAPTER 22. MORE ADVANCED IMAGE ALGORITHM/FILTERS AND TEMPLATED
EXECUTION Draft Sep. 2009
{
vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
// always request the whole extent
inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(),
inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT()),6);
return 1;
}
3. RequestData in vtkSimpleImageToImageFilter, this method serves to take the complex VTK pipeline
interface and reduce it to calling a simpler method SimpleExecute which does the work. This is dened in the
derived classes, see examples from previous chapters.
virtual void SimpleExecute(vtkImageData* input, vtkImageData* output) = 0;
The top part of the code extracts the information from the pipeline and checks the validity of the input. Then
the output data is allocated nally SimpleExecute is called.
int vtkSimpleImageToImageFilter::RequestData(
vtkInformation* vtkNotUsed( request ),
vtkInformationVector** inputVector,
vtkInformationVector* outputVector)
{
// get the data object
vtkInformation *outInfo = outputVector->GetInformationObject(0);
vtkImageData *output = vtkImageData::SafeDownCast(
outInfo->Get(vtkDataObject::DATA_OBJECT()));
vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
vtkImageData *input = vtkImageData::SafeDownCast(
inInfo->Get(vtkDataObject::DATA_OBJECT()));
int inExt[6];
input->GetExtent(inExt);
// if the input extent is empty then exit
if (inExt[1] < inExt[0] || inExt[3] < inExt[2] || inExt[5] < inExt[4])
{ return 1; }
// Set the extent of the output and allocate memory.
output->SetExtent(
outInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT()));
output->AllocateScalars();
thisSimpleExecute(input, output);
243
CHAPTER 22. MORE ADVANCED IMAGE ALGORITHM/FILTERS AND TEMPLATED
EXECUTION Draft Sep. 2009
return 1;
}
22.3 A Threaded Algorithm vtkImageShiftScale
The vtkThreadedImageAlgorithm class is the parent class from which multithreaded (i.e. parallel, able to run
using multiple threads concurrently) VTK image processing algorithms are commonly derived from. The code
in this is not for the faint-hearted. We simply note that essentially this class reroutes the pipeline such that its
derived classes need to dene a method:
virtual void ThreadedRequestData(vtkInformation *request,
vtkInformationVector **inputVector,
vtkInformationVector *outputVector,
vtkImageData ***inData,
vtkImageData **outData,
int extent[6], int threadId);
which will handle the requests for updates on pieces of the output image. We will look at a concrete example,
vtkImageShiftScale.
22.3.1 The Header vtkImageShiftScale.h
vtkImageShift takes an image I as input and outputs an image J of the form J = (I + a) b, where a is
the shift and b is called the scale. This makes it embarrassingly parallel-izable, since each voxel can more or
less be processed individually. This lter derives from vtkThreadedImageAlgorithm. The header le is fairly
straight-forward:
#include "vtkThreadedImageAlgorithm.h"
class vtkImageShiftScale : public vtkThreadedImageAlgorithm
{
public:
static vtkImageShiftScale *New();
vtkTypeRevisionMacro(vtkImageShiftScale,vtkThreadedImageAlgorithm);
void PrintSelf(ostream& os, vtkIndent indent);
// Description:
// Set/Get the shift value.
vtkSetMacro(Shift,double);
vtkGetMacro(Shift,double);
// Description:
// Set/Get the scale value.
vtkSetMacro(Scale,double);
vtkGetMacro(Scale,double);
244
CHAPTER 22. MORE ADVANCED IMAGE ALGORITHM/FILTERS AND TEMPLATED
EXECUTION Draft Sep. 2009
// Description:
// Set the desired output scalar type. The result of the shift
// and scale operations is cast to the type specified.
vtkSetMacro(OutputScalarType, int);
vtkGetMacro(OutputScalarType, int);
void SetOutputScalarTypeToDouble()
{this->SetOutputScalarType(VTK_DOUBLE);}
// Omitted Code ...
// Description:
// When the ClampOverflow flag is on, the data is thresholded so that
// the output value does not exceed the max or min of the data type.
// By default, ClampOverflow is off.
vtkSetMacro(ClampOverflow, int);
vtkGetMacro(ClampOverflow, int);
vtkBooleanMacro(ClampOverflow, int);
protected:
vtkImageShiftScale();
~vtkImageShiftScale();
double Shift;
double Scale;
int OutputScalarType;
int ClampOverflow;
virtual int RequestInformation(vtkInformation*,vtkInformationVector**,
vtkInformationVector*);
virtual void ThreadedRequestData(vtkInformation*,vtkInformationVector**,
vtkInformationVector*,vtkImageData*** inData,
vtkImageData** outData,
int outExt[6],
int threadId);
private:
vtkImageShiftScale(const vtkImageShiftScale&); // Not implemented.
void operator=(const vtkImageShiftScale&); // Not implemented.
};
The key things to note are the two methods near the bottom namely (i) RequestInformation and (ii) Thread-
edRequestData. The rst is trivial in this case and is used to set the output scalar type (if dierent from the
input).
int vtkImageShiftScale::RequestInformation(vtkInformation*,
vtkInformationVector**,
vtkInformationVector* outputVector)
{
// Set the image scalar type for the output. -1 = same as input, so do nothing
if(this->OutputScalarType != -1)
{
245
CHAPTER 22. MORE ADVANCED IMAGE ALGORITHM/FILTERS AND TEMPLATED
EXECUTION Draft Sep. 2009
vtkInformation* outInfo = outputVector->GetInformationObject(0);
vtkDataObject::SetPointDataActiveScalarInfo(
outInfo, this->OutputScalarType, -1);
}
return 1;
}
22.3.2 Templated Execution
Unlike ITK, which we will discuss in the next chapter, VTK does not use templates in the class interface. This
results in the VTK lter classes not being templated in themselves. However, VTK does use templates in the
implementation of some of the code for eciency. Since, however, the classes themselves are not templated, such
lters cannot have templated member functions. The solution to this quandary is the use of ordinary, specially
named templated procedures, inserted in the VTK source which are called from within the member functions.
These procedures are often specially named to reduce the likelihood of naming conicts at link time. (Naming
conicts occur when more than one function/procedure has the same exact name and argument list). In general
their names begin with the class name of the class whose methods will call them. This is best illustrated by means
of the following example.
In vtkImageShiftScale, the input point to the lter is the ThreadedRequestData method (which is the rough
equivalent of SimpleExecute in the classes deriving from vtkSimpleImageToImageFilter).
This then calls an ordinary function called vtkImageShiftScaleExecute1 which is templated by the type of the
input image. This in turn calls a second function vtkImageShiftScaleExecute which is templated over both
the type of the input image and the type of the output image. The reason for this double hop, is that we use
(implicitly) switch statements to check for data type. Doing this in a single step, would require N
2
cases (where
N is the number of possible data types) whereas doing this in two steps reduces the coding complexity to N
followed by N more (= 2N total cases).
The key to understanding the code below is to understand the operation of the vtkTemplateMacros.
Step 1: ThreadedRequestData: This is the main method of the lter. The method takes seven inputs: (i)
Inputs 1-3 are the necessary information vectors etc., inputs 4-5 are the input and output arrays.
More interestingly, outExt[6] is an array species the part of the image to update and threadId this is the
thread id.
In multi-threaded lters, the execution the lter can be split over a number of processors, if these are available.
To accomplish this, the output is split into pieces, and each thread is responsible for computing the result for its
piece. For example, in the case of this lter, vtkImageShiftScale, thread 1 may be computing the output for the
top half of the image and thread 2 for the bottom half. (Naturally not all operations can be multi-threaded.).
outExt[6] denes the region over which this thread is responsible. It is a six-component array of the form
(xmin, xmax, ymin, ymax, zmin, zmax).
void vtkImageShiftScale::ThreadedRequestData(vtkInformation*,
vtkInformationVector**,
vtkInformationVector*,
vtkImageData*** inData,
vtkImageData** outData,
int outExt[6],
int threadId)
{
246
CHAPTER 22. MORE ADVANCED IMAGE ALGORITHM/FILTERS AND TEMPLATED
EXECUTION Draft Sep. 2009
vtkImageData* input = inData[0][0];
vtkImageData* output = outData[0];
switch(input->GetScalarType())
{
vtkTemplateMacro(
vtkImageShiftScaleExecute1(this, input, output, outExt, threadId,
static_cast<VTK_TT*>(0)));
default:
vtkErrorMacro("ThreadedRequestData: Unknown input ScalarType");
return;
}
}
The messy part of this function is the use of the vtkTemplateMacro. Expanding this macro would result (approx-
imately) in code of the form:
switch (inData->GetScalarType()) {
// BEGIN MACRO
case double:
vtkImageShiftScaleExecute1(this,input,output,outExt,id,static_cast<double *>0);
break;
case float:
vtkImageShiftScaleExecute1(this,input,output,outExt,id,static_cast<float *>0);
break;
// OMMITTED CODE
// similarly for long, unsigned long, int, unsigned int, short,
// unsigned short, char and unsigned char
// END MACRO
default:
vtkErrorMacro(<< "Execute: Unknown ScalarType");
return;
}
Also note, that since vtkImageShiftScaleExecute1 is not a member function of vtkImageShiftScale, we pass
the this pointer as an explicit argument, so that this function can access class data members for more information
otherwise they would all have to be passed as parameters.
Step 2: vtkImageShiftScaleExecute1
Please note that this function is not a member of vtkImageShiftScale. It is an ordinary c-like function which simply
happens to be in the same le as the implementation of vtkImageShiftScale. This enable its use without necessary
needing to declare it in any header le this follows the solid principles of PIMPL or private implementation!
template <class T>
void vtkImageShiftScaleExecute1(vtkImageShiftScale* self,
vtkImageData* inData,
vtkImageData* outData,
int outExt[6], int id, T*)
247
CHAPTER 22. MORE ADVANCED IMAGE ALGORITHM/FILTERS AND TEMPLATED
EXECUTION Draft Sep. 2009
{
switch (outData->GetScalarType())
{
vtkTemplateMacro(
vtkImageShiftScaleExecute(self, inData,
outData, outExt, id,
static_cast<T*>(0),
static_cast<VTK_TT*>(0)));
default:
vtkErrorWithObjectMacro(
self, "ThreadedRequestData: Unknown output ScalarType");
return;
}
}
This function is templated using type T, which is the type of the input image (e.g. short, oat etc.). It then uses
the vtkTemplateMacro macro which creates another set of case statements where this time the conditional is on
the output type.
This switch method ends up calling the nal function vtkImageShiftScaleExecute, where the actual work will take
place.
Step 3: vtkImageShiftScaleExecute
vtkImageShiftScaleExecute is were the seemingly trivial work of addition and multiplication takes place. This
is a double-templated function over input type IT and output type OT. Its rst argument is a pointer to the
instance of the class from which it was called, whereas the rest are information about the operation. The last
two arguments are zeros and are unused, the important aspect of these last two arguments is that they explicitly
dene the input and output types for the templating.
The code follows I have rearranged it marginally for greater clarity. First we get the key class parameters from
the calling class, using the self pointer:
template <class IT, class OT>
template <class IT, class OT>
void vtkImageShiftScaleExecute(vtkImageShiftScale* self,
vtkImageData* inData,
vtkImageData* outData,
int outExt[6], int id,
IT*, OT*)
{
// Get the shift and scale parameters values.
double shift = self->GetShift();
double scale = self->GetScale();
// Clamp pixel values within the range of the output type.
int clamp = self->GetClampOverflow();
Next we get the range of types for the current type. These would be 0 and 255 in the case of unsigned char, 0
to 65535 for short etc.
248
CHAPTER 22. MORE ADVANCED IMAGE ALGORITHM/FILTERS AND TEMPLATED
EXECUTION Draft Sep. 2009
// for preventing overflow
double typeMin = outData->GetScalarTypeMin();
double typeMax = outData->GetScalarTypeMax();
Next we create two iterators. Iterators are special classes (originally dened in the C++ Standard Template
Library) that allow the easy looping through data structures of arbitrary complexity. Iterators are the modern
way of direct pointer manipulation.
In this case, the iterators are not pointers but are simply allocated on the stack (i.e. like ordinary variables). The
rst iterator (inIT) is simply used to traverse through the input image, whereas the second iterator (outIt) does
the same job for the second image. The second iterator is of type vtkImageProgressIterator and in addition to
looping over the image, periodically calls the self->UpdateProgress() method to keep the user informed of
the process of the lter.
The iterator breaks the image region (dened by outExt) over which it will iterate into a number of continuous
(in memory) parts called spans. For example if we have as input a 161616 image and outExt=[0, 7, 0, 7, 0, 7],
the rst span would be from voxel (0, 0, 0) to (7, 0, 0). At this point, we will need to jump to (0, 1, 0) and begin
a second span.
// Create iterators for the input and output extents assigned to
// this thread.
vtkImageIterator<IT> inIt(inData, outExt);
vtkImageProgressIterator<OT> outIt(outData, outExt, self, id);
// Loop through output pixels.
while (!outIt.IsAtEnd())
{
IT* inSI = inIt.BeginSpan();
OT* outSI = outIt.BeginSpan();
OT* outSIEnd = outIt.EndSpan();
if (clamp)
{
while (outSI != outSIEnd)
{
// Pixel operation
double val = (static_cast<double>(*inSI) + shift) * scale;
if (val > typeMax)
{
val = typeMax;
}
if (val < typeMin)
{
val = typeMin;
}
*outSI = static_cast<OT>(val);
++outSI;
++inSI;
}
}
else
{
249
CHAPTER 22. MORE ADVANCED IMAGE ALGORITHM/FILTERS AND TEMPLATED
EXECUTION Draft Sep. 2009
while (outSI != outSIEnd)
{
// Pixel operation
*outSI = static_cast<OT>((static_cast<double>(*inSI) + shift) * scale);
++outSI;
++inSI;
}
}
inIt.NextSpan();
outIt.NextSpan();
}
}
The work of the lter is done by the middle portion. The input value can be accessed by pointer dereferencing
(.e.g. *inSI). This could be written in longhand as:
// Get input
IT inval = (IT) (*inSI);
// Perform the operation in double
double val = ((double)(*inSI) + shift) * scale;
// If we are checking for clamping verify legal range
if (clamp){
if (val > typeMax)
val = typeMax;
if (val < typeMin)
val = typeMin;
}
// Set the Output
*outSI = (OT)(val);
Note: The two functions vtkImageShiftScaleExecute1 and vtkImageShiftScaleExecute do not appear in any header
(.h) le. They are an example of private implementation, and are only known to other functions in the same source
(.cpp) le. However, in this case, the order in which the functions appear in the .cpp le is important. A function
can not call a function that has not already been dened or at the very least its interface has been dened.
Hence, in the source le, vtkImageShiftScaleExecute appears rst followed by vtkImageShiftScaleExecute1 and
vtkImageShiftScale::ThreadedExecute appears last.
Unfortunately, for many lters, the VTK iterators are not suciently powerful. Many lters (e.g. vtkImageGaus-
sianSmooth) rely on direct pointer manipulations instead.
22.4 STL Notes:
If you want understand the operations of these iterators better, I suggest reading through some of the tutorials
on the STL e.g. http://www.cprogramming.com/tutorial/stl/iterators.html. The STL denes many
interesting classes, that can be of great use. It is, unfortunately beyond the scope of this introductory material.
250
Draft Sep. 2009
Chapter 23
Copying Data Objects and Breaking the
Pipeline
23.1 Introduction
Most of the VTK examples you will see are essentially single pipelines. This gives the appearance that the only
way to process data in VTK is to take raw data as an input, pass it through various lters for manipulation and
display the output in a single connected pipeline. In larger projects, however, we often need to do a small amount
of processing and return the result. Later this result, perhaps as a response to some user input via a GUI, will
then be processed some more to yield a dierent output etc.
In this chapter we discuss ways of breaking the single pipeline and returning intermediate results. Key to this
process is the ability to copy data-objects. We rst discuss three dierent methods for doing this at varying
levels of completenesss. This is followed by two concrete examples. The rst shows how to properly extract an
isosurface from an image and the second how to compute a gradient of smoothed image. In both examples, the
focus is on proper implementation and returning of the data-object as opposed to the details of the algorithms
themselves.
23.2 CopyStructure, ShallowCopy and DeepCopy
It is often desirable to make a copy of a dataset. Datasets such as vtkPolyData and vtkImageData support, at
least, three dierent means of copying. These are:
1. CopyStructure: This copies the geometric structure of an input data-set. In the case of vtkImageData
this simply copies the image dimensions, origin, spacing, number of scalar components and data type (and
a few other miscellaneous members). It essentially creates an image of the same size as the input, but
does not allocate memory. This is very useful as an initialization of a lter method, where the output image
is of the same size and type as the input image:
vtkImageData* out=vtkImageData::New();
out->CopyStructure(input);
// Now modify as desired
out->SetScalarTypeToFloat();
// At this point only allocate memory
out->AllocateScalars();
251
CHAPTER 23. COPYING DATA OBJECTS AND BREAKING THE PIPELINE Draft Sep. 2009
2. ShallowCopy: ShallowCopy is essentially the same as CopyStructure with the key addition that all array
data (e.g. intensities) are linked! Shallow Copy for a vtkImageData (in a very simplied form)
vtkImageData* out=vtkImageData::New();
out->ShallowCopy(input);
// or equivalently
out->CopyStructure(input);
out->GetPointPointData()->SetScalars(input->GetPointData()->GetScalars());
All pointer-based objects, which include all vtkDataArray structures are simply passed as pointers (with
appropriate reference count increases). No new memory is allocated to store these. Any modication to
the intensities in out, also modies the intensities in input as their intensities are stored in the exact
same array!
ShallowCopy is extremely useful for preserving the results of a lter or a combination of lters (a pipeline)
while destroying the actual pipeline. This is illustrated in the two examples later in this document.
3. DeepCopy: DeepCopy creates a complete duplicate version of a data-object. This allocates all necessary
memory and creates a separate but identical object to the input.
vtkImageData* out=vtkImageData::New();
out->DeepCopy(input);
Use DeepCopy sparingly, unless you absolutely need a complete duplicate copy of an image prior to modi-
cation.
23.3 Example 1: Extracting a Surface from a LevelSet Function
Consider, for example, an implementation of a Levelset segmentation algorithm, which results in a distance
map (or levelset function) stored in an image LevelsetImage. Either during the evolution of the levelset, or at
the end, we may need to return a surface extracted from the zero-levelset. (The zero-levelset conventionally
represents the output surface of the segmentation.) This may be conveniently implemented in a member function
GetZeroSurface.
A rst attempt at implementing this function can take the form:
vtkPolyData* vtkMyLevelsetFilter::GetZeroSurface() {
// this->LevelsetImage is of type vtkImageData
vtkContourFilter *ContourFilter = vtkContourFilter::New();
ContourFilter->SetInput(this->LevelsetImage);
ContourFilter->SetValue(1, 0.0);
ContourFilter->Update();
return ContourFilter->GetOutput();
}
This will work, but there is a key problem associated with it. The problem is that we return a pointer to the
output of a lter, without returning the actual lter itself. This can mess up the reference counting scheme in
VTK.
252
CHAPTER 23. COPYING DATA OBJECTS AND BREAKING THE PIPELINE Draft Sep. 2009
Consider the case where we call this lter once. The lter ContourFilter is rst created. Then we set the image
this->LevelsetImage as its input, which results in the reference counter of the image being incremented by
one.
Then we call the lters Update function. This, incidentally, is critical. VTK pipelines operate on a lazy executing
scheme, so the lter will not do anything unless it has to, using Update forces the lter to go to work. (In normal
pipelines, i.e. source to display, updating the display propagates an update event backwards through the pipeline
and forces all intermediate lters to update!).
The lter results in a surface (vtkPolyData) which is then returned to the user.
Next time we call the lter, we create a new ContourFilter object and set this->LevelsetImage as its input,
which results in the reference counter of the image, again, being incremented by one.
If we call this function 100 times then LevelsetImage will have a reference count of 100 which means that it
will never be deleted even when the LevelsetFilter class is deleted, resulting in a potential memory leak in a large
piece of software.
The correct solution to this problem, is to copy the result of the lter output, delete the lter, and return this
copy.
vtkPolyData* vtkMyLevelsetFilter::GetZeroSurface()
{
vtkContourFilter *ContourFilter = vtkContourFilter::New();
ContourFilter->SetInput(this->LevelsetImage);
ContourFilter->SetValue(1, 0.0);
ContourFilter->Update();
vtkPolyData* zerosurface=vtkPolyData::New();
zerosurface->ShallowCopy(ContourFilter->GetOutput());
ContourFilter->Delete();
return zerosurface;
}
Here, we rst create a temporary surface (zerosurface). Next we perform a shallow copy operation which copies
the contents of the output of the ContourFilter to this temporary surface. Then the ContourFilter is deleted,
cleaning up all reference counting issues. At this point we return the zerosurface object to the calling code.
The calling function is then responsible to delete the zerosurface object when it is done with it, this object has
no attachments to any lingering pipeline code.
The Recipe: In the many cases where one needs to use a VTK pipeline to generate an output data structure
(as opposed to an output display or le) and return it, the following recipe can be very useful:
Create the pipeline
Call the Update function of the last lter this will invoke, in turn, the Update functions of all the
previous lters
Create a new output structure (e.g. image or surface most likely)
ShallowCopy the output of the nal lter to the new output structure.
Delete all lters in the pipeline.
Return the output structure.
253
CHAPTER 23. COPYING DATA OBJECTS AND BREAKING THE PIPELINE Draft Sep. 2009
A slight variation on the above example, in which the level is specied as opposed to assumed to be zero, is given
in the class vtkMyUtility.cpp.
23.4 Example 2: An Image Processing Example
Consider the case where one needs to compute the gradient of an image at a specic scale. This requires (i) rst
smoothing the image and (ii) computing the gradient. This operation can be accomplished by a pipeline shown
below:
vtkImageData* vtkMyUtility::SmoothImageAndComputeGradient(vtkImageData* input,
double sigma,int dimensionality){
vtkImageGaussianSmooth* sm=vtkImageGaussianSmooth::New();
sm->SetInput(input);
sm->SetStandardDeviations(sigma,sigma,sigma);
sm->SetDimensionality(dimensionality);
vtkImageGradient* gradient=vtkImageGradient::New();
gradient->SetInput(sm->GetOutput());
sm->Delete();
gradient->SetDimensionality(dimensionality);
gradient->Update();
vtkImageData* grad=vtkImageData::New();
grad->ShallowCopy(gradient->GetOutput());
gradient->Delete();
return grad;
}
Note that we follow the same recipe. The pipeline is rst created. Then, the Update function of the last lter
gradient->Update() is called to force execution. Next, we create a new output data structure (grad) and
perform the shallow copy operation. The pipeline is deleted as usual and the output image is then returned.
23.5 Implementation
Both of these examples are implemented in a class vtkMyUtility. The header le of this has the form:
class vtkMyUtility : public vtkObject {
public:
static vtkMyUtility *New();
vtkTypeMacro(vtkMyUtility,vtkObject);
// Example 1 -- Extract Iso-Contour
vtkPolyData* ExtractContour(vtkImageData *img,double level);
// Example 2 -- Smooth Image and Compute Gradient
static vtkImageData* SmoothImageAndComputeGradient(vtkImageData* img,double sigma,
int dimensionality);
protected:
254
CHAPTER 23. COPYING DATA OBJECTS AND BREAKING THE PIPELINE Draft Sep. 2009
};
A script (script23-1.tcl) exercises these functions and outputs a surface and a gradient image respectively.
23.6 Note:
There is a bug in VTK 5.2 in the ShallowCopy method of vtkImageData. We have this patched in the version of
VTK you get with BioImage Suite 3 and are in the process of submitting a x to VTK itself.
255
Draft Sep. 2009
Chapter 24
The Insight Toolkit
24.1 Introduction
The Insight Toolkit (ITK) is an open source software toolkit for registration and segmentation. It is implemented
in C++ and uses the CMake build environment in fact CMake was developed for the ITK project. ITK was
started in 1999 under a contract by the US National Library of Medicine of the National Institutes of Health to
a consortium of academic and industrial partners.
The ITK software guide [?], which is available at the ITK webpage www.itk.org, has a good description of the
algorithmic content of ITK. A set of presentations submitted to the Insight Journal is another good resource
(http://hdl.handle.net/1926/161).
While ITK can be thought o as a rst-cousin of VTK, there is one critical dierence. ITK is implemented
using generic programming principles. It uses templates both for the algorithm implementation and, unlike VTK,
the class interfaces themselves. This type of heavily templated C++ code challenges many compilers and it can
take much longer to compile. The other dierence, is that the memory model depends on smart pointers that
maintain a reference count to objects. Smart pointers can be allocated on the stack, and when scope is exited,
the smart pointers disappear and decrement their reference count to the object that they refer to. There is no
need to call itkFilter->Delete(), unlike VTK lters.
The use of ITK, especially by beginners, is more challenging than VTK. The use of generic programming techniques
assumes a rm grounding in templated programming in general, and the Standard Template Library in particular.
The use of templated lters also makes the use of the toolkit from languages other than C++ less elegant than
VTK.
The use of templates in the interface has one negative consequence. While the use of templated classes can
simplify the lter design there is no need for multiple switch statements as is the case in VTK templated
implementations it results in the need to explicitly specify image types at compile time. In contrast, in VTK
one can allocate a vtkImageData object rst, then dynamically set its type, and potentially even change its type
later. In ITK images need to be allocated explicitly with a xed type at compile time, which is a limitation in
using it to develop larger systems.
To elaborate this further, consider the case of allocating an image. In VTK, this is accomplished as:
vtkImageData* img=vtkImageData::New();
img->SetScalarTypeToFloat();
256
CHAPTER 24. THE INSIGHT TOOLKIT Draft Sep. 2009
By contrast, in ITK, the same task requires either:
itk::Image< float , 3 >::Pointer img=itk::Image< float , 3 >::New();
where 3 is the image dimension and oat is the image type. Alternatively, we can use the typedef construct to
create a shorthand for the complex type. This results in the following code segment:
typedef itk::Image< float , 3 > ImageType
typename ImageType::Pointer img=ImageType::New();
As you can see, things can get ugly pretty quickly.
1
Learning to program using ITK, i.e. implementing algorithms by leveraging ITK code, is beyond the scope of this
class. However, there are a lot of algorithms in ITK that can be usefully exploited for many tasks. In addition ITK
has a nice image I/O framework which supports a large number of image formats. In the rest of this handout,
we will focus on using ITK in a VTK-centric environment. This will be accomplished by hiding ITK code inside
functions which take VTK images as inputs and return VTK images as outputs. Within such functions, we can
convert the images to ITK data structures, do the operation and convert. In this way ITK code is safely packaged
in a VTK wrapper.
The techniques we will use to accomplish this task are similar to those described in our discussion on templated
VTK lters. We will again use ordinary specially named templated functions, inserted in VTK source which are
called from within the member functions. All ITK code will reside primarily within these templated functions.
24.2 The vtkITKMyUtility Class
This is a simple class which implements three static member functions: (i) CurvatureAnisotropicDiusion this
calls an ITK smoothing lter by the same name, (ii) LoadImage this can be used to load an image using the
ITK IO Factory and (iii) SaveImage this can be used to save an image. In the case of the last two functions, the
le type is automatically determined by the image name! Note that the header below has no traces of anything
to do with ITK:
#include <vtkObject.h>
class vtkImageData;
class vtkITKMyUtility : public vtkObject {
public:
static vtkITKMyUtility *New();
vtkTypeMacro(vtkITKMyUtility,vtkObject);
static vtkImageData* CurvatureAnisotropicDiffusionFilter(vtkImageData* input,
double Conductance=1.0,
double TimeStep=0.15,int NumberOfIterations=8);
1
It must be acknowledged that the ability to have code that can handle images of arbitrary types and dimensions has
its own aesthetic appeal. However, inside the code all the types are of some user dened type which can be dizzying until
one gets used to it.
257
CHAPTER 24. THE INSIGHT TOOLKIT Draft Sep. 2009
static vtkImageData* LoadImage(char* fname);
static int SaveImage(vtkImageData* input,char* filename);
protected:
vtkITKMyUtility() {};
virtual ~vtkITKMyUtility() {};
};
Each of the three static member functions calls special templated utility ordinary functions placed in vtkITKMyU-
tility.cpp to do the templated operations. Within these ordinary functions we will make use of two convenience
classes that come as part of a supplementary distribution called InsightApplications that is also available from the
ITK web-page. These classes are VTKImageToImageFilter and ImageToVTKImageFilter. The notation used here
is that the word Image signies an ITK image, whereas the word VTKImage is used to signify a vtkImageData
structure.
These lters make use of pairs of classes known as importers and exporters. These can export and import an
image from/to a naked C-like pointer. VTK has a pair of these classes called vtkImageExport and vtkImageImport.
They convert from VTK Images to ITK Images and back.
24.3 Curvature Anisotropic Diusion Filtering
This is a type of nonlinear image smoothing that tries to smooth uniform areas while preserving sharp disconti-
nuities. It is available in ITK as part of the itk::CurvatureAnisotropicDiffusionImageFilter class.
2
The Filtering Function: The real work is handled by the following doubly-templated function note that
this is not a member of vtkITKMyUtility but just an ordinary function:
template <class IT,int dimension>
void vtkITKMyUtilityCurvatureAnisotropicDiffusionSmoothImage(vtkImageData* input,
vtkImageData* output,
double Conductance,
double TimeStep,
int NumberOfIterations)
{
// Define the parts (types) of the ITK pipeline.
typedef itk::Image<IT, dimension> ImageType;
typedef itk::VTKImageToImageFilter<ImageType> VTKImageToImageFilterType;
typedef itk::ImageToVTKImageFilter<ImageType> ImageToVTKImageFilterType;
typedef itk::CurvatureAnisotropicDiffusionImageFilter<ImageType, ImageType > FilterType;
// From VTK to ITK
typename VTKImageToImageFilterType::Pointer importer=VTKImageToImageFilterType::New();
importer->SetInput(input);
importer->Update();
// Create the itk::CurvatureAnisotropicDiffusionImageFilter and connect it
typename FilterType::Pointer filter = FilterType::New();
2
ITK also makes use of C++ namespaces these are similar to the Tcl namespaces that we looked at earlier.
258
CHAPTER 24. THE INSIGHT TOOLKIT Draft Sep. 2009
filter->SetInput(importer->GetOutput());
filter->SetTimeStep(TimeStep);
filter->SetNumberOfIterations(NumberOfIterations);
filter->SetConductanceParameter( Conductance );
filter->Update();
// Back to VTK
typename ImageToVTKImageFilterType::Pointer exporter=ImageToVTKImageFilterType::New();
exporter->SetInput(filter->GetOutput());
exporter->Update();
output->DeepCopy(exporter->GetOutput());
}
The code is fairly straightforward, once one gets over the messy type denitions at the top. To avoid constantly
writing things like Image<IT,dimension>, we create shorthands for all the types at the top using the typedef
operator.
Next we convert the input image, which comes as a vtkImageData to an itkImage using a properly templated
instance of VTKImageToImageFilter. Following this we are into straight ITK code as lifted from one of the
examples that came with ITK. This naturally results in an itkImage which we then convert back to VTK using
an instance of ImageToVTKImageFilter.
Finally the result is copied to an output image using a DeepCopy operation which ensures that the data will still
be around once the pipeline is deleted. Crossing over toolkits has overheads this is one of them.
Note that we do not delete the ITK objects. ITK uses smart pointers which essentially delete themselves. ITK
came into being a few years after VTK and in some respects has some signicant improvements. This is one of
them.
An Intermediate Function: The function above is called by an intermediate function that takes care of the
second template argument, the image dimension. This is fairly straightforward:
template <class IT>
void
vtkITKMyUtilityCurvatureAnisotropicDiffusionSmoothImage1(vtkImageData* indata,
vtkImageData* outdata,double Conductance,double TimeStep,
int NumberOfIterations,int dimension,IT *)
{
if (dimension==2)
vtkITKMyUtilityCurvatureAnisotropicDiffusionSmoothImage<IT,2>(indata,outdata,Conductance,
TimeStep,NumberOfIterations);
else
vtkITKMyUtilityCurvatureAnisotropicDiffusionSmoothImage<IT,3>(indata,outdata,Conductance,
TimeStep,NumberOfIterations);
}
The Class Member Function: The two functions above are buried inside vtkITKMyUtility.cpp and are
inaccessible to the outside world. Outside code access the ltering operation through the following class member
259
CHAPTER 24. THE INSIGHT TOOLKIT Draft Sep. 2009
function:
vtkImageData*
vtkITKMyUtility::CurvatureAnisotropicDiffusionFilter(vtkImageData* input,
double Conductance,double TimeStep,int NumberOfIterations)
{
if (input==NULL)
return NULL;
vtkImageData* output=vtkImageData::New();
output->CopyStructure(input);
int dimension=3;
int dim[3]; input->GetDimensions(dim);
if (dim[2]==1)
dimension=2;
switch (input->GetScalarType())
{
vtkTemplateMacro7(vtkITKMyUtilityCurvatureAnisotropicDiffusionSmoothImage1, input,output,
Conductance,TimeStep,NumberOfIterations,dimension,
static_cast<VTK_TT *>(0));
}
return output;
}
This rst creates the output image. Then it checks whether the input image is really a 2D Image. If it is, the
dimension variable is set to 2 otherwise it stays at 3. Finally, we use the vtkTemplateMacro7 to call the previous
ordinary function. The vtkTemplateMacros were discussed in more detail in chapter 20.
24.4 The LoadImage Function
ITK has a very nice Image Factory IO setup. The user only needs to specify an image lename. The factory
instatiates the appropriate Image Reader class based on the lename (e.g. Analyze, TIF, PNG) and loads the
image.
The workhorse function: As before, we will use a combination of an ordinary templated function and a
non-templated member function to perform the operation. First, the actual method that loads the image:
template <class IT>
vtkImageData* vtkITKMyUtilityLoadImage(char* fname)
{
typedef itk::Image< IT, 3 > ImageType;
typedef itk::ImageToVTKImageFilter<ImageType> ImageToVTKImageFilterType;
typedef itk::ImageFileReader< ImageType > ReaderType;
260
CHAPTER 24. THE INSIGHT TOOLKIT Draft Sep. 2009
typename ReaderType::Pointer reader = ReaderType::New();
reader->SetFileName(fname);
reader->Update();
typename ImageToVTKImageFilterType::Pointer exporter=ImageToVTKImageFilterType::New();
exporter->SetInput(reader->GetOutput());
exporter->Update();
vtkImageData* output=vtkImageData::New();
output->DeepCopy(exporter->GetOutput());
return output;
}
This is similar to the smoothing lter above, other than for the fact that here we only have an output.
The Interface Function: The outside class accesses the Load Image functionality through the following
member function. This has two parts: (i) First, we read the image information to identify the image type using
a trick posted on the ITK-users mailing list. (ii) Then an explicit switch statement is used to call the workhorse
function above with the proper template argument:
vtkImageData* vtkITKMyUtility::LoadImage(char* filename)
{
// Some of this code derives from code posted by Hideaki Hiraki
// on the Insight-users mailing list
itk::ImageIOBase::Pointer imageIO;
imageIO=itk::ImageIOFactory::CreateImageIO(filename, itk::ImageIOFactory::ReadMode);
imageIO->SetFileName(filename);
imageIO->ReadImageInformation();
switch( imageIO->GetComponentType() ){
case itk::ImageIOBase::UCHAR:
return vtkITKMyUtilityLoadImage<unsigned char>(filename);
break;
case itk::ImageIOBase::CHAR:
return vtkITKMyUtilityLoadImage<char>(filename);
break;
case itk::ImageIOBase::SHORT:
return vtkITKMyUtilityLoadImage<short>(filename);
break;
case itk::ImageIOBase::FLOAT:
return vtkITKMyUtilityLoadImage<float>(filename);
break;
// .... Lots more case statements omitted to save space.
}
return NULL;
}
261
CHAPTER 24. THE INSIGHT TOOLKIT Draft Sep. 2009
The implementation of the Save Image function is similar to the smoothing lter and will not be discussed in any
detail here.
24.5 The CMakeLists.txt File
This is an example of a combined VTK/ITK project. Both VTK and ITK must be found.
PROJECT(VTKITK)
SET(KITBASE VTKITK)
SET(KIT vtk${KITBASE})
INCLUDE (${CMAKE_ROOT}/Modules/FindVTK.cmake)
FIND_PACKAGE(VTK REQUIRED)
IF (USE_VTK_FILE)
INCLUDE(${USE_VTK_FILE})
ENDIF(USE_VTK_FILE)
FIND_PACKAGE(ITK REQUIRED)
IF (USE_ITK_FILE)
INCLUDE(${USE_ITK_FILE})
ENDIF(USE_ITK_FILE)
INCLUDE_DIRECTORIES(${VTKITK_SOURCE_DIR})
SET (LIBRARY_OUTPUT_PATH ${VTKITK_SOURCE_DIR})
SET(LIBRARY_SRCS
vtkITKMyUtility.cpp)
LINK_LIBRARIES(
vtkCommon
vtkCommonTCL
${ITK_LIBRARIES})
INCLUDE(${VTK_CMAKE_DIR}/vtkWrapTcl.cmake)
VTK_WRAP_TCL2L (${KIT}TCL LIBRARY_TCL_SRCS ${LIBRARY_SRCS})
ADD_LIBRARY (${KIT}TCL SHARED ${LIBRARY_TCL_SRCS} ${LIBRARY_SRCS})
The script script24-2.tcl is used to exercise the code. This loads an image in the new NIfTI format, smooths
it and saves it out in analyze format!
lappend auto_path [ file dirname [ info script ]]
package require mylibload 1.0
mylibload vtkVTKITKTCL
set util [ vtkITKMyUtility New ]
262
CHAPTER 24. THE INSIGHT TOOLKIT Draft Sep. 2009
set inimg [ $util LoadImage axial_small.hdr ]
puts stderr "Image Loaded [ $inimg GetDimensions ]"
puts stderr "Calling Curvature Anistropic Diffusion Filter"
set img [ $util CurvatureAnisotropicDiffusionFilter $inimg 1.0 0.05 8 ]
puts stderr "Done [ $img GetDimensions ] on to saving"
$util SaveImage $img "itksmooth.nii.gz"
exit
24.6 An integrated lter vtkitkSignedDanielssonDistanceMapImage-
Filter
In this Section we describe an example of wrapping an ITK image-to-image lter inside a VTK image-to-image
lter. This demonstrates a means of leveraging existing ITK within a mostly VTK-based piece of software.
In general our approach consists of:
1. Writing a standard VTK-based lter deriving from vtkSimpleImageToImageFilter.
2. Invoking the ITK code from the SimpleExecute command of the lter. In our case this calls a separate
method RunFilter.
3. The Run Filter method simply checks whether the image is 2D or 3D and calls a templated function (not
a member function of the class) to actually do the processing.
The SignedDanielssonDistanceMapImageFilter takes in a binary image and computes an image whose value is
signed distance map from the boundary of the binary image. This is often useful as an initialization step for
Levelset algorithms. The value is negative inside the object and positive outside. The implementation of the
actual algorithm consists of 3 layers.
1. The SimpleExecute Method: This simply checks for inputs and invokes RunFilter.
void vtkitkSignedDanielssonDistanceMapImageFilter::SimpleExecute(vtkImageData* input,vtkImageData* output)
{
if (input==NULL)
{
vtkErrorMacro(<<"Bad Inputs to vtkitkSignedDanielssonDistanceMapImageFilter");
return;
}
this->RunFilter(input,output);
}
2. The RunFilter Method: This checks whether the image is 2D or 3D and invokes a standard function
vtkitkSignedDanielssonDistanceMapImageFilter RunFilter which is templated over the image dimension (either 2
or 3).
int vtkitkSignedDanielssonDistanceMapImageFilter::RunFilter(vtkImageData* input,vtkImageData* output)
263
CHAPTER 24. THE INSIGHT TOOLKIT Draft Sep. 2009
{
int dim[3]; input->GetDimensions(dim);
if (dim[2]==1)
return vtkitkSignedDanielssonDistanceMapImageFilter_RunFilter<2>(input,output,this);
return vtkitkSignedDanielssonDistanceMapImageFilter_RunFilter<3>(input,output,this);
}
3. The Actual Code: This has six parts:
1. First dene all the types this is part of the joy of running ITK.
2. Take VTK image make it oat and threshold this both accomplished using vtkImageThreshold,
3. Convert VTK image output to ITK image,
4. Run the ITK Code,
5. Convert the ITK output to VTK (note the DeepCopy needed at the end!).
6. Cleanup all VTK lters, ITK lters use smart pointers so they take care of themselves!
1. Create the denitions: The typedef command used to rename messy things to simpler things down the road.
}
template <int ImageDimension>
int vtkitkSignedDanielssonDistanceMapImageFilter_RunFilter(vtkImageData* input,vtkImageData* output,vtkitkSignedDanielssonDistanceMapImageFilter* self)
{
typedef float PixelType;
typedef itk::Image<PixelType,ImageDimension> ImageType;
typedef itk::VTKImageImport<ImageType> ImportType;
typedef itk::VTKImageExport<ImageType> ExportType;
typedef itk::SignedDanielssonDistanceMapImageFilter<ImageType,ImageType> DMapFilterType;
2. Threshold and make oat: The input image is thresholded to ensure that it is binary. We pass the value of
the this pointer to this non-class member function as self so that we can access the necessary parameters for
thresholding. The output of the threshold lter is a oat binary image.
}
vtkImageThreshold* thresholder=vtkImageThreshold::New();
thresholder->SetInput(input);
thresholder->SetOutputScalarTypeToShort();
thresholder->SetOutValue(self->GetOutValue());
thresholder->SetInValue(self->GetInValue());
thresholder->ThresholdBetween(self->GetLowerThreshold(),self->GetUpperThreshold());
thresholder->SetOutputScalarTypeToFloat();
thresholder->ReplaceInOn();
thresholder->ReplaceOutOn();
3. Convert VTK to ITK: This is boilerplate as before.
264
CHAPTER 24. THE INSIGHT TOOLKIT Draft Sep. 2009
}
vtkImageExport *vtkInputExporter = vtkImageExport::New();
vtkInputExporter->SetInput(thresholder->GetOutput());
typename ImportType::Pointer itkInputImporter = ImportType::New();
ConnectPipelines(vtkInputExporter, itkInputImporter);
itkInputImporter->Update();
4. Run the ITK Code: This is the heart of the function. Instantiate the lter, set the input and update.
}
typename DMapFilterType::Pointer dmap = DMapFilterType::New();
dmap->SetInput(itkInputImporter->GetOutput());
dmap->Update();
5. Convert ITK to VTK: This is also boilerplate as before. Note the DeepCopy at the end. This is required as a
ShallowCopy will point to ITK data-structures which will be automatically deleted at the end of the function.
}
// Step 5: Push it out to VTK
typename ExportType::Pointer itkExporter = ExportType::New();
itkExporter->SetInput(dmap->GetOutput());
itkExporter->Update();
vtkImageImport* vtkImporter=vtkImageImport::New();
ConnectPipelines(itkExporter, vtkImporter);
vtkImporter->Update();
output->DeepCopy(vtkImporter->GetOutput());
6. Cleanup: We delete all VTK lters. The ITK lters are self-deleting.
}
// Step 6: Cleanup VTK filters only
vtkImporter->Delete();
thresholder->Delete();
vtkInputExporter->Delete();
}
The use of this class is demonstrated in script script24-3.tcl.
265
CHAPTER 24. THE INSIGHT TOOLKIT Draft Sep. 2009
24.7 Concluding Remarks
The Insight Toolkit (ITK) is another large object-oriented library that oers a great deal of functionality for medical
image analysis. It has among others, some very nice implementations of the Levelset method, Finite Element
Code, Registration code etc. Unfortunately, in my opinion, the use of a fully generic programming style requires
a level of C++ expertise that makes it dicult to recommend whole-heartedly to a beginner. The methodology
presented in this chapter aims to demonstrate how one can take advantage of ITK code by neatly packaging it
inside VTK.
266
Draft Sep. 2009
Part VII
Appendices
267
Draft Sep. 2009
Appendix A
Introduction to Computer Graphics -
Part I
Computer Graphics has become an integral part for visualizing any type of data (images, surfaces etc.). In the rst
chapter, we will discuss some basic computer graphics concepts such as a camera, dierent types of projections,
camera options in VTK and go over a quick introduction to texture mapping.
A.1 Introduction
Computer graphics has now become an integral part of entertainment (games, movies) as well as research for
visualizing data (medical, seismic, oil exploration, atmospheric, computational uid dynamics and so on).
In this chapter, we will look at a brief introduction to computer graphics and focus on topics that you will most
probably run into during this course or in your research.
A.2 Cameras
In computer graphics, to create a visual representation of a scene or a dataset, we need to actually place a camera
in the scene or in front of the dataset. A real world camera analogy will help us understand some of the similarities
and dissimilarities with the synthetic camera in the computer graphics world.
In computer graphics, as in the real world, a scene is dened by objects in the scene. But, just as you need to
position your camera and point it at a subset of objects in the scene to take a photograph of the scene, you need
to specify the position of the virtual camera and point it at objects/data before you can see a visual representation
of it. Very often one ends up with a blank screen due to the fact that the camera position is correct but it is
pointing in the opposite direction to the where the objects are relative to the camera. Just as in the real world,
you can move around an object and take photographs of it from dierent angles, you can move around data and
examine dierent facets of it by looking at its visual representation on the screen. [Note: In computer graphics, if
we want to take a photograph of an object from a dierent viewpoint, we can either move the camera around the
object or rotate/transform the object in front of the static camera to see dierent facets of the object.] Therefore,
we can move, rotate, orient and snap the shutter of the virtual camera to capture a 2D image.
In actuality, a camera in computer graphics is nothing but a piece of code that produces an image for you. As you
can imagine, the camera has its own coordinate system (u, v, n) in order to facilitate transformations around
objects in the scene which are dened in their own coordinate system (x, y, z).
268
APPENDIX A. INTRODUCTION TO COMPUTER GRAPHICS - PART I Draft Sep. 2009
Figure A.1: The left column describes concepts in the real world camera whereas the right column describes concepts in
the virtual/computer graphics world.
269
APPENDIX A. INTRODUCTION TO COMPUTER GRAPHICS - PART I Draft Sep. 2009
Figure A.2: The gure shows a schematic of the projector rays that emanate from the center of projection (center of
the camera) and pass through each point of an object to intersect the projection plane
A.3 Viewing Pipeline
Naturally generating an image using a virtual camera in computer graphics is a bit more than merely pushing
a button on your digital camera. But, the considerations that one needs to keep in mind during the process
of generating an image are very similar to what one would consider during the process of actually composing a
photograph in the real world.
For image acquisition/generation, we need to specify the following:
Specication of projection type
Specication of viewing parameters (camera position, direction in which the camera is pointing etc.)
Clipping in 3D (objects outside the visual frame/view volume are clipped out)
Projection and Display (Orthographic/Perspective projection and the nal transformations with respect to
display the image on the computer screen).
Let us now closely look at what each of the above items entails.
A.3.1 Projection
In general, projection consists of transforming an n-dimensional system into a system with a dimension less than
n. Since in computer graphics, the scenes and objects are mostly 3-dimensional, we shall focus on the 3D to 2D
projection where the three dimensional world is projected onto a viewing plane to create a 2D image representation
of the scene.
Projection Rays The projection of 3D objects onto a image/viewing plane can be dened by projection rays
called Projectors. These projectors start from a center of projection (the eye of the viewer or the center of the
camera). They pass through each point of an object and intersect a projection plane to form the projection.
Figure A.2.
Center of Projection The center of projection for a camera is generally a nite distance away from the
projection plane. For some projections (orthographic), it is convenient to think of the center of projection as
innitely away. Figure A.3 shows dierent options for center of projection where the projectors could either be
convergent or parallel.
270
APPENDIX A. INTRODUCTION TO COMPUTER GRAPHICS - PART I Draft Sep. 2009
Figure A.3: This gure shows two dierent types of projectors and their implication on the center of projection. The
left image shows the case for perspective projection, whereas the right image shows orthographic projection.
Figure A.4: The left and middle gure show dierent views of the 1-Point perspective projection where lines parallel to
the X-, Y-axes do not converge. The right image shows a schematic of perspective projection with the center of projection,
projection plane and the normal to the projection plane.
A.3.1.1 Perspective Projection
Perspective projection can be loosely dened as the projection when any set of parallel lines (not parallel to
the projection plane) converge to a vanishing point. Depending on the number of vanishing points, there are
1-point perspective, 2-point perspective and 3-point perspective projections. In 1-point perspective projection,
lines parallel to x, y do not converge. Figure A.4 shows a side view (left image) and a front view (middle image)
where the vanishing point can be seen. A schematic of perspective projection is seen in the right image which
shows the center of projection, projection plane and the normal to the projection plane.
For perspective projection in computer graphics, the view volume is in the shape of a frustum after all the
parameters for the camera are dened. Everything inside the view volume gets displayed on the screen whereas
everything outside the view volume gets clipped/culled out. The view volume is dened by the extents of the
camera (left, right, top, bottom, near plane and far plane). Figure A.5 shows a diagrammatic representation of
the view volume in the shape of a frustum for perspective projection.
Figure A.5: The view volume for perspective projection is a frustum whose shape is dened by specifying the left, right,
top, bottom, near plane and far plane. Everything in the scene that falls in the view volume gets displayed on the screen
whereas everything outside the volume gets clipped/culled out.
271
APPENDIX A. INTRODUCTION TO COMPUTER GRAPHICS - PART I Draft Sep. 2009
Figure A.6: The left image shows a schematic of orthographic projection where the projectors are parallel and the center
of projection is at innity. The right image shows that the view volume in this projection is a rectangular parallelepiped
that is dened by the camera parameters.
Figure A.7: These images show the eect of varying the up vector, viewing direction and camera position on the nal
output image.
A.3.1.2 Orthographic Projection
Orthographic projection can be dened as the projection when the projectors are parallel to each other and the
center of projection is at innity. The left image in Figure A.6 shows a simple example of orthographic projection
of an object onto the image plane. The right image depicts the view volume for orthographic projection. The
view volume in this case is specied using left, right, top, bottom, near and far planes just as in perspective
projection. But, since the center of projection is at innity, the view volume is a rectangular parallelepiped. As
in perspective projection, everything inside the view volume is displayed on the screen whereas everything outside
the view volume gets clipped/culled out.
A.3.1.3 Some other camera parameters
In addition to the projection and camera position, the view direction as well as the up vector are also critical to
the correct generation of an image. The up vector can be thought of as the direction that one considers up in
the scene. If you are taking landscape photograph, your up vector is (0, 1, 0) whereas if you are taking a portrait
photo, the up vector will be (1, 0, 0) since now up is in the direction of the X-axis. Figure A.7 shows the eect
of varying the camera position, the up vector and the view direction.
272
APPENDIX A. INTRODUCTION TO COMPUTER GRAPHICS - PART I Draft Sep. 2009
Figure A.8: This shows the process of mapping a texture onto a polygon. The texture coordinates are always between
0 to 1 and are independent of the geometry. Note: For black and white printouts, the texture coordinates are below the
vertex coordinates and are between 0.0 and 1.0.
A.4 Computer graphics in VTK
VTK (Visualization toolkit) contains extensive functionality to display graphics and visualize data. We will focus
on a large part of the rest of the course on using VTK, so I shall not delve into too much detail other than the
computer graphics capabilities.
In VTK, they have used the Scene/Theatre paradigm in the virtual setting with Actors, Props, Lights and Camera.
Actors represent Images (2D/3D), Surfaces, Filter output, pretty much any data element. Props are the objects
added to the renderer to create a scene. Props generally specify information about the orientation, size, origin,
position of the Actor and are used extensively in VTK.
A.4.1 Camera in VTK
Now that we know all that we need to know about cameras, let us look at the available functionality in VTK.
VTK has a class called vtkCamera, that is basically a virtual camera for 3D rendering. It allows the users to
move, rotate, orient the camera around a focal point. It allows the user ne tuned control over the camera and
has some other functions to modify the current camera parameters to change camera settings on the y.
There are a lot more graphics/visualization features in VTK that we shall examine more closely later in the course.
A.5 Texture mapping
Texture mapping is the process of mapping an image/texture onto a geometric shape (triangle, polygon, sphere,
tetrahedron etc.) to increase the visual appeal of the model for realism. A texture is typically a 2-D image (though
it could be a 1D/3D image). Image elements are called texels (texture elements). The mapping of the texture
to a surface determines the correspondence, i.e., how the texture lies on the surface. As is probably evident, it is
much easier to map a texture to a triangle since texture coordinates can be easily specied for a triangle. On the
other hand, for a complex arbitrary 3-D shape, it is much harder to specify a one-to-one mapping between the
texture and points on the 3-D shape. Figure A.8 shows a schematic that describes the process of texture mapping.
A simple brick texture is mapped onto a polygon which has texture coordinates. The texture coordinates specify
the way in which the texture is glued onto the polygon.
There are many possible mappings that dene how a texture is mapped onto a surface. Some of the more popular
ones are
Orthogonal mapping - Straightforward one-to-one mapping without any transformations
273
APPENDIX A. INTRODUCTION TO COMPUTER GRAPHICS - PART I Draft Sep. 2009
Spherical mapping - Use to map a two dimensional image/texture onto a sphere.
Cylindrical mapping - To map a texture onto a cylinder.
Texture Chart - An explicitly dened mapping between points in a texture and the triangle that they map
to on an object.
274
Draft Sep. 2009
Appendix B
Introduction to Computer Graphics -
Part II
Computer Graphics has become an integral part for visualizing any type of data (images, surfaces etc.). In the
second chapter, we will discuss some computer graphics concepts such as dierent types of data, visualization
methods to visualize the data including volume rendering, colormaps and a quick introduction to lighting and
shading.
B.1 Introduction
Data for image processing can be either 2D/3D/4D image data or surfaces that are created or extracted from
the image data. Surface data can be visualized using simple computer graphics primitives such as points, lines
and polygons. 2D image data can be visualized using texture mapping techniques. 3D image data can be
visualized using isosurface rendering or direct volume rendering techniques. In order to visualize data, colors play
an important role in conveying dierences in the data and convey shape and structure cues. Colormaps are a way
to assign color in visualizations. Lighting and shading are extremely useful in conveying shape and structure of
data (especially 3D data).
B.2 Types of Data
Data is generally in the form of images or surfaces (also known as meshes). Images can be 2D, 3D or 4D (time
series data). Images are generally a 2D/3D/4D array of values representing data such as intensity, orientation of
tensors (DTI), activation in MR Spectroscopy or fMRI. Each individual element in a 2D image can be represented
by a pixel (picture element). Similarly, each element in a 3D image can be represented by a voxel (volume
element).
A Mesh is a collection of vertices in space connected by edges. The faces that are formed from connecting the
edges form a primitive (triangle, polygon etc.). A collection of such geometric primitives form a surface that is
then lit and shaded to show shape and structure.
Figure B.1 shows three dierent representations of the same data. The leftmost image shows a volume rendering
of the MNI head volume. The middle image shows a mesh extracted from the data while the rightmost image
shows a surface representation of the same data.
275
APPENDIX B. INTRODUCTION TO COMPUTER GRAPHICS - PART II Draft Sep. 2009
Figure B.1: Three dierent types of representations of the same data. The leftmost image is a volume rendering of
the MNI head volume. The middle image is a mesh extracted from the same 3D data. The rightmost image is a surface
representation of the mesh generated by drawing faces and shading them.
B.3 Visualizing data
To understand the data better we need to create a visual representation of the data. Visualizing meshes and
surfaces consists of drawing points, lines and geometric primitives such as triangles, polygons etc. The shape cues
are provided by lighting and shading, which we will discuss in section B.5 of the handouts in this chapter.
B.3.1 Visualizing images
2D images are visualized using texture mapping techniques which were introduced in the previous class. A polygon
is drawn on the screen and a texture is created from the data. The texture is then mapped onto the polygon for
display purposes. All the operations (such as image smoothing, edge detection etc.) are applied to the 2D image
and not to the polygon/texture. The texture map is then updated with the ltered image and the results shown
as a texture mapped polygon on the screen.
3D images are visualized by isosurface rendering or volume visualization techniques. An isosurface is a 3D analog
of an isocontour. It is a surface that represents points of a constant value (e.g. pressure, temperature, velocity,
density) within a volume of space; in other words it is a level set of a continuous function whose domain is
3D-space. In Figure B.1, the rightmost image is an isosurface rendering of the 3D data visualized in the leftmost
image.
B.3.1.1 Volume Rendering
Volume rendering is dened as the process of generating a 2D image directly from 3D volumetric data. Due to
the fact that a visual representation is obtained directly from 3D data, it is also sometimes referred to as Direct
Volume Rendering.
The idea behind volume rendering is to simulate the transport of light through matter. Some of the light gets
absorbed and some passes through. Based on the property of the material, which in our case is the intensity of
the voxel at each 3D location, some voxels let more light pass through than some others.
The volume rendering equation below is evaluated at each point along the ray
I =
_
D
0
c
_
s(x())
_
exp
_
_
0
(s(x(
)))d
_
d
where x() is the viewing ray parameterized by the distance to the viewpoint, c(s(x) is the color assigned by
the colormap and (s(x)) is the extinction coecient assigned to each possible material in the volume, D is the
276
APPENDIX B. INTRODUCTION TO COMPUTER GRAPHICS - PART II Draft Sep. 2009
Figure B.2: This image shows the four ray traversal methods (L to R): First, Average, MIP and Composite.
maximum distance that the ray traverses before it goes outside the volume.
This can be approximated to the following
C
i+1
= A
i
C
i
+ (1 A
i
) a
i
c
i
A
i+1
= A
i
+ (1 A
i
) a
i
where C
i
stands for the accumulated color along the ray and A
i
represents the accumulated opacity along the
ray. The current voxels color and opacity, which is obtained from the colormap, is represented as c
i
and a
i
respectively.
Ray traversal methods Based on dierent ray traversals, dierent visualizations are generated. The simplest
ray traversal method is rst-hit ray traversal method in which the rst non-zero voxel encountered is used for
color and opacity lookup. The second method averages the intensity along the voxels to produce an X-ray eect.
The third method is called Maximum Intensity Projection (MIP) where, as the name suggests, the maximum
intensity encountered along the ray is used to lookup the color and opacity for that pixel. MIP is widely used for
visualizing vessels in Magnetic Resonance Angiography. The last and most commonly used ray traversal method
is called Accumulate, where the color and opacity are accumulated along the way which allows for transparent
layers to be visible and takes into account multiple material properties. Figure B.2 shows the four dierent ray
traversal methods.
Volume rendering has been implemented in multiple ways to evaluate the volume rendering integral and produce
a visual representation of 3D data. We are going to look at raycasting, 2D and 3D texture-mapping based volume
rendering. Shear warp and splatting are the other popular volume rendering techniques.
Raycasting is a volume rendering technique in which for every pixel in the image, a ray is cast into the 3D space
containing the volume. Figure B.3 shows a schematic of the raycasting process. For every non-zero voxel, the
color and opacity for that voxel is looked up into a color lookup table (known as a transfer function and commonly
referred to as a colormap). The color and opacity are then combined with the accumulated color and opacity
along the ray. After the ray has gone through the volume, the accumulated color and opacity are assigned to
that pixel. The speed and quality of the raycasting algorithm is dependent on the steps of the ray as it traverses
through the volume. Newer advances in graphics hardware have made it possible to implemented a hardware
accelerated raycasting algorithm.
2D Texture mapping based volume rendering is a technique that uses the texture mapping capabilities of
graphics hardware to perform volume rendering. The volume is viewed as a set of slices parallel to the coordinate
planes. Polygons are drawn in front of each other with each polygon being textured with dierent slices of the
data. A blending operation is then performed to obtain a visual representation of the data. Figure B.4 shows a
schematic where the 2D polygon slices, the texture mapped polygons and the blended volume rendered image is
shown.
There are 3 stacks of 2D textures, each aligned to one of the axes (x, y and z). These slices are called axis aligned
as they are aligned to one of the coordinate axes. The blend operation simulates bilinear interpolation and since it
is completely dependent on graphics hardware, it is considerably faster than raycasting but requires more memory
(3 copies of same data in 3 textures).
277
APPENDIX B. INTRODUCTION TO COMPUTER GRAPHICS - PART II Draft Sep. 2009
Figure B.3: This image shows a schematic of the raycasting process. A ray traverses the volume and accumulates the
color and opacity along the way.
Figure B.4: This image shows a schematic of texture mapping based volume rendering process. The polygon slices, the
texture mapped polygons and the blended volume rendered image are seen.
3D Texture mapping based volume rendering is a technique that uses more advanced graphics hardware
capabilities which allow for 3D textures. Now the data is loaded into a single 3D texture and slices are drawn
parallel to the viewing direction. This simulates trilinear interpolation and is much faster since it happens on
graphics hardware. The speed and quality of the visualization are directly dependent on the number of slices
drawn perpendicular to the viewing direction (sampling the data). Figure B.5 shows a schematic of 3D texture
mapping based volume rendering. The slices are drawn perpendicular to the view vector as can be seen in the left
image. The slices are textured using a single 3D texture and therefore eliminate the need to have three copies of
the same data in graphics memory.
B.4 Colormaps
Colormaps are a way to apply color and opacity to data. Images contain numbers that represent a quantity such
as intensity, pressure and so on. To display this quantity and distinguish between dierent values we need to
assign colors to dierent values of that quantity. Colormaps are used to assign color and opacity to values in
2D/3D data during the process of visualizing the data. Basically, colormaps are lookup tables where the intensity
(or any other quantity in the data) is looked up to obtain color and opacity.
The simplest colormap is a grayscale colormap in which the lowest intensity is mapped to black and the highest
intensity is mapped to white and all the intermediate intensities are shades of gray.
Additionally, the lower threshold and upper threshold for a colormap can be specied. It ensures that all the
values below the lower threshold are set to zero (black) while all the values above the higher threshold are set to
one (white).
Some standard colormaps that are commonly used are rainbow, reverse rainbow and heat/temperature color map.
278
APPENDIX B. INTRODUCTION TO COMPUTER GRAPHICS - PART II Draft Sep. 2009
Figure B.5: This image shows a schematic of the 3D texture mapping based volume rendering process. The view-aligned
polygon slices, the 3D texture mapped polygons and the blended volume rendered image are seen.
Figure B.6: This image shows the standard colormaps (top to bottom): Greyscale, Rainbow, Reverse Rainbow, Temper-
ature colormap.
In the rainbow colormap, the rainbow is used to map the quantity to color. The reverse rainbow colormap only
reverses the lookup table. The heat/temperature colormap is widely used since it is perceptually constant like
the grayscale colormap. Even though the rainbow colormap is widely used and is the default setting in a lot of
software, it has been shown that it is a bad choice and leads to inaccurate readings by viewers using the colormap
to make decisions based on the visualizations. Figure B.6 shows all the four colormaps discussed above.
One can also dened custom colormaps by varying the lookup table based on ones needs. This invariably leads
to more interesting results but also requires a lot of tedious manipulation by hand.
B.5 Illumination
Illumination can be dened as the transport of energy from light sources to surfaces and points in the scene. It
includes direct and indirect illumination.
Lighting is dened as the process of computing the luminous intensity (i.e., outgoing light) at a particular 3-D
point, usually on a surface. Shading is dened as the process of assigning colors to pixels.
B.5.1 Phong lighting model
The Phong lighting model is the most commonly used empirical lighting model in computer graphics. It simulates
realistic lighting by using a simple combination of three components: ambient, diuse and specular lighting.
Ambient lighting is an approximation for indirect illumination. Diuse lighting is directional lighting and depends
on the position of the light source. Specular lighting is what gives the surface a shiny, polished look. A light
279
APPENDIX B. INTRODUCTION TO COMPUTER GRAPHICS - PART II Draft Sep. 2009
Figure B.7: This image shows a volume rendering obtained using a custom colormap. The custom colormap too has
been shown here. The right image is another volume rendering of the with a dierent custom color map.
Figure B.8: This image shows the three components: ambient, diuse, specular lighting and the white specular highlight
too can be seen on the sphere.
shining on a specular surface causes a bright spot known as a specular highlight. Where these highlights appear
is a function of the viewers position, so specular reectance is view-dependent. Figure B.8 shows the three
components of the phong lighting model.
I
total
= k
a
I
ambient
+
#lights
i=1
I
i
_
k
d
(n L
i
) +k
s
(v R
i
)
n
shiny
_
where I
total
is the total color of each pixel, I
ambient
is the ambient light intensity, k
a
, k
d
, k
s
represent the ambient,
diuse, specular coecients that control the contribution of the ambient, diuse, specular components. n is the
normal vector, L
i
is the light vector, v is the view vector, R
i
is the reection vector that is computed using Snells
Law. n
shiny
is the specular exponent that determines the size of the specular highlight.
B.5.2 Shading
Shading is the process of applying the computed color in the lighting model to every point on the surface. Flat
shading uses the normal for each face in the polygonal model. The lighting model is computed once for a point
on the face and same computed color is applied to all the points on that face. Gouraud Shading uses vertex
normals and for points in between two vertices, it interpolates the result of the colors computed at each vertex to
give a smoother appearance. Phong shading uses vertex normals and actually computes the vertex normals for
each vertex on each face and evaluates the lighting model on each point on the surface. This produces the best
quality but is computationally expensive as compared to at and Gouraud shading. Figure B.9 shows the type
of normals used for each type of shading and the bottom row shows the resulting images obtained by using the
three dierent types of shading.
280
APPENDIX B. INTRODUCTION TO COMPUTER GRAPHICS - PART II Draft Sep. 2009
Figure B.9: Flat, Gouraud and Phong Shading. The top row shows the normals used in each type of shading. The
bottom row shows a sphere shaded using the three dierent shading models.
281
Draft Sep. 2009
Appendix C
Final Exam
Programming
1. Implement, using C++ and VTK, a reasonably complex medical image analysis algorithm of your choice.
This must be an algorithm that you have not previously implemented in C++ converting code from
MATLAB is allowed. The implementation must use VTK data structures for images, surfaces etc. If you
need suggestions, I have placed several papers in a subdirectory called final/papers; any one of these
will be ne.
(a) The algorithm must be implemented in C++.
(b) All code must be in classes deriving from VTK classes (e.g. vtkProcessObject)
2. Compile the algorithm into a shared library that is accessible from (i.e. loadable into) Tcl.
3. Implement using Tcl a command-line script for quickly testing the algorithm. This should load a synthetic
image of your choice, execute the algorithm and generate some output result.
4. Implement using Tcl/[Incr] Tcl a complete application for interacting with your algorithm. This must
include:
(a) A graphical user interface for parameter setting and executing the algorithm.
(b) An integrated interactive viewer for displaying the results.
(c) You may leverage BioImage Suite components if you wish to do so.
Report
The report should have three parts:
1. A brief (1-2 page) description of the selected algorithm.
2. A description of the implementation strategy (e.g. how the algorithm was broken up into dierent classes
etc.), and description of the functions in your code. (This is the heart of the report 4-6 pages, longer if
needed).
3. A brief (2 pages) Users Guide for your application. Including snapshots of key GUI elements etc.
The lecture notes for Sessions 20 and Session 21 are typical examples of what is expected for Parts 1 and 2.
282
Draft Sep. 2009
Appendix D
Code License
All example code from this book is made available under the following BSD-style open source license.
Copyright (c) 2006 Xenophon Papademetris, All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of the Insight Software Consortium, nor the names of any
consortium members, nor of any contributors, may be used to endorse
or promote products derived from this software without specific prior
written permission.
* Modified source versions must be plainly marked as such, and must not
be misrepresented as being the original software.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS AS
ISAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
283