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; }
See milena/doc/examples/tuto3/fill_non_generic.cc.
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);
... Or fill only a region of interest (a set of points).
box2d b(20,20); fill((ima | b).rw(), literal::green);
Here is the full example:
#include <mln/core/image/image2d.hh> #include <mln/core/image/dmorph/image_if.hh> #include <mln/core/alias/neighb2d.hh> #include <mln/data/fill.hh> #include <mln/labeling/blobs.hh> #include <mln/labeling/compute.hh> #include <mln/labeling/blobs.hh> #include <mln/data/compare.hh> #include <mln/util/array.hh> #include <mln/value/label_8.hh> #include <mln/accu/math/count.hh> #include <mln/pw/all.hh> #include <tests/data.hh> #include <doc/tools/sample_utils.hh> 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
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;
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”.
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.
$ g++ -DNDEBUG -Ipath/to/mln my_program.cc
In this section you will find remarks about the compiler optimization flags and their impact on the compilation and execution time.
Currently, we have not tested different optimization flags with other compilers. If you did, please report us your results.
$ 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
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;
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.
image2d<int_u8> ima(5,5); data::fill(ima, 2); debug::println(ima);
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
image2d<int_u8> ima(5,5); data::fill(ima, 2); border::fill(ima, 7); debug::println_with_border(ima);
7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 2 2 2 2 2 7 7 7 7 7 7 2 2 2 2 2 7 7 7 7 7 7 2 2 2 2 2 7 7 7 7 7 7 2 2 2 2 2 7 7 7 7 7 7 2 2 2 2 2 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7
int_u8 vals[25] = { 100, 100, 200, 200, 230, 100, 100, 200, 230, 230, 140, 140, 140, 0, 0, 65, 186, 65, 127, 127, 65, 65, 65, 127, 127 }; image2d<int_u8> ima = make::image2d(vals); image2d<rgb8> ima_color = labeling::colorize(rgb8(), ima, 230);
![]() | → |
![]() |