3

I'm currently working on an image processing application, mainly based on C++ and ITK.

1. The Situation

I have node classes (e.g. FlipFilter) derived from the same base class. A node gets a struct with an image_ptr and all of the meta-information (dimension of the Image, PixelType (e.g. RGB, RGBA, scalar) and ComponentType (e.g. Float, int). The node has to instantiate an ITK-Filter, based on these Meta-Informations, which can change on every input-image.

2. The Problem

These ITK-Filters need the ImageType (e.g. RGB with ComponentType UCHAR) as a template argument. A template class has to be instantiated at compile-time. My node is getting the image with its type at runtime. So I somehow have to create all permutations of the filter for each node and then use the appropriate instantiation.

3. My current Solution

This struct contains all the meta-information + a smart-pointer pointing towards the actual image. I'm using a base pointer of the image because the image itself is also a template (later I'm downcasting).

struct ImageData
{
    short NumberOfDimensions;
    itk::ImageIOBase::IOComponentType ComponentType;
    itk::ImageIOBase::IOPixelType PixelType;
    itk::DataObject::Pointer Image;
    ImageData() {}
    ~ImageData() {}
};

This is the update function of my node. It is supposed to create the filter an execute it on an image.

void LitkFlipImageFilter::update()
{
    if (Input1 == nullptr)
        throw(std::runtime_error("Input1 not set"));


    Input1->update();

    ImageData Input1Data = Input1->getOutput();

    switch (Input1Data.PixelType)
    {
        default:
        {
            throw std::runtime_error("Type not Supported");
            break;
        }
        case itk::ImageIOBase::RGB:
        {
            switch (Input1Data.ComponentType)
            {
                default:
                {
                    throw std::runtime_error("Type not Supported");
                    break;
                }
                case itk::ImageIOBase::IOComponentType::UCHAR:
                {
                    using PixelType = itk::RGBPixel< unsigned char >;
                    using ImageType = itk::Image < PixelType, 2 >;
                    itk::FlipImageFilter<ImageType>::Pointer filter = itk::FlipImageFilter<ImageType>::New();
                    //do stuff

                    break;
                }
            break;
            }
        }
    }
}

4. The Problem with my Solution

It's working but creates a lot of repetitive code and large nested switch cases. Do you know a more elegant way of solving this problem?

Thank you!

leiyc
  • 903
  • 11
  • 23
mdin
  • 31
  • 2
  • There is nothing wrong with the solution you have here. Implement it once then move on. – Ben Aug 12 '18 at 13:37
  • ITK is designed with the idea that you know the image type at compile time. If you don’t, consider a different library. SimpleITK implements all these cases for you, leaving you with a simpler interface to the same routines. You could also consider [DIPlib](https://github.com/DIPlib/diplib), which supports runtime type and dimensionality selection out of the box (I’m an author). – Cris Luengo Aug 12 '18 at 13:54
  • @CrisLuengo i will take a look at DIPlib ;) – mdin Aug 13 '18 at 14:40

1 Answers1

2

The high level processing you want is:

template <typename PixelType>
void do_stuff()
{
    using ImageType = Image < PixelType, 2 >;
    ...do stuff...
}

You can create one verbose but reusable (by varying the "Fn" code to dispatch to) version of your switching code:

template <typename Fn>
void dispatch(PixelType pt, ComponentTypeId ct, Fn fn) {
    switch (pt)
    {
      case RGB:
        switch (ct) {
          case Uint8_t: fn(RGBPixel<uint8_t>{}); return;
          case Float:   fn(RGBPixel<float>{}); return;
        };
      case RGBA:
        switch (ct) {
          case Uint8_t: fn(RGBAPixel<uint8_t>{}); return;
          case Float:   fn(RGBAPixel<float>{}); return;
        };
      case Scalar:
        switch (ct) {
          case Uint8_t: fn(ScalarPixel<uint8_t>{}); return;
          case Float:   fn(ScalarPixel<float>{}); return;
        };
    }
}

Then, call it like this:

dispatch(runtime_pixel_type, runtime_component_type, 
         [](auto pt) { do_stuff<decltype(pt)>(); });

Notes:

  • using a default-constructed "XXXPixel" argument to the lambda is ugly - C++2a is supposed to introduce proper templated lambdas that might (?!) clean this up.

  • you can chain several "dispatch" functions that each dispatch based on one runtime variable to avoid a multiplicative explosion of switch cases; that scales better but is overkill here, and you'd have to work around PixelType being a template.

  • you can add your default: throws back in - no need to break (or return) after them as they never return to the following line of code

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • First of all, thank you for your suggestion! In your lambda-function you already initiated the do_stuff template. The dispatcher then gets the function and gives it the type (again), but what I want to do is to instantiate do_stuff in a generic function (like your dispatcher) for different node types (e.g. FlipNode, FlopNode, XYZNode) so i don't have to write a huge switch case for each node type. I tried to modify your solution, but so far I didn't make it. :( – mdin Aug 13 '18 at 14:39
  • @mdin: *"In your lambda-function you already initiated the do_stuff template. The dispatcher then gets the function and gives it the type (again)"* - the lambda specifies "auto" for the pixel type, so it can be instantiated by the calling dispatcher code for each of the types handled in the switch. So, not sure why you say "again". Anyway, you do need some kind of mapping from runtime values to instantiations; if the switch thing's too verbose for you, consider [these techniques](https://stackoverflow.com/a/7089649/410767). – Tony Delroy Aug 14 '18 at 06:52