ImFusion SDK 4.3
Algorithms

General Algorithms

Algorithms are one of the central concepts of the ImFusion framework because they allow to develop new functionality in a clean, well-defined manner without having to modify the base framework or interfering with existing algorithms. All algorithms are derived from the Algorithm interface. The central methods of this interface are the compute method and the createCompatible method which must be defined by every algorithm. Optionally an algorithm controller derived from Algorithm Controller can be defined which can be used to display a graphical user interface for interacting with the algorithm, for instance allowing to set properties or to create new views for displaying algorithm results. To make an algorithm known to the framework a factory needs to be written which in turn has to be registered with the global FactoryRegistry object.

I/O-Algorithms

Support for loading and saving custom data formats can be added by implementing specialized algorithms. In contrast to regular algorithms I/O-algorithms derive from IoAlgorithm. IoAlgorithms have special properties which allow to generically specify the location of the data and whether data should be imported or exported. The actual loading and writing of the data is performed in the compute method. It is also possible to write controllers for an IoAlgorithm which can for instance display custom import and export options. For the common use case of only needing to select the input/output file the default controller IoController can be used.

Algorithm Development

Defining the Algorithm

To create a new algorithm a class deriving from Algorithm needs to be created. Inside this class the compute and the createCompatible method need to be defined. compute is called by the framework or the algorithm controller to perform the actual computations. createCompatible is used to create an algorithm instance for a given set of input data represented as a DataList object. Inside createCompatible the algorithm needs to check whether it supports operating on the provided data. If so it should create an instance of itself and configure it so that it runs on the input data. createCompatible should only succeed when all data in the data list is needed by the algorithm to operate. For instance an algorithm which downsamples a single 2D image should fail createCompatible if the data list contains more than one element even if one of them is a 2D image. The framework generates the data list passed to createCompatible in one of two ways:

  • If the algorithm is created from a workspace file the inputs specified in the corresponding algorithm section of the workspace.
  • If the algorithm is created from the context menu of the GUI application the inputs are the selected data sets. The framework only adds algorithms to the algorithm context menu which pass createCompatible.

If the algorithm creates output data it also needs to implement the takeOutput method to provide the data to the framework or the controller.

To support automated algorithm serialization and instantiation by the framework for instance for including the algorithm configuration in a workspace file the methods of the Configurable interface need to be implemented.

#include <ImFusion/Base/Algorithm.h>
namespace ImFusion
{
class SharedImageSet;
// Algorithms need to derive from the Algorithm class
class DemoAlgorithm : public Algorithm
{
public:
// Creates the algorithm instance. It is good practice to pass the image data in the
// required format here, instead of calling input() which returns the DataList with
// which the algorithm was created.
explicit DemoAlgorithm(SharedImageSet* img);
// Method for configuring algorithm settings
void setParam(int param);
// Needs to be implemented by every algorithm to check for data compatibility and to
// create the algorithm.
static bool createCompatible(const DataList &data, Algorithm **a = 0);
// Needs to be implemented by every algorithm to perform the actual processing
void compute() override;
// Return any new Data that was created by the Algorithm during the last call to
// compute().
// \note Since ownership of the data is transferred, you can call this method only
// once between calls to compute().
OwningDataList takeOutput() override;
// Configure algorithm from supplied properties
void configure(const Properties* p) override;
// Write algorithm settings into supplied properties object
void configuration(Properties* p) const override;
// Set the progress
void setProgress(Progress* p) override;
private:
Progress* m_progress;
};
}
Namespace of the ImFusion SDK.
Definition Assert.h:7
#include "DemoAlgorithm.h"
#include <ImFusion/Base/DataList.h>
#include <ImFusion/Base/MemImage.h>
#include <ImFusion/Base/SharedImage.h>
#include <ImFusion/Base/SharedImageSet.h>
namespace ImFusion
{
DemoAlgorithm::DemoAlgorithm(SharedImageSet* img):
m_imgOut(0)
{
// Call this to use the default values as specified in the configuration()-method
configureDefaults();
}
DemoAlgorithm::~DemoAlgorithm() = default;
// Called by framework to determine algorithm support for passed data
bool DemoAlgorithm::createCompatible(const DataList &data, Algorithm **a)
{
// This algorithm only works on one image. If more than one image has been passed
// don't do anything.
if (data.size() != 1)
return false;
// Test whether the data provided is supported by this algorithm
SharedImageSet* img = data.getImage(Data::UNKNOWN);
// If the data does not contain an image don't do anything
if (img == 0)
return false;
// Requirements are met, create the algorithm if asked otherwise just indicate support
// for input data.
if (a)
{
*a = new DemoAlgorithm(img);
(*a)->setInput(data);
}
return true;
}
// Called by framework or controller to perform computations
void DemoAlgorithm::compute()
{
// Create output data
m_imgOut = new SharedImageSet();
// Perform processing
Progress::Task pt(m_progress, 1, "Computing...");
// ...
pt.incrementStep();
// Add result to output image set
m_imgOut->add(<result>);
}
// Called by framework or controller to retrieve output data
OwningDataList DemoAlgorithm::takeOutput()
{
// Transfer the algorithm output to the caller
OwningDataList result;
if (m_imgOut)
result.add(std::move(m_imgOut));
return result;
}
// Configure algorithm from properties
void DemoAlgorithm::configure(const Properties* p)
{
if (p == 0)
return;
p->param("factor",m_factor);
}
// Save algorithm parameters to properties
void DemoAlgorithm::configuration(Properties* p) const
{
if (p == 0)
return;
// Last parameter to setParam specifies default value
p->setParam("factor",m_factor,2);
}
void DemoAlgorithm::setProgress(Progress* p)
{
m_progress = p;
}
}

