Getting started with Milena




Getting familiar with genericity

One of Milena’s main features is its genericity. In order to understand how to take benefit of it, let’s see what genericity really means for us and how it is illustrated in the library.

A Generic algorithm is written once, without duplicates, and works on different kinds of input.

Let’s have a look to a small example. In any image processing library, we may be interested in a small routine to fill an image with data. A common implementation would look like this one:

// Java or C -like code

void fill(image *ima, unsigned char v)
{
   for (int i = 0; i < ima->nrows; ++i)
     for (int j = 0; j < ima->ncols; ++j)
        ima->data[i][j] = v;
}

In this example, there are a lot of implicit assumptions about the input:

So, what would happen if we would like to use it for 3D images, use rgb8 as value or even work on a region of interest?

This implementation would require to be re-implemented and the user would have to deal with the various versions of the fill routine. For the developer, it is error prone, redundant and hard to maintain. For the user, it is confusing and forces to always think about what he is manipulating. According to our definition, this algorithm is clearly not generic.

This is not acceptable and that’s why Milena is developed considering genericity and user/developer friendliness.

With Milena, the previous example would be written as follow:

template <typename I>
void fill(I& ima, mln_value(I) v)
{
  mln_piter(I) p(ima.domain());
  for_all(p)
    ima(p) = v;
}

In this version, the routine can take any kind of image types as arguments. So it is for the values: the expected type depends on the value used in the given image. The for_all loop is also significantly generic to support any kind of images since the iterator guarantees it will pass through every sites.

This code is more generic and remains really close to the common description of the generic algorithm.

As a result, with this algorithm we can fill an image,...

  fill(ima, literal::green);

fill-2.png

... Or fill only a region of interest (a set of points).

  box2d b(20,20);
  fill((ima | b).rw(), literal::green);

fill-1.png




First generic algorithm

In this section, we will introduce several routines/tools which are useful while writing generic algorithms. It is more important to focus on these routines/tools than what this program actually does.

Here is the full example:

namespace mln
{

  template <typename I, typename N>
  mln_concrete(I)
  my_algorithm(const Image<I>& ima_,
               const Neighborhood<N>& nbh_)
  {
    trace::entering("my_algorithm");

    const I& ima = exact(ima_);
    const N& nbh = exact(nbh_);
    mln_precondition(ima.is_valid());
    mln_precondition(nbh.is_valid());

    typedef value::label_8 V;
    V nlabels;
    mln_ch_value(I,V) lbl = labeling::blobs(ima, nbh, nlabels);
    util::array<unsigned>
      count = labeling::compute(accu::meta::math::count(), lbl, nlabels);

    mln_concrete(I) output;
    initialize(output, ima);
    data::fill(output, literal::one);

    for (unsigned i = 1; i <= nlabels; ++i)
      if (count[i] < 10u)
        data::fill((output | (pw::value(lbl) == pw::cst(i))).rw(), literal::zero);

    trace::exiting("my_algorithm");
    return output;
  }

} // end of namespace mln

Let’s see the different parts of the algorithm.

  template <typename I, typename N>
  mln_concrete(I)
  my_algorithm(const Image<I>& ima_,
               const Neighborhood<N>& nbh_)

The prototype is restrictive enough, readable and still generic. We use concepts to statically check that the generic type passed as parameter is what the routine expects. The “exact” image type is I. For instance an image of type image2d inherits from Image<image2d>. So an image2d is an Image<I>. Note that the return type of this function is defined by a macro. mln_concrete is a macro hiding tricky mechanisms (traits) used in Milena. The important point to remember is that a generic function should not return I directly but mln_concrete(I) instead.

    trace::entering("my_algorithm");

Like any Milena’s routine, note that we use trace. This debugging tool will be detailed in section .

    const I& ima = exact(ima_);
    const N& nbh = exact(nbh_);
    mln_precondition(ima.is_valid());
    mln_precondition(nbh.is_valid());

Since the function take some arguments as concept objects, these object cannot be used as such. Indeed, concepts are empty shells only used for dispatching and concept checking, that’s the reason why they are parameterized with their exact type. The exact type let us know what is the real type of the object. To get an object with the exact type, simply call exact(). Of course, it is always a good idea to add few preconditions to help during debug.

    typedef value::label_8 V;
    V nlabels;
    mln_ch_value(I,V) lbl = labeling::blobs(ima, nbh, nlabels);
    util::array<unsigned>
      count = labeling::compute(accu::meta::math::count(), lbl, nlabels);

In this portion of code, the image is labeled and the number of sites per label is computed. This code does not depend on the image type at all. Again, a macro mln_ch_value (“mln change value”) helps us. labeling::blobs is a routine returning an image of the same kind as the input image but with a different value. mln_ch_value enables the possibility of doing that, whatever the image type I and whatever its value type, it returns the same image type with a different value type.

    mln_concrete(I) output;
    initialize(output, ima);
    data::fill(output, literal::one);

