vigra - Functor Expressions

[ VIGRA Homepage | Function Index | Class Index | Namespaces | File List | Main Page ]

Functor Expressions |

Simple automatic functor creation by means of expression templates (also known as a "lambda library"). Note, however, that the vigra::multi_math module offers similar functionality with an easier syntax.

#include <vigra/functorexpression.hxx>
Namespace: vigra::functor

Motivation

Many generic algorithms are made more flexible by means of functors which define part of the algorithms' behavior according to the needs of a specific situation. For example, we can apply an exponential to each pixel by passing a pointer to the `exp` function to `transformImage()`:

vigra::FImage src(w,h), dest(w,h);
... // fill src
vigra::transformImage(srcImageRange(src), destImage(dest), &exp);

However, this only works for simple operations. If we wanted to apply the exponential to a scaled pixel value (i.e. we want to execute `exp(-beta*v)`), we first need to implement a new functor:

struct Exponential
{
Exponential(double b)
: beta(b)
{}
template <class PixelType>
PixelType operator()(PixelType const& v) const
{
return exp(-beta*v);
}
double beta;
};

This functor would be used like this:

double beta = ...;
vigra::transformImage(srcImageRange(src), destImage(dest),
Exponential(beta));

However, this approach has some disadvantages:

- Writing a functor is more work then simply program the loop directly, i.e. non-generically. Programmers will tend to avoid generic constructs, if they require so much writing.
- Often, functors are only needed for a single expression. It is not desirable to get into the trouble of introducing and documenting a new class if that class is used only once.

- Functors cannot be implemented directly at the point of use. Thus, to find out exactly what a functor is doing, one needs to look somewhere else. This complicates use and maintenance ot generic code.

Therefore, it is necessary to provide a means to generate functors on the fly where they are needed. The C++ standard library contains so called "functor combinators" that allow to construct complicated functors from simpler ones. The above problem "apply exp(-beta*v) to every pixel" would be solved like this:

float beta = ...;
vigra::transformImage(srcImageRange(src), destImage(dest),
std::compose1(std::ptr_fun(exp),
std::bind1st(std::multiplies<float>(), -beta)));

I won't go into details on how this works. Suffice it to say that this technique requires a functional programming style that is unfamiliar to many programmers, and thus leads to code that is difficult to understand. Moreover, this technique has some limitations that prevent certain expressions from being implementable this way. Therefore, VIGRA provides a better and simpler means to create functors on the fly.

Automatic Functor Creation

Automatic functor creation in VIGRA is based on a technique called Expression Templates. This means that C++ operators are overloaded so that they don't execute the specified operation directly, but instead produce a functor which will later calculate the result. This technique has the big advantage that the familiar operator notation can be used, while all the flexibility of generic programming is preserved.

The above problem "apply <TT>exp(-beta*v)</TT> to every pixel" will be solved like this:

using namespace vigra::functor;
float beta = ...;
transformImage(srcImageRange(src), destImage(dest),
exp(Param(-beta)*Arg1()));

Here, four expression templates have been used to create the desired functor:

`Param(-beta):`

  creates a functor that represents a constant (`-beta` in this case)

`Arg1():`

  represents the first argument of the expression (i.e. the pixels of image `src` in the example). Likewise, `Arg2()` and `Arg3()` are defined to represent more arguments. These are needed for algorithms that have multiple input images, such as combineTwoImages() and combineThreeImages().

* (multiplication):

  creates a functor that returns the product of its arguments. Likewise, the other C++ operators (i.e. `+, -, *, /, %, ==, !=, <, <=, >, >=, &&, ||, &, |, ^, !, ~`) are overloaded.

`exp():`

  creates a functor that takes the exponential of its argument. Likewise, the other algebraic functions (i.e. `sq, sqrt, exp, log, log10, sin, asin, cos, acos, tan, atan, abs, floor, ceil, pow, atan2, fmod, min, max`) are overloaded.

We will explain additional capabilities of the functor creation mechanism by means of examples.

