Skip to content

NewNode

Sebastian Buck edited this page Aug 15, 2016 · 2 revisions

How to create a new node

The goal of this tutorial is to create a new node deriving from Node to be used in cs::APEX.

The node you are going to create will take two images as input and will have a toggle switch so select which one to publish on its output connector.


Next Tutorial: How to create a core plugin?


Maintenance

To begin, open a terminal and navigate to the tutorial package:

roscd csapex_tutorial/

Let's create a new class called UserSwitch:

touch src/user_switch.cpp

and add the file to CMakeLists.txt

gedit CMakeLists.txt

To create a new plug-in library, add the following lines to CMakeLists.txt

add_library(csapex_tutorial_node
  src/user_switch.cpp
)
target_link_libraries(csapex_tutorial_node
  ${catkin_LIBRARIES}
)
install(TARGETS csapex_tutorial_node
        ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
        LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
        RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION})

Scaffolding

Now we're ready to create the plug-in. You can now use QtCreator to open the CMakeLists.txt, if you want.

Open user_switch.cpp and insert:

/// PROJECT
#include <csapex/model/node.h>
#include <csapex/csapex_fwd.h>

/// SYSTEM
#include <csapex/utility/register_apex_plugin.h>

namespace csapex
{

namespace tutorial
{

class UserSwitch : public Node
{
public:
    UserSwitch() 
    {
    }


    /// initialize the node
    virtual void setup(csapex::NodeModifier& node_modifier) override
    {
    }

    /// register parameters
    virtual void setupParameters(Parameterizable& parameters) override
    {
    }

    /// callback when all messages arrived
    virtual void process(csapex::NodeModifier& node_modifier, csapex::Parameterizable& parameters) override
    {
    }

private:
};

} // namespace tutorial

} // namespace csapex

// Register the class
CSAPEX_REGISTER_CLASS(csapex::tutorial::UserSwitch, csapex::Node)

This is the basic scaffolding code for any processing node, where

virtual void setup(csapex::NodeModifier& node_modifier) override

is used to set up inputs and outputs of the node,

virtual void setupParameters(Parameterizable& parameters) override

is used to set up parameters of the node and

virtual void process(csapex::NodeModifier& node_modifier, csapex::Parameterizable& parameters) override

is called whenever all inputs are available and the node should be executed.

The line

CSAPEX_REGISTER_CLASS(csapex::tutorial::UserSwitch, csapex::Node)

is necessary to register the class at runtime to the plug-in system.

Export

Next we need to export the plug-in in the file plugins.xml. Change the file to the following :

<library path="libcsapex_tutorial_node">
  <class type="csapex::tutorial::UserSwitch" base_class_type="csapex::Node">
    <description>Lets the user select one of two input images.</description>
    <tags>Tutorial</tags>
  </class>
</library>

Now you can start using UserSwitch in cs::APEX. Just open the GUI and load the tutorial Tutorials / Plug-in Creation / 01 - UserSwitch. You will get some error messages, because the node does not yet do anything.

Inputs and Outputs

Next we register inputs and outputs and process messages received from them:

/// PROJECT
#include <csapex/model/node.h>
#include <csapex/csapex_fwd.h>
#include <csapex/model/node_modifier.h>
#include <csapex_opencv/cv_mat_message.h>
#include <csapex/msg/io.h>

/// SYSTEM
#include <csapex/utility/register_apex_plugin.h>

namespace csapex
{

namespace tutorial
{

class UserSwitch : public Node
{
public:
    UserSwitch()
    {
    }


    /// initialize the node
    virtual void setup(csapex::NodeModifier& node_modifier) override
    {
        // Connector for the first input image
        in_a_ = node_modifier.addInput<connection_types::CvMatMessage>("Image 1");

        // Connector for the second input image
        in_b_ = node_modifier.addInput<connection_types::CvMatMessage>("Image 2");

        // Connector for the output input image (sub id = 0)
        out_ = node_modifier.addOutput<connection_types::CvMatMessage>("Either image 1 or image 2");

    }