The output image is declared here. Like any variable, it must be initialized at some point. To do so, initialize() is provided. It is a generic routine which can initialize the geometry of any image kind with another image of the same kind. After this call, output has a valid domain and is valid. It can be used in an algorithm, here data::fill, to have its values modified. Note that the value passed to data::fill is also generic. The library includes few generic common values from which any value type can convert to. literal::one is one of them. It is a generic one value which can convert to every value type in the library.

    for (unsigned i = 1; i <= nlabels; ++i)
      if (count[i] < 10u)
        data::fill((output | (pw::value(lbl) == pw::cst(i))).rw(), literal::zero);

In this part, every region from the labeled image, of which cardinality is lower than 10 sites, is set to literal::zero in output. Once again, a generic value is used in order to avoid constraints on the image value type.

    trace::exiting("my_algorithm");
    return output;

Don’t forget to close the trace before exiting the function. Then return the result.




Compilation

Include path

If Milena has been installed in a custom directory, e.g. not /usr/include or /usr/local/include, the path to the library headers must be passed to the compiler.

With g++ and MinGW, the option is -I<path> .

$ g++ -Ipath/to/mln my_program.cc

For other compilers, please look at the documentation and search for “include path”.

Library linking

As it is usually expected when using a library, no library linking is needed for the library itself. Milena is a “header only” library and is compiled “on demand” with your program.

If you use specific input/output you may need to link your program with the right graphic library. For more information, please refer to section Input / Output in the Quick Reference Guide.

Disable Debug

By default, Olena enables a lot of internal pre and post conditions. Usually, this is a useful feature and it should be enabled. It can heavily slow down a program though and these tests can be disabled by compiling using -DNDEBUG:

$ g++ -DNDEBUG -Ipath/to/mln my_program.cc

Compiler optimization flags

In this section you will find remarks about the compiler optimization flags and their impact on the compilation and execution time.

GCC

Other compilers

Currently, we have not tested different optimization flags with other compilers. If you did, please report us your results.




Debug hints

Using assertions and GDB

As said above, Milena already includes a lot of post and pre conditions. Thus, if you made a mistake in your code there is a high probability that it will be detected at run time. If an assertion fails, we advice you to compile with the following options:

$ g++ -ggdb -Ipath/to/mln my_program.cc

Note that you MUST NOT compile with −DNDEBUG since assertions will be disabled. Once compiled, restart the program with GDB.

$ gdb ./my_program

In the GDB console, run it again.

(gdb) run <any parameter you may want to pass to the program>

When an assertion fails, in the GDB console simply type:

(gdb) bt

The full backtrace will be printed out and you will be able to find from where the error come from. The filenames, the line numbers and the parameters values are printed out in the backtrace as you can see in the following example:

#0  0xffffe410 in __kernel_vsyscall ()
#1  0xb7d00640 in raise () from /lib/i686/cmov/libc.so.6
#2  0xb7d02018 in abort () from /lib/i686/cmov/libc.so.6
#3  0xb7cf95be in __assert_fail () from /lib/i686/cmov/libc.so.6
#4  0x0804e094 in mln::image2d<bool>::has (this=0xbff32f34, p=@0xbff32f3c)
    at /lrde/stockholm/lazzara/svn/olena/git/oln/milena/mln/core/image/image2d.hh:442
#5  0x0804e6d7 in mln::image2d<bool>::operator() (this=0xbff32f34, p=@0xbff32f3c)
    at /lrde/stockholm/lazzara/svn/olena/git/oln/milena/mln/core/image/image2d.hh:460
#6  0x080490b0 in main () at test.cc:18

Traces

Sometimes, compiling for GDB without optimization flags and with debug assertions enabled could lead to execution time dramatically high. If the function parameter values are not necessary for debugging, a good alternative is the trace system provided in Milena. Each time a routine is called, a trace log is written.

This trace allows to follow the stack trace at runtime. It also provides the time passed in each function call if the call last at least 10ms.

In order to enable traces in a program, set the related global variable to true:

...
trace::quiet = true;
...

Since it’s a global variable, at anytime in the source code, the trace can be enabled/disabled.

Traces are enabled:

  // ...
  trace::quiet = false;

labeleling::blobs is run and the debug is then disabled.

  labeling::blobs(ima, c4(), nlabels);

  trace::quiet = true;

  geom::bbox(ima);
  // ...

The previous code will produce the following trace:

labeling::blobs {
  core::initialize {}
  data::fill {
    data::fill_with_value {
      data::impl::fill_with_value_one_block {
        data::memset_ {
          data::impl::memset_ {}
        } data::memset_
      } data::impl::fill_with_value_one_block
    } data::fill_with_value
  } data::fill
} labeling::blobs - 0.08s

As you can see, labeling::blobs is located just after having set trace::quiet to true so its trace is part of the output. geom::bbox’s trace is not part of the output though since traces have been disabled just before it is called.

Debug routines

Milena also provides a lot of debug tools. Here is a small list of the tools:







←— Go to Installation     |     Go to Data representation   —→


Generated on Tue Jul 14 16:30:21 2009 for Milena (Olena) by  doxygen 1.5.9