Skip to content

Create your own algorithm in FAST

Erik Smistad edited this page Sep 3, 2015 · 17 revisions

This page is a tutorial on how to create your own algorithm in FAST.

The algorithm we will create in this tutorial is very simple. It will double the value of every pixel/voxel in an image and thus we call it DoubleFilter.

First off, we have to define the DoubleFilter class. All algorithms have to extend the ProcessObject class and implement a method called execute. This method is used to execute the algorithm and is called when the pipeline is updated. See the framework design page for details on how the execution pipeline works.

In a file called DoubleFilter.hpp, write the following:

#ifndef DOUBLEFILTER_HPP
#define DOUBLEFILTER_HPP

#include "FAST/ProcessObject.hpp"

namespace fast {

/**
 * This is an example filter which doubles the value of each element in an image
 */
class DoubleFilter : public ProcessObject {
    FAST_OBJECT(DoubleFilter)
    private:
        // Constructor
        DoubleFilter();
        // This method will execute the algorithm
        void execute();
};

}; // namespace fast

#endif // DOUBLEFILTER_HPP

And here is the implementation of the class (DoubleFilter.cpp):

#include "DoubleFilter.hpp"
#include "FAST/Data/Image.hpp"

namespace fast {

DoubleFilter::DoubleFilter() {
    // This creates an input port of type Image.
    createInputPort<Image>(0);

    // This creates an output port of type Image.
    // The OUTPUT_DEPENDS_ON_INPUT flag means that if the input
    // is a static image, the output will also be a static image,
    // and if the input is a dynamic image, the output will also
    // be a dynamic image.
    createOutputPort<Image>(0, OUTPUT_DEPENDS_ON_INPUT, 0);

    // This creates an OpenCL program from a source file on disk
    createOpenCLProgram(std::string(FAST_SOURCE_DIR) + "Tests/Algorithms/DoubleFilter.cl");
}

/*
 * This function performs the filter serially on the Host using plain old C++.
 * It is templated so that it can support images of different data types.
 */
template<class T>
inline void executeAlgorithmOnHost(Image::pointer input, Image::pointer output) {
    ImageAccess::pointer inputAccess = input->getImageAccess(ACCESS_READ);
    ImageAccess::pointer outputAccess = output->getImageAccess(ACCESS_READ_WRITE);

    T * inputData = (T*)inputAccess->get();
    T * outputData = (T*)outputAccess->get();

    unsigned int nrOfElements = input->getWidth()*input->getHeight()*input->getDepth()*input->getNrOfComponents();
    for(unsigned int i = 0; i < nrOfElements; i++) {
        outputData[i] = 2.0*inputData[i];
    }
}

void DoubleFilter::execute() {
    // Get input and output data
    Image::pointer input = getStaticInputData<Image>();
    Image::pointer output = getStaticOutputData<Image>();

    // Initialize output image
    output->createFromImage(input);

    if(getMainDevice()->isHost()) {
        // Execution device is Host, use the executeAlgorithmOnHost function with the given data type
        switch(input->getDataType()) {
            // This macro creates a case statement for each data type and sets FAST_TYPE to the correct C++ data type
            fastSwitchTypeMacro(executeAlgorithmOnHost<FAST_TYPE>(input, output));
        }
    } else {
        // Execution device is an OpenCL device
        OpenCLDevice::pointer device = getMainDevice();

        // Set build options based on the data type of the data
        std::string buildOptions = "-DTYPE=" + getCTypeAsString(input->getDataType());

        // Compile the code
        cl::Kernel kernel = cl::Kernel(getOpenCLProgram(device, "", buildOptions), "doubleFilter");

        // Get global size for the kernel
        cl::NDRange globalSize(input->getWidth()*input->getHeight()*input->getDepth()*input->getNrOfComponents());

        // Set the arguments for the kernel
        OpenCLBufferAccess::pointer inputAccess = input->getOpenCLBufferAccess(ACCESS_READ, device);
        OpenCLBufferAccess::pointer outputAccess = output->getOpenCLBufferAccess(ACCESS_READ_WRITE, device);
        kernel.setArg(0, *inputAccess->get());
        kernel.setArg(1, *outputAccess->get());

        // Execute the kernel
        device->getCommandQueue().enqueueNDRangeKernel(
                kernel,
                cl::NullRange,
                globalSize,
                cl::NullRange
        );
    }
}

} // end namespace fast

And the OpenCL kernel code (DoubleFilter.cl):

__kernel void doubleFilter(
        __global TYPE * input,
        __global TYPE * output
        ) {
    output[get_global_id(0)] = input[get_global_id(0)]*2;
}

Using the filter

// Import an image
ImageFileImporter::pointer importer = ImageFileImporter::New();
importer->setFilename("someimage.png");

// Run the filter
DoubleFilter::pointer filter = DoubleFilter::New();
filter->setInputConnection(importer->getOutputPort());

// Render image
ImageRenderer::pointer renderer = ImageRenderer::New();
renderer->addInputConnection(filter->getOutputPort());

// Create window and start pipeline
SimpleWindow::pointer window = SimpleWindow::New();
window->addRenderer(renderer);
window->start();
Clone this wiki locally