    /// register parameters
    virtual void setupParameters(Parameterizable& parameters) override
    {
    }

    /// callback when all messages arrived
    virtual void process(csapex::NodeModifier& node_modifier, csapex::Parameterizable& parameters) override
    {
        // read the images from both inputs
        auto img_msg_a = msg::getMessage<connection_types::CvMatMessage>(in_a_);
        auto img_msg_b = msg::getMessage<connection_types::CvMatMessage>(in_b_);

        // for now, publish the first one to the output
        msg::publish(out_, img_msg_a);
    }

private:
    Input* in_a_;
    Input* in_b_;

    Output* out_;
};

} // namespace tutorial

} // namespace csapex

// Register the class
CSAPEX_REGISTER_CLASS(csapex::tutorial::UserSwitch, csapex::Node)

If you load the tutorial config now, you should see that the node has two inputs that are connected to different image sources. The OutputDisplay should display the first of the two images.

Parameters

For the last step, we want to add a parameter to select, which of the two input images should be sent forward. For this we add a SetParameter. This is a little overkill since there are only two options, however it demonstrates the parameter system a little bit better:

/// PROJECT
#include <csapex/model/node.h>
#include <csapex/csapex_fwd.h>
#include <csapex/model/node_modifier.h>
#include <csapex_opencv/cv_mat_message.h>
#include <csapex/msg/io.h>
#include <csapex/param/parameter_factory.h>

/// SYSTEM
#include <csapex/utility/register_apex_plugin.h>

namespace csapex
{

namespace tutorial
{

class UserSwitch : public Node
{
private:
    enum class Selector {
        FIRST,
        SECOND
    };

public:
    UserSwitch()
    {
    }


    /// initialize the node
    virtual void setup(csapex::NodeModifier& node_modifier) override
    {
        // Connector for the first input image
        in_a_ = node_modifier.addInput<connection_types::CvMatMessage>("Image 1");

        // Connector for the second input image
        in_b_ = node_modifier.addInput<connection_types::CvMatMessage>("Image 2");

        // Connector for the output input image (sub id = 0)
        out_ = node_modifier.addOutput<connection_types::CvMatMessage>("Either image 1 or image 2");

    }

    /// register parameters
    virtual void setupParameters(Parameterizable& parameters) override
    {
        /// for demonstration we map an enum to a set parameter of type int
        std::map<std::string, int> selector = {
            {"FIRST", (int) Selector::FIRST},
            {"SECOND", (int) Selector::SECOND},
        };
        
        // we register the parameter with the name "selector", the default value of "Selector::FIRST" and a callback lambda function
        parameters.addParameter(param::ParameterFactory::declareParameterSet("selector", selector, (int) Selector::FIRST),
                                [this](param::Parameter* p) {
            /// this callback is called whenever the parameter value changes
            /// it is also called once before the first call of process 
            selector_ = static_cast<Selector>(p->as<int>());
        });
    }

    /// callback when all messages arrived
    virtual void process(csapex::NodeModifier& node_modifier, csapex::Parameterizable& parameters) override
    {
        connection_types::CvMatMessage::ConstPtr output_msg;
        // selector_ is set whenever the parameter changes
        switch(selector_) {
        case Selector::FIRST:
            output_msg = msg::getMessage<connection_types::CvMatMessage>(in_a_);
            break;
        case Selector::SECOND:
            output_msg = msg::getMessage<connection_types::CvMatMessage>(in_b_);
            break;
        default:
            throw std::runtime_error("unknown selector");
        }

        msg::publish(out_, output_msg);
    }

private:
    Input* in_a_;
    Input* in_b_;

    Output* out_;

    Selector selector_;
};

} // namespace tutorial

} // namespace csapex

// Register the class
CSAPEX_REGISTER_CLASS(csapex::tutorial::UserSwitch, csapex::Node)

We are now done with the node. You can again load up the tutorial config and try changing the parameter value.


In the next tutorial we are looking at how to create a core plug-in that extends base functionality.

Clone this wiki locally