Defining the Algorithm Controller

If desired an algorithm controller can be written to implement custom logic and user interface components for interacting with the algorithm. The algorithm controller needs to derive from the AlgorithmController class. Typically the algorithm controller class also derives from QWidget to be able to display a widget; however, this is not a requirement. If a controller is available for an algorithm the framework creates the controller after creating the algorithm. The previously created algorithm object is passed to the algorithm controller constructor so that it can be accessed from the controller. Then the init method which every controller needs to implement is called by the framework. Use this method for initializations requiring access to MainWindowBase or DisplayWidgetMulti as init is called only after the m_disp and m_main attributes have been set. A controller with user interface components additionally needs to contain methods to react to user interaction such as a button press to call methods on the actual algorithm instance. While it is possible to perform all computations inside the controller and leave the algorithm as a stub, this should be avoided for the sake of a clean separation of GUI and processing code and to enable the usage of algorithms from non-GUI aware code.

A controller can fully interact with the framework allowing it to add new data to the data model (m_main->dataModel()->add(<data>)) or to add custom views (m_disp->addView(<view>)) or annotations (m_disp->addObject(<object>, ...)).

#include <ImFusion/GUI/AlgorithmController.h>
#include <QtWidgets/QWidget>
class Ui_DemoController;
namespace ImFusion
{
class DemoAlgorithm;
// \brief Demonstrates usage of algorithm controllers
class DemoController: public QWidget, public AlgorithmController
{
Q_OBJECT
public:
// Constructor with the algorithm instance
explicit DemoController(DemoAlgorithm* algorithm);
// Destructor
~DemoController() override;
// Initializes the widget
void init() override;
public slots:
// Apply the chosen processing
void onApply();
protected:
// The actual GUI
Ui_DemoController* m_ui;
// The algorithm instance
DemoAlgorithm* m_alg;
};
}
#include "DemoController.h"
#include "DemoAlgorithm.h"
#include <ImFusion/Base/DataModel.h>
#include <ImFusion/GUI/MainWindowBase.h>
#include <ImFusion/Base/SharedImageSet.h>
#include "ui_DemoController.h"
namespace ImFusion
{
DemoController::DemoController(DemoAlgorithm* algorithm) : AlgorithmController(algorithm),
m_alg(algorithm)
{
m_ui = new Ui_DemoController();
m_ui->setupUi(this);
connect(m_ui->pushButtonApply,SIGNAL(clicked()),this,SLOT(onApply()));
}
DemoController::~DemoController()
{
delete m_ui;
}
void DemoController::init()
{
m_alg->setProgress(m_main->progress());
}
void DemoController::onApply()
{
m_alg->setFactor(m_ui->spinBoxFactor->value());
m_alg->compute();
DataList d;
m_alg->output(d);
for (auto i : d.getImages(Data::UNKNOWN))
m_main->dataModel()->add(i);
}
}

Registering the Algorithm

To make the application aware of the algorithm and optionally the controller they need to be registered with the global factory registry. To this end it is necessary to derive classes from AlgorithmFactory and AlgorithmControllerFactory respectively:

  • Inside the AlgorithmFactory-derived class the constructor needs to call registerAlgorithm with the algorithm type and the name of the algorithm. The name of the algorithm is used for display in the GUI as well as for identifying the algorithm in the factory.
  • In the AlgorithmControllerFactory-derived class the create method needs to be implemented which should test whether the supplied algorithm is of the correct type and then generate the corresponding algorithm controller.

To make the application aware of the factories - depending on your use case - it is necessary to do one of the following:

#include <ImFusion/Base/AlgorithmFactory.h>
#include <ImFusion/Base/AlgorithmControllerFactory.h>
namespace ImFusion
{
class Algorithm;
// Factory for demo algorithm
class DemoAlgorithmFactory : public AlgorithmFactory
{
public:
DemoAlgorithmFactory();
};
// Factory for demo algorithm controller
class DemoControllerFactory : public AlgorithmControllerFactory
{
public:
AlgorithmController* create(Algorithm *a) const override;
};
}
#include "DemoFactory.h"
#include "DemoAlgorithm.h"
#include "DemoController.h"
namespace ImFusion
{
DemoAlgorithmFactory::DemoAlgorithmFactory()
{
registerAlgorithm<DemoAlgorithm>("Demo;Demo algorithm");
}
AlgorithmController* DemoControllerFactory::create(Algorithm *a) const
{
if (a == 0)
return 0;
if (DemoAlgorithm *alg = dynamic_cast<DemoAlgorithm*>(a))
return new DemoController(alg);
return 0;
}
}
Search Tab / S to search, Esc to close