-
Notifications
You must be signed in to change notification settings - Fork 10
NewNode
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?
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})
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.
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.
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.
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.