Kitware Source Feature Article: April 2010

Visual Debugging of ITK

It is well known that a good debugger is an essential programming tool. Debuggers serve not only to effectively resolve defects, but they provide a means to examine the mechanics of an unknown code base and enable rapid development of new code.

Until recently, debuggers had limited utility for complex, high-level libraries like ITK. The built-in print capability of debuggers was limited to simple, low-level data types such as float, char* or C arrays. Examining complex C++ classes, such as an itk::Image, in an informative manner was difficult.

However, the PythonGDB project [1] has solved this problem by allowing the creation of custom pretty-printers for arbitrary data types. GDB [2] now has support for an embedded Python interpreter that hooks into much of the debugger’s functionality. In this article we describe how to use custom Python pretty-printers to improve development of ITK code.

An example
Note: The code for this example can be found in the examples subdirectory of the author’s ITK pretty-printer repository, gdb-pretty-itk [3].

Custom text printer
Run GDB on the executable that has been built with debugging symbols:

gdb ./itk-example

Set a break point after we have called Update() on an itk::ImageFileReader object:

 (gdb)  break 33
Breakpoint 1 at 0x41df69: file /tmp/gdb-pretty/ ...
 (gdb)  run lena.bmp heart.mhd
   [Thread debugging using libthread_db enabled]
