Developing a filter module - 3dct/open_iA GitHub Wiki

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.

For the remainder of the tutorial here we assume a new module named Derivative, with a description.cmake file as described in the previous tutorial. Inside the module folder, create a Dependencies.cmake file:

set(DEPENDENCIES_LIBRARIES
	iA::base
)

Note how here we are declaring a dependency on iA::base library (instead of iA::guibase as in the previous tutorial); this is because for a filter, we require no access to graphical user interface stuff. See the code structure overview for some information on which core libraries contain what functionality.

Basic image filters deriving from iAFilter are typically declared something like this:

#pragma once

#include "iAAutoRegistration.h"
#include "iAFilter.h"
#include "iAFilterRegistry.h"

class iADerivativeFilter: public iAFilter, private iAAutoRegistration<iAFilter, iADerivativeFilter, iAFilterRegistry>
{
public:
	iADerivativeFilter();
private:
	void performWork(QVariantMap const & parameters) override;
};

The create method is used as a factory method for the filter. 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, called IAFILTER_DEFAULT_CLASS. Deriving from iAAutoRegistration (an instance of the curiously recurring template pattern) makes this class register itself (more on that below). Since we are using auto-registration, we don't need a header file (because the filter is never used directly, only via the registry).

Let's take a look at the implementation of the class:

iADerivativeFilter.cpp:

#include <defines.h> // for DIM
#include <iAFilterDefault.h>
#include <iADataSet.h>
#include <iAProgress.h>
#include <iATypedCallHelper.h>

#include <itkDerivativeImageFilter.h>

IAFILTER_DEFAULT_CLASS(iADerivativeFilter);

template<class T> 
void derivative(iAFilter* filter, QVariantMap 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->imageInput(0)->itkImage()) );
	filter->progress()->observe( derFilter );
	derFilter->Update();
	filter->addOutput(derFilter->GetOutput());
}

void iADerivativeFilter::performWork(QVariantMap const & parameters)
{
	ITK_TYPED_CALL(derivative, inputScalarType(), this, parameters);
}

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", iAValueType::Discrete, 1, 1);
	addParameter("Direction", iAValueType::Discrete, 0, 0, DIM-1);
}

As discussed above, the IAFILTER_DEFAULT_CLASS takes care of declaring and auto-registering the class. The performWork method takes care of performing the actual filter operation. The parameters are passed in a QVariantMap, 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 imageInput(index) method of the iAFilter-derived class gives us access to the input image with given index as iAImageData 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 (see 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 create 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).

One great thing about IAFILTER_DEFAULT_CLASS is that it does a lot of things automatically for you. For example, it auto-registers the filter with 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 menu entries are created based on category and name of the filter: The category (if non-empty) will be used as sub-menu to the application's "Filter" menu, and the filter name will be used as name of the menu entry. All filters in the registry can also be run via the "Select and Run Filter..." menu entry, where you can search for filters by their name. The filter runner linked to the automatically created menu entry as well as to the "Select and Run Filter..." will take care of reading the previously used parameter settings from the settings store, displaying the dialog for specifying the parmeters (along with the filter description), 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; then you cannot use the IAFILTER_DEFAULT_CLASS macro anymore, you have to write the full class declaration by yourself. 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).

The module interface is very simple here:

iADerivativeModuleInterface.h:

#pragma once

#include "iAModuleInterface.h"

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

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). Since the filter auto-registers itself, the Initialize method actually doesn't need to do anything, therefore it is empty and provided in the header file directly. It needs to exist though, as the call to it is hard-coded in the module initialization code (at the moment, this might change in the future).

⚠️ **GitHub.com Fallback** ⚠️