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

Functor Expressions VIGRA

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:

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.1 (Fri May 19 2017)