Skip to content

Developing a filter module

Bernhard Froehler edited this page Feb 28, 2020 · 18 revisions

Here's a guide for how to implement an image processing filter inside an open_iA module, on the example of a median filter implemented with the help of the ITK toolkit. You can find the full source code in the CommonImageFilters module in the open_iA repository (look at the iADerivative class in the iAGradients.cpp/.h files; we have modified the name and category of the filter here to distinguish this example filter from the existing one).

Start by creating a new module, or choosing an existing module to extend. You can find more information on creating a new module in our other Tutorial on Developing a simple module.

Inside the module folder, create a class deriving from iAFilter:

iADerivativeFilter.h:

#pragma once

#include "iAFilter.h"

class iADerivativeFilter: public iAFilter
{
public:
	static QSharedPointer<iADerivativeFilter> create();
	void performWork(QMap<QString, QVariant> const & parameters) override;
private:
	iADerivativeFilter();
};

The create method and the fact that the constructor is private will ensure that the class can only be constructed with the help of a shared pointer, which helps open_iA with memory management. The performWork method will contain the actual execution of the filter. The constructor sets up the filter description. Since this class declaration looks basically the same for all simple filters, there is a macro for shortening this declaration. Instead of the above, you could also simply write the iADerivativeFilter.h as:

#pragma once

#include "iAFilter.h"

IAFILTER_DEFAULT_CLASS(iADerivativeFilter);

Next, let's take a look at the implementation of the methods of the class:

iADerivativeFilter.cpp:

#include "iADerivativeFilter.h"

#include "defines.h" // for DIM
#include "iAConnector.h"
#include "iAProgress.h"
#include "iATypedCallHelper.h"

#include <itkDerivativeImageFilter.h>

template<class T> 
void derivative(iAFilter* filter, QMap<QString, QVariant> const & params)
{
	typedef itk::Image<T, DIM> InputImageType;
	typedef itk::Image<float, DIM> RealImageType;
	typedef itk::DerivativeImageFilter< InputImageType, RealImageType > DIFType;

	auto derFilter = DIFType::New();
	derFilter->SetOrder(params["Order"].toUInt());
	derFilter->SetDirection(params["Direction"].toUInt());
	derFilter->SetInput( dynamic_cast< InputImageType * >(filter->input()[0]->itkImage()) );
	filter->progress()->Observe( derFilter );
	derFilter->Update();
	filter->addOutput(derFilter->GetOutput());
}

void iADerivativeFilter::performWork(QMap<QString, QVariant> const & parameters)
{
	ITK_TYPED_CALL(derivative, InputPixelType(), this, parameters);
}

IAFILTER_CREATE(iADerivativeFilter)

iADerivativeFilter::iADerivativeFilter() :
	iAFilter("Derivative Filter", "Tutorial",
		"Computes the directional derivative for each image element.<br/>"
		"The <em>order</em> of the derivative can be specified, as well as the desired <em>direction</em> (0=x, 1=y, 2=z).<br/>"
		"For more information, see the "
		"<a href=\"https://itk.org/Doxygen/html/classitk_1_1DerivativeImageFilter.html\">"
		"Derivative Filter</a> in the ITK documentation.")
{
	AddParameter("Order", Discrete, 1, 1);
	AddParameter("Direction", Discrete, 0, 0, DIM-1);
}

As you can see, you don't have to write the definition of the create method yourself - the IAFILTER_CREATE macro call will take care of that for you.

The performWork method takes care of performing the actual filter operation. The parameters are passed in a QMap, and, as shown, can be accessed by the name they are given in the constructor. Here we make use of the ITK_TYPED_CALL macro and a function templated on the pixel type to be able to perform the filter on input images of arbitrary pixel type. The Input() method of the iAFilter-derived class gives us access to the input image in the form of an constant vector of iAConnector class. It provides access to the image either as a smart pointer to a vtkImageData object or as a generic ITK image data pointer. We still need to cast it to the appropriately templated ITK image. For reporting progress, iAFilter provides a progress observer via the Progress() method, which returns an instance of iAProgress, which can track the progress of ITK and VTK operations via its Observe() method. Output is generated by calling the AddOutput method of iAFilter (which can be called multiple times, in case the filter has multiple images as output). In addition, a filter could add output scalar values (e.g. quality measures) via the AddOutputValue method.

The constructor describes the filter: The first argument to the parent constructor is the name of the filter, followed by its category. The filter will automatically get a menu entry based on these two values, as we will explain in more detail in the required changes to the module interface initialization below. The description in the third parameter will be displayed in the parameter dialog and can, as shown, contain HTML. In the body of the constructor we define the parameters that this filter accepts. Here we only accept two discrete parameters, the order of the derivative, and the axis direction in which to calculate the derivative. The type of parameters is an iAValueType the third parameter to AddParameter is the default value, the fourth the minimum value. See the iAFilter::AddParameter method for more details on the available options for defining parameters.

With theses parameter descriptions, open_iA can creates a dialog to show to the user so that he can adapt the parameter values to his current needs. open_iA will also store the values entered by the user in the platform-specific settings store (on Windows in the registry, on Mac OS and Linux in some file in the user's home directory). Yet for these things to happen, and for the menu entry to be created, as mentioned above, we need to register the filter with the core. We do that in the module interface:

iADerivativeModuleInterface.h:

#pragma once

#include "iAModuleInterface.h"

class iADerivativeModuleInterface: public iAModuleInterface
{
public:
	void Initialize();
};

iADerivativeModuleInterface.cpp:

#include "iADerivativeModuleInterface.h"

#include "iADerivativeFilter.h"

#include "iAFilterRegistry.h"

void iADerivativeModuleInterface::Initialize()
{
	REGISTER_FILTER(iADerivativeFilter);
}

The Module Interface only needs to implement the Initialize method, which will be executed by the open_iA core on startup of the program, if the module dll resides in the plugin folder at that time (and is compatible with the version of open_iA used, which typically means that it was compiled together; you will get a warning in the console window in case open_iA has troubles loading a plugin library). Calling the macro REGISTER_FILTER with the name of your filter class derived from iAFilter, as shown above for the iADerivativeFilter class, enters the filter into the central filter registry. For all filters registered there, open_iA will create menu entries as well as handlers for when these menu entries are clicked. The automatic handler will take care of reading the previously used parameter settings from the settings store, displaying the dialog for specifying the parmeters, storing these parameters back to the settings store if the user closed the dialog with "OK", and subsequently running the filter in a separate thread.

There are some hooks in place to customize this procedure. If you for example have some additional checks to run on your parameters before the filter thread is created, you can override the iAFilter::CheckParameters method. Or you could use the REGISTER_FILTER_WITH_RUNNER macro to register your filter with a custom GUI runner (inheriting from iAFilterRunnerGUI), in which you can override all the stages of running the filter via the GUI.

The filters will also be available in the open_iA_cmd application, which lets you trigger these filters from the command line. Note that the overrides in a custom iAFilterRunnerGUI will only be executed when the filter runs via the GUI, but not when the filter is run via the command line.

For more information, check the classes iAFilter (Code|API Documentation), [iAFilterRegistry(Code|API Documentation) and iAFilterRunnerGUI (Code|API Documentation).

Clone this wiki locally