The same argument can be used several times in the expression. For example, to calculate the gradient magnitude from the components of the gradient vector, you may write:

using namespace vigra::functor;
vigra::FImage gradient_x(w,h), gradient_y(w,h), magnitude(w,h);
... // calculate gradient_x and gradient_y
combineTwoImages(srcImageRange(gradient_x), srcImage(gradient_y),
destImage(magnitude),
sqrt(Arg1()*Arg1() + Arg2()*Arg2()));

It is also possible to build other functions into functor expressions. Suppose you want to apply `my_complicated_function()` to the sum of two images:

using namespace vigra::functor;
vigra::FImage src1(w,h), src2(w,h), dest(w,h);
double my_complicated_function(double);
combineTwoImages(srcImageRange(src1), srcImage(src2), destImage(dest),
applyFct(&my_complicated_function, Arg1()+Arg2()));

[Note that the arguments of the wrapped function are passed as additional arguments to `applyFct()`]

You can implement conditional expression by means of the `ifThenElse()` functor. It corresponds to the "? :" operator that cannot be overloaded. `ifThenElse()` can be used, for example, to threshold an image:

using namespace vigra::functor;
vigra::FImage src(w,h), thresholded(w,h);
...// fill src
float threshold = ...;
transformImage(srcImageRange(src), destImage(thresholded),
ifThenElse(Arg1() < Param(threshold),
Param(0.0), // yes branch
Param(1.0)) // no branch
);

You can use the `Var()` functor to assign values to a variable (`=, +=, -=, *=, /=` are supported). For example, the average gray value of the image is calculated like this:

using namespace vigra::functor;
vigra::FImage src(w,h);
...// fill src
double sum = 0.0;
inspectImage(srcImageRange(src), Var(sum) += Arg1());
std::cout << "Average: " << (sum / (w*h)) << std::endl;

For use in inspectImage() and its relatives, there is a second conditional functor `ifThen()` that emulates the `if()` statement and does not return a value. Using `ifThen()`, we can calculate the size of an image region:

using namespace vigra::functor;
vigra::IImage label_image(w,h);
...// mark regions by labels in label_image
int region_label = ...; // the region we want to inspect
int size = 0;
inspectImage(srcImageRange(label_image),
ifThen(Arg1() == Param(region_label),
Var(size) += Param(1)));
std::cout << "Size of region " << region_label << ": " << size << std::endl;

Often, we want to execute several commands in one functor. This can be done by means of the overloaded `operator,()` ("operator comma"). Expressions separated by a comma will be executed in succession. We can thus simultaneously find the size and the average gray value of a region:

using namespace vigra::functor;
vigra::FImage src(w,h);
vigra::IImage label_image(w,h);
...// segment src and mark regions in label_image
int region_label = ...; // the region we want to inspect
int size = 0;
double sum = 0.0;
inspectTwoImages(srcImageRange(src), srcImage(label_image),
ifThen(Arg2() == Param(region_label),
(
Var(size) += Param(1), // the comma operator is invoked
Var(sum) += Arg1()
)));
std::cout << "Region " << region_label << ": size = " << size <<
", average = " << sum / size << std::endl;

[Note that the list of comma-separated expressions must be enclosed in parentheses.]

A comma separated list of expressions can also be applied in the context of transformImage() and its cousins. Here, a general rule of C++ applies: The return value of a comma expression is the value of its last subexpression. For example, we can initialize an image so that each pixel contains its address in scan order:

using namespace vigra::functor;
vigra::IImage img(w,h);
int count = -1;
initImageWithFunctor(destImageRange(img),
(
Var(count) += Param(1),
Var(count) // this is the result of the comma expression
));

Further information about how this mechanism works can be found in this paper (sorry, slightly out of date).

© Ullrich Köthe (ullrich.koethe@iwr.uni-heidelberg.de) Heidelberg Collaboratory for Image Processing, University of Heidelberg, Germany | html generated using doxygen and Python vigra 1.11.0 (Fri May 19 2017)