Breakpoint 1, main (argc=3, argv=0x7fffffffd518 ...
33         reader2D->Print( std::cout );

ITK has written into the code Print() methods for almost all objects. This is intended to provide debugging information about the object’s state. We evaluate the next line of code to see the output of reader2D‘s Print() method:

 (gdb)  next
ImageFileReader (0x7257f0)
    RTTI typeinfo: itk::ImageFileReader<itk::Image unsigned char, 2u>, itk::DefaultConvertPixelTraits<unsigned char> >
    Reference Count: 1
    Modified Time: 10
    Debug: Off
    Observers:
        none
    Number Of Required Inputs: 0
    Number Of Required Outputs: 1
    Number Of Threads: 4
[...]
    m_FileName: lena.bmp
    m_UseStreaming: 1
35         Image2DType::Pointer itkImage2D = reader2D-
>GetOutput();

The same output can be produced with our custom ITK pretty-printer:

 (gdb) print reader2D
$1 = "itk::ImageFileReader (0x7257f0)" = {
   RTTI typeinfo = itk::ImageFileReader<itk::Image<unsigned char, 2u>, itk::DefaultConvertPixelTraits<unsigned char> >,
   Reference Count = 1,
   Modified Time = 10,
   Debug = Off,
   Observers = none,
   Number Of Required Inputs = 0,
   Number Of Required Outputs = 1,
   Number Of Threads = 4,
[...]
    FileName = "lena.bmp",
    UseStreaming = true
}

Notice that in this case it is no longer necessary to have PrintSelf() methods embedded in the library’s code. We also do not need to write in the debugging statement, re-compile, re-run, and then remove the debugging statement every time we want to investigate internal behavior. We can try this on the itk::Image object:

 (gdb) next
41          Reader3DType::Pointer reader3D =
   Reader3DType::New();
 (gdb)  print itkImage2D
$2 = "itk::Image (0x729bd0)" = {
   RTTI typeinfo = itk::Image<unsigned char, 2u>,
   Reference Count = 2,
   Modified Time = 169,
   Debug = Off,
   Observers = none,
   Source = 0x7257f0,
   Source output index = 0,
   Release Data = Off,
   Data Released = False,
   Global Release Data = Off,
   PipelineMTime = 10,
   UpdateMTime = 170,
   LargestPossibleRegion = "itk::ImageRegion
(0x729ce8)" = {
       Index = (0, 0),
       Size = (512, 512)
   },
   BufferedRegion = "itk::ImageRegion (0x729d38)" = {
       Index = (0, 0),
       Size = (512, 512)
   },
[...]

2D Image printer
The GDB Python interface has other simple, effective, and powerful capabilities in addition to creating custom pretty-printers. For example, custom commands can be created. These commands can be loaded using 'require'. Let us load a command that allows selection of a specific pretty-printer (assuming many are available for printing the same data type):

(gdb) require command view

The ‘view with’ command takes two arguments: a regular expression that specifies the module that the desired pretty-printer lookup_function is defined in (explained later), and an expression to be passed to the print command. Available modules are shown in itk-example-gdb.py. We issue:

(gdb) view with matplotlib.imshow itkImage2D

And we immediately see the contents of the image’s BufferedRegion.

Buffered Region

The next part of the code talks about doing some kind of beautification. What does it really do?

 (gdb) list 38
33  reader2D->Print( std::cout );
34
35  Image2DType::Pointer itkImage2D = reader2D->GetOutput();
36
37  // Beautification...
38  Image2DType::IndexType index;
39  index[0] = 285;
40  index[1] = 354;
41  itkImage2D->SetPixel( index, 5 );
42
 (gdb)  b 43
Breakpoint 2 at 0x41e384: file /tmp/gdb-pretty/ ...
 (gdb) c
Breakpoint 2, main (argc=3, argv=0x7fffffffd518 ...
43  index[0] = 3;
 (gdb) view with matplotlib.imshow itkImage2D

Beauty Mark

Beauty Mark Closeup
We see it added a beauty mark above her lip.

3D Image printer
Custom, visual pretty-printers can be generated for 3D images too. For example, iso-contours can be viewed using Mayavi [4]:

 (gdb) b 63
Breakpoint 3 at 0x41e473: file /tmp/gdb-pretty/ ...
 (gdb) c
Breakpoint 3, main (argc=3, argv=0x7fffffffd518 ...
63  return EXIT_SUCCESS;
 (gdb)  view with mayavi.contour itkImage3D

Data in Paraview

Or we can interrogate the data with an application such as ParaView or VV [5]:

(gdb)  view with vv itkImage3D

Set of Slices

Examining in an interactive IPython shell
We can dynamically explore the data in an IPython [6] shell using Matplotlib’s 2D pyplot facilities [7] or Mayavi’s 3D Mlab facilities:

 (gdb)  view with mayavi.ipython itkImage3D
$7 = /tmp/tmpfbKd8u/7.vtk /tmp/tmpfbKd8u/7.npz mlab
   pid = 14823

IPython with Variables

This loads an IPython shell with variables in the local namespace corresponding to the printed value.

In  [1]: val7
Out[1]: <enthought.mayavi.sources.vtk_file_reader. VTKFileReader object at 0x6397fb0>
In  [2]: mlab.pipeline.volume(val7)

Image Subregions

Subregions of the image can be easily extracted and examined.

In  [3]: arz4
Out[3]: <numpy.lib.io.NpzFile object at 0x1c27390>

In  [4]: arz4.items()
Out[4]:
 [(’scalars’,
  array([[[16, 15, 16, ..., 24, 26, 30],
         [18, 13, 15, ..., 24, 27, 31],
         [15, 10, 11, ..., 20, 26, 31],
         ...,
         [44, 45, 39, ..., 39, 36, 28],
         [ 0, 0, 0, ..., 0, 0, 0],
         [ 0, 0, 0, ..., 0, 0, 0]],
  [...]
         [58, 59, 47, ..., 43, 41, 35],
         [62, 57, 43, ..., 38, 33, 26],
         [55, 54, 49, ..., 34, 26, 18]]],
dtype=uint8)),
  (’origin’, array([ 0., 0., 0.])),
  (’spacing’, array([ 1., 1., 4.]))]

In [5]: plt.plot( arz4[’scalars’][:,10,10] )

We can also save images as we progress through our program for comparison by subtraction or other exploratory methods in our IPython session.

IPython Chart

How it works
Step 1: Define a class’s pretty-printer
A custom pretty-printer is a Python class that follows a simple API. For example, the complete itk::Object text printer is:

from itk.v3.text.Common.LightObject import \
ITKLightObjectTextPrinter

NOT_A_TEMPLATE = True
class ITKObjectTextPrinter:
  def __init__ (self, val):
     self.val = val

     lop = ITKLightObjectTextPrinter (val)
     self.parameters = lop.parameters + \
     [  ("Modified Time ", val['m_MTime']),
        ("Debug", "On" if val['m_Debug'] else "Off"),
        ("Observers", \
     val['m_SubjectImplementation']['m_Observers'] \
     if val['m_SubjectImplementation'] \
     else 'none') ]

def to_string (self):
  return "itk::Object (" + \
     str (self.val.address) + ")"

def children (self):
  return self.parameters

def display_hint (self):
  return 'string'

The class’s constructor takes a Python gdb.Value object as an argument. A to_string() method defines how to generate a string representation of the object. An optional children() method returns an object that follows the Python iterator protocol, in this case a Python list, which returns a (‘name’, value) tuple child on each iteration. The display_hint() is an optional method used by the user interface during display of the output.

Step 2: Create a lookup function
When the print command is executed in GDB, the resulting value is passed to the registered pretty-printer lookup functions. The lookup function should return either a pretty-printer class that can print the value, or the Python object None. If the lookup function returns None, GDB passes the value to the next registered lookup function. If no custom pretty printer is found to print the value, the default print behavior is executed.

A lookup function can be any Python function that performs filtering on the value. In practice, the lookup function usually consists of a few common steps focusing on the type attribute associated with the gdb.Value. If the type is a reference, it is converted to the type that it references. If the type is a typedef, it is converted to the original type. Filtering can examine template arguments. If the type is an itk::SmartPointer<>, the template argument is extracted. Finally, a pretty-printer Python dictionary associates a regular expression matching the typename to the corresponding pretty-printer class.

Step 3: Associate the lookup function with an object file
Lookup functions can be associated with GDB objfiles. Here objfiles refer to executables or libraries. A file in the same directory as the executable or library with the same name except for an appended -gdb.py gets executed when GDB loads the objfile. This script is used to register the lookup functions associated with the program or library. This allows only the pretty-printers needed by the program to be loaded, which reduces startup time. The order of the lookup functions also determines the default pretty-printer.

Further reading
The author’s GDB branch used for this article can be found on gitorious [8]. ITK pretty-printers can be found at the same location, gdb-pretty-itk. Documentation for GDB and the Python API can be generated by calling ‘make html’ in the gdb/doc directory of GDB’s source. A screencast demonstrating custom pretty printers can be found at archive.org [9].

Acknowledgements
Thanks to Tom Tromey, Jan Kratochvil, Joel Brobecker, Phil Muldoon, et al. for their work on the GDB project and helpfulness on the mailing lists and IRC.

References
[1]  Work to integrate Python scripting into GDB http://sourceware.org/gdb/wiki/PythonGdb/
[2]  The GNU Debugger. http://sources.redhat.com/gdb/
[3]  http://gitorious.org/gdb-pretty/gdb-pretty-itk/
[4]  VTK based scientific data visualizer http://code.enthought.com/projects/mayavi/
[5]  VV is an open-source and cross-platform image viewer, designed for fast and simple spatio-temporal images visualization:
      2D, 2D+t, 3D and 3D+t (or 4D) images http://www.creatis.insa-lyon.fr/rio/vv/
[6]  An advanced interactive shell for Python. http://ipython.scipy.org/
[7]  Pure Python plotting library with MATLAB like syntax. http://matplotlib.sourceforge.net/
[8]  http://gitorious.org/~thewtex/gdb-python/archer-thewtex-python/commits/archer-thewtex-python-next
[9]  http://www.archive.org/details/VisualDebuggingWithCustomPrettyPrinters

Matthew McCormick  Matt McCormick is a PhD candidate at the University of Wisconsin-Madison in the Department of Biomedical Engineering. His research interests include diagnostic ultrasound strain imaging and parametric tissue characterization. He enjoys scientific computing and is an active member of UW-Madison Hacker Within (hackerwithin.org).