Menu

FlowDesigner_Programmer's_Guide

Dominic Letourneau

Introduction

API and Classes Diagrams

  • Will be available soon.

Writing New Nodes

Most of the new nodes will derive from either the Node abstract class or the BufferedNode abstract class. You should use public inheritance when deriving your new class. In all cases, you will need to define a constructor for your new node class. The parameters for this constructors are: (string nodeName, const ParameterSet &params), which are used to initialize the base class, e.g.

    #include "BufferedNode.h"
    class MyNode : public BufferedNode { 
    public: 
       MyNode(nodeName, params) : BufferedNode(nodeName, params)

       ... 
    };

Also, if you derive from BufferedNode, you need to define the virtual void calculate(int output_id, int count, Buffer &out) method. The arguments are the ID of the input requested (output_id), the iteration ID (count) and the output buffer for the requested output (out). The calculate method is expected to assign an object to out[count].

If you derive directly from the Node class, you will need to override the ObjectRef getOutput(int output_id, int count) method. The meaning of output_id and count is the same as for the BufferedNode equivalent, and the result should be returned as an ObjectRef.

Here are some other methods you might want to define too:

  • void initialize(): As the name implies, it is meant to perform some initialization that cannot be done within the constructor. This method is called only once, starting the processing, but after all the request() have been made. In most (all) cases initialize() should start by calling the base class implementation (e.g. BufferedNode::initialize()).
  • void reset(): This method should return the node to the same state it was after initialize() was first called. In most (all) cases reset() should start by calling the base class implementation (e.g. BufferedNode::reset()).

In some rare cases, you will want to define the following method:

  • void request(int outputID, const ParameterSet &req): This method is meant to pass on special requests to input nodes. For now, this is mainly used by the BufferedNode class to compute the size needed for the output buffers. Remember that if you override this method, you must make sure that it propagates the request to all its input nodes. Otherwise, the nodes that won't be reached will have incorrect buffer size.

At last for a new node to be visible in flowdesigner, a special header must be present. An example of this is:

    class  MyNode;

    DECLARE_NODE( MyNode)

    /*Node
     *
     * @name  MyNode
     * @category  MyCategory:MySubCategory
     * @description  Some description of what MyNode does
     * 
     * @input_name  SOME_INPUT_NAME
     * @input_type  this_input_type
     * @input_description  Description of this input
     * 
     * @input_name  SOME_OTHER_INPUT
     * @input_type  that_input_type
     * @input_description  Description of that output
     * 
     * @output_name  SOME_OUTPUT
     * @output_type  this_output_type
     * @output_description  Description of the output
     * 
     * @parameter_name  SOME_PARAMETER
     * @parameter_type  this_parameter_type
     * @parameter_description  The description of the parameter
     *
     * END*/

Although this header is only a C++ comment, it is parsed by a PERL script to produce an XML description of each toolbox. The DECLARE_NODE(MyNode) macro is used to register the node in a dictionary when the toolbox is dynamically loaded.

Example: VAdd.cc

Most nodes must include BufferedNode.h. Also, since this node deals with vectors, we need Vector.h

#include "BufferedNode.h" 
#include "Vector.h"

//forward declaration of class VAdd for use with the DECLARE_NODE macro
class VAdd;

//Declaration of the node. This definition is transformed into XML data for the GUI, as well as documentation for the node
DECLARE_NODE(VAdd)

/*Node  
 *  
 * @name VAdd  
 * @category DSP:Base  
 * @description Adds two vectors of same length  
 *
 *  
 * @input_name INPUT1  
 * @input_type Vector<float>  
 *
 * @input_description First vector  
 *  
 * @input_name INPUT2  
 * @input_type Vector<float>  
 * @input_description Second vector  
 *  
 * @output_name OUTPUT  
 * @output_type Vector<float>  
 * @output_description Result vector    
 * 
END*/

//Class definition/implementation. Note that because we won't need to derive from this class, we don't need a header file (.h) and we can put everything in the .cc. Our node, like most other nodes, derives from BufferedNode.

    class VAdd : public BufferedNode {
       int input1ID;
       int input2ID;
       int outputID;
    public:
       VAdd(string nodeName, ParameterSet params)
       : BufferedNode(nodeName, params)
       {

          //In the constructor, we create both the inputs and outputs.
          input1ID = addInput("INPUT1");
          input2ID = addInput("INPUT2");
          outputID = addOutput("OUTPUT");
       }

       //This is the main method for the node, it is called from the BufferedNode class each time a result needs to be calculated.
       void calculate(int output_id, int count, Buffer &out)
       { 
          //Get input data from previous node(s).
          ObjectRef input1Value = getInput(input1ID, count);
          ObjectRef input2Value = getInput(input2ID, count); 
          //We cast the generic objects (received through ObjectRefs) into a reference to a Vector<float>. 
          //If the cast fails, an exception will automatically be thrown.

          const Vector<float> &in1 = object_cast<float> > (input1Value);
          const Vector<float> &in2 = object_cast<float> > (input2Value);

          //Check that the size of the two vectors match. Otherwise, throw an exception. 
          //Here __FILE__ and __LINE__ are pre-processor macros that will print the file and line where this exception was thrown.

          if (in1.size() != in2.size())
              throw new NodeException(this, 
                                      "Input vectors must be of same length",
                                       __FILE__, __LINE__);

          int inputLength = in1.size();

          //Allocate a new Vector<float> from the pool of free vectors (that's why we don't use new).
          Vector<float> = &output = *Vector<float>::alloc(inputLength);

          //Put the new Vector<float> in the return buffer.
          out[count] = &output;

          //Compute the result of the sum.
          for (int i=0;i

Adding New Data Type

It is possible to define new types in FlowDesigner. In order to be used in new nodes, new types must derive from the Object base class. That the only absolute requirement. However, if you want the new type to integrate more closely with FlowDesigner, there are several things you can do:

  • Implement the void printOn(ostream &out) const method. This method writes the object to the out stream.
  • Implement the void readFrom (istream &in).
  • Add the macro DECLARE_TYPE(MyType) to the C++ file where the object is implemented.
    There is a certain format which all Object must respect. The object should start with "<MyType" and end with ">" (without the quotes). Usually, every field will be inside

Creating New Operators

Double Dispatched Operators

It is possible to define binary operators that can act on different kinds of input. One example is the "add" operator, which can be used to add two ints, two floats, two vectors, or an int and a float, ... See data-flow/include/operators.h


Related

FlowDesigner-Wiki: DocumentationMain

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.