-
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 Neighbourhood module in the open_iA repository.
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
:
iAMedianFilter.h:
#pragma once
#include "iAFilter.h"
class iAMedianFilter : public iAFilter
{
public:
static QSharedPointer<iAMedianFilter> Create();
void Run(QMap<QString, QVariant> parameters) override;
private:
iAMedianFilter();
};
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. Here's the implementation:
iAMedianFilter.cpp:
#include "pch.h"
#include "iAMedianFilter.h"
#include "defines.h" // for DIM
#include "iAConnector.h"
#include "iAProgress.h"
#include "iATypedCallHelper.h"
#include <itkMedianImageFilter.h>
#include <itkCastImageFilter.h>
template<class T> void median_template( unsigned int r_x, unsigned int r_y, unsigned int r_z, iAProgress* p, iAConnector* image )
{
typedef itk::Image< T, DIM> InputImageType;
typedef itk::Image< float, DIM > RealImageType;
typedef itk::CastImageFilter< InputImageType, RealImageType> CastToRealFilterType;
typedef itk::MedianImageFilter<RealImageType, RealImageType > FilterType;
auto toReal = CastToRealFilterType::New();
toReal->SetInput( dynamic_cast< InputImageType * >( image->GetITKImage() ) );
auto filter = FilterType::New();
FilterType::InputSizeType indexRadius;
indexRadius[0] = r_x;
indexRadius[1] = r_y;
indexRadius[2] = r_z;
filter->SetRadius(indexRadius);
filter->SetInput( toReal->GetOutput() );
p->Observe( filter );
filter->Update();
image->SetImage(filter->GetOutput());
image->Modified();
filter->ReleaseDataFlagOn();
}
void iAMedianFilter::Run(QMap<QString, QVariant> parameters)
{
iAConnector::ITKScalarPixelType itkType = m_con->GetITKScalarPixelType();
ITK_TYPED_CALL(median_template, itkType,
parameters["Kernel Radius X"].toUInt(),
parameters["Kernel Radius Y"].toUInt(),
parameters["Kernel Radius Z"].toUInt(), m_progress, m_con);
}
IAFILTER_CREATE(iAMedianFilter)
iAMedianFilter::iAMedianFilter() :
iAFilter("Median Filter", "Neighbourhood",
"Applies a median filter to the volume.<br/>"
"Computes an image where an output voxel is assigned the median value of the voxels "
"in a neighborhood around the input voxel at that position. The median filter belongs "
"to the family of nonlinear filters. It is used to smooth an image without being "
"biased by outliers or shot noise.<br/>"
"The parameters define the radius of the kernel x,y and z direction.<br/>"
"For more information, see the "
"<a href=\"http://www.itk.org/Doxygen/html/classitk_1_1MedianImageFilter.html\">"
"Median Image Filter</a> in the ITK documentation.")
{
AddParameter("Kernel Radius X", Discrete, 1, 1);
AddParameter("Kernel Radius Y", Discrete, 1, 1);
AddParameter("Kernel Radius Z", Discrete, 1, 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 three discrete parameters, the radius of the kernel in each axis direction. The type of parameters is [[an iAValueType|https://github.com/3dct/open_iA/blob/develop/core/src/iAValueType.h#L23-L31] 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:
iANeighbourhoodModuleInterface.h:
#pragma once
#include "iAModuleInterface.h"
class iANeighbourhoodModuleInterface : public iAModuleInterface
{
public:
void Initialize();
};
iANeighbourhoodModuleInterface.cpp:
#include "iANeighbourhoodModuleInterface.h"
#include "iAMedianFilter.h"
#include "iAFilterRegistry.h"
void iANeighbourhoodModuleInterface::Initialize()
{
REGISTER_FILTER(iAMedianFilter);
}
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 iAMedienFilter
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).
For more information on the API details, check the iAFilter and iAFilterRegistry classes for more detail.
open_iA Documentation, licensed under CC BY-NC-SA 4.0