-
Notifications
You must be signed in to change notification settings - Fork 16
Developing a filter module
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 Gradients module in the open_iA repository (you will find the implementation of the iADerivative class in the iAGradients.cpp/.h files).
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
:
iADerivative.h:
#pragma once
#include "iAFilter.h"
class iADerivative: public iAFilter
{
public:
static QSharedPointer<iADerivative> Create();
void Run(QMap<QString, QVariant> const & parameters) override;
private:
iADerivative();
};
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 Run
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 d declaration. Instead of the above, you could also simply write the iADerivative.h as:
#pragma once
#include "iAFilter.h"
IAFILTER_DEFAULT_CLASS(iADerivative);
Next, let's take a look at the implementation of the method of the class:
iADerivative.cpp:
#include "pch.h"
#include "iADerivative.h"
#include "defines.h" // for DIM
#include "iAConnector.h"
#include "iAProgress.h"
#include "iATypedCallHelper.h"
#include <itkDerivativeImageFilter.h>
template<class T>
void derivative_template( unsigned int o, unsigned int d, iAProgress* p, iAConnector* image )
{
typedef itk::Image<T, DIM> InputImageType;
typedef itk::Image<float, DIM> RealImageType;
typedef itk::DerivativeImageFilter< InputImageType, RealImageType > DIFType;
auto filter = DIFType::New();
filter->SetOrder( o );
filter->SetDirection( d );
filter->SetInput( dynamic_cast< InputImageType * >(image->GetITKImage()) );
p->Observe( filter );
filter->Update();
image->SetImage(filter->GetOutput());
image->Modified();
filter->ReleaseDataFlagOn();
}
void iADerivative::Run(QMap<QString, QVariant> const & parameters)
{
ITK_TYPED_CALL(derivative_template, m_con->GetITKScalarPixelType(),
parameters["Order"].toUInt(), parameters["Direction"].toUInt(), m_progress, m_con);
}
IAFILTER_CREATE(iADerivative)
iADerivative::iADerivative() :
iAFilter("Derivative", "Gradients",
"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 Run
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 member m_con
of the iAFilter
-derived class contains the input image in the form of an iAConnector class. It provides access to the image either as a smart pointer to vtkImageData or as a generic ITK image data pointer.
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 radius of the kernel in each axis direction. 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). 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 "iADerivative.h"
#include "iAFilterRegistry.h"
void iADerivativeModuleInterface::Initialize()
{
REGISTER_FILTER(iADerivative);
}
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 iADerivative
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) which will get called once the thread running the filter has been created (and will get passed a pointer to this thread).
The filters will also be available in the open_iA_cmd application, which lets you trigger these filters from the command line.
For more information on the API details, check the iAFilter and iAFilterRegistry classes.
open_iA Documentation, licensed under CC BY-NC-SA 4.0