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!
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With