Skip to content

Add Functor module with FunctorImageFilter

Julien Michel requested to merge functorfilter-take2 into develop

Summary

This MR introduces a new Functor module, which contains the FunctorImageFilter.

It is the same as MR !268 (closed) with squashed commit and a better documentation.

For examples of use, please refer to the test of the class.

Accepted types in functor arguments and return type

Prototype for this filter is as follows : FunctorImageFilter<TFunction,TNameMap = void>.

Where TFunction can be any lambda, free function, or class member operator(), with any number of arguments.

Let R (T1 t1, T2 t2 ..., TN tn) (const) be TFunction signature. T1, T2, ...TN can be:

  • A value, ref or const ref to the following,
  • A scalar type (unsigned int, double),
  • A std::complex
  • An instance of itk::FixedArray, itk::RGBPixel, itk::RGBAPixel
  • A itk::VariableLengthVector
  • A itk::Neighborhood of any of the above

R can be:

  • A value, ref or const ref to the following,
  • A scalar type (unsigned int, double),
  • A std::complex
  • An instance of itk::FixedArray, itk::RGBPixel, itk::RGBAPixel
  • A itk::VariableLengthVector

Note that the following operator prototype is also supported: void (R& return, T1 t1, T2 t2 ..., TN tn) (const)

Setting the inputs

FunctorImageFilter will automatically deduce the correct image type from return type and arguments of the function or operator(), and will offer setters of the form SetVariadicInput<N>(...) excepting the correct type for Nth input. Note that one can also use the SetVariadicInputs(in1, in2, in3 ...) variadic method to set all inputs at once.

Correct allocation of ouptut when output image is VectorImage

If the TFunction produces a VariableLengthVector, then it should also provide a OutputSize() method which will be called by FunctorImageFilter to allocate the correct number of bands in output image.

Full prototype of OutputSize() method is constexpr size_t OutputSize(const std::array<size_t, N> inputsNbBands) const with N being the number of argument of the functor. inputsNbBands will contain the number of bands for each input which can be used to derive the output number of bands. However, if the number of bands in input is not relevant to derive the number of output band, the following simpler signature can be used instead constexpr size_t OutputSize(...) const.

If a lambda returning a VariableLengthVector is used, one can not add the OutputSize() method but the NumberOfOutputBandsDecorator template class can be used to wrap the lambda and still use the filter.

Last, for the sake of backward compatibility, SetInput1(), ..., SetInput10() are also defined (if and only if the number of arguments in the functor is high enough).

Accessing the Functor

Functor can be retrieved with GetFunctor() (returns a const reference) or GetModifiableFunctor() (returns a non-const reference AND calls Modified() on filter).

Naming inputs

In order to ease the use of the filter by users, it is possible to name input with tags instead of indices.

In that case TNameMap is not void, but a std::tuple of tags (i.e. empty classes):

struct tag1{};
struct tag2{};

If TNameMap = std::tuple<tag1,tag2> then FunctorImageFilter will also support settings input as follows : SetVariadicNamedInput<tag1>(in) or SetVariadicNamedInput(tag1{},in) ... A good practice could be that this name mapping is provided by the functor itself.

Setting the Radius

If one of the arguments of the functor maps to itk::Neighborhood the corresponding input will automatically be padded to the filter radius, which can be changed using SetRadius().

Easy creation of filters

The FunctorImageFilter offers two free functions that make it very easy to create ready to use instances of the filter:

// Simple case
SimpleFunctor f1;
auto filter1 = NewFunctorFilter(f1);
filter1->SetVariadicInputs(in1,...,inN);
filter1->Update();

// Functor With Radius
FunctorWithRadius f2;
auto filter2 = NewFunctorFilter(f1 {{3,3}}); // passing the radius directly
filter2->SetVariadicInputs(in1,...,inN);
filter2->Update();

// Lambda returning a VariableLengthVector of 3 bands
auto lambdaWithVariableLengthVector = [](const double &){return itk::VariableLengthVector<double> res(3); return res;};
auto filter2 = NewFunctorFilter(lambdaWithVariableLengthVector,3); // passing the number of output bands directly
filter2->SetVariadicInputs(in1);
filter2->Update();

Of course one can also directly use FunctorImageFilter typedef to create the filter through the classical New() static method.

Using this for refactoring of existing functor based filters
  • If functor outputs a VariableLengthVector:
    • change signature to void operator(TOut & out, ...) const (more efficient).
    • Implement the OutputSize() method if functor produces a VariableLengthVector
  • If the functor is used in filter with custom setters (such as SetInputRed(), SetInputMask(), SetInputHH() ...), create a set of corresponding tags somewhere (i.e. struct red{}; ...)
  • Replace filter with template using:
template <typename TInputImage1, typename TInputImage2, typename TOuputImage> 
using TheFunctorFilter = FunctorImageFilter< TheFunctor < typename TInputImage1::PixelType,
                                                          typename TInputImage2::PixelType,
                                                          typename TOutputImage::PixelType>,
                                             std::tuple<tag1,tag2> >;
  • Rewrite the tests and applications to replace (and add this to migration guide):
    • calls to SetInputTag1() by SetVariadicNamedInput<tag1>()
    • calls to SetAlpha() by GetModifiableFunctor().SetAlpha()
  • Remove/deprecate old code (the fun part)

Implementation Details

I tried to document the code as best as I could. If something is missing, please tell me I will fix it.

Tests

An extensive test suite has been added to cover all possible cases, with a lot of static_assert. Most cases are also covered in the test with real functors.

Limitations

This will not work with:

  • Functor with more than one operator()
  • Functor template operator() method
  • Something called polymorphic lambdas (not sure if this is even useful)

Copyright

The copyright owner is CNES and has signed the ORFEO ToolBox Contributor License Agreement.

Thanks to Jordi for helping starting this.


Check before merging:

  • All discussions are resolved
  • At least 2 👍 votes from core developers, no 👎 vote.
  • The feature branch is (reasonably) up-to-date with the base branch
  • Dashboard is green
  • Copyright owner has signed the ORFEO ToolBox Contributor License Agreement
Edited by Julien Michel

Merge request reports