OpenNI 1.5.2
Creating a Node Exporter

Once we completed our node implementation class, we should create a node exporter class to export our new node to OpenNI. The node exporter is some sort of a factory. It allows getting some information about this node implementation, it supports enumerating which instances of this implementation can be created at the moment, and allows creating such instances and destroying them.

Creating a node exporter is done by defining a new class inheriting from xn::ModuleExportedProductionNode.

Node Description

Each node implementation has a description. The description contains the following information:

This description should be unique to each node implementation.

Enumeration

The enumeration process is where production graphs are created. When an application asks for a node of a specific type, OpenNI enumerates the node exporters which declared this type. The node exporter is responsible of returning the list of production graphs that can be created in order to have such a node. Of course, each such production graph will have our node implementation as its root.

The enumeration process is the opportunity to:

A production graph alternative is represented by a xn::NodeInfo object. It contains a description of the node (must be the same as the description returned by the xn::ModuleExportedProductionNode::GetDescription() method), an optional creation info and a list of depended nodes (through which the entire graph can be described).

Adding production graphs to the list is usually done using the xn::NodeInfoList::Add() method.

Note that one of the returned production graph alternatives might be used later on to create the production graph, so it's important that this alternative will fully describe the exact instance to be created. If two alternatives only differ in the way the root node (our node implementation) is created, the difference can be described in the creation info member.

If the node implementation depends on exactly one input node, it can use the xn::Context::AutoEnumerateOverSingleInput() utility method.

If no production graphs alternatives are currently available, besides returning an empty list, it is also advised to return a return value other than XN_STATUS_OK. This return value will be added to the EnumerationErrors object, so that the application can later on check why a specific node failed to enumerate.

Note: Current OpenNI interface can not pass state while enumerating. This causes a problem if production graph cycles might occur (for example, if a depth generator enumerates for depth generator, or if a hands generator enumerates for user generator which itself enumerates for hands generator). Right now, the best solution is to use a static boolean inside your Enumerate() implementation, to recognize if the method is called recursively, and if so, return nothing.

Creating the Node

Once enumeration is complete, the application can choose one of the returned production graphs alternatives and ask OpenNI to create it. OpenNI assures the node exporter that all needed nodes in the production graphs will be created before calling to xn::ModuleExportedProductionNode::Create(), so that the exporter can take those nodes and use them. In addition to the information found in the NodeInfo object (needed nodes, creation info), OpenNI passes to the exporter an instance name (the name that this node will have in OpenNI context), and a configuration dir (taken from the module registration. see Registering the new Module).

The exporter should create the node implementation it exports, and return a pointer to it to OpenNI.

Destroying the Node

Once OpenNI determines that the node is no longer needed, it will ask the exporter to destroy it by calling xn::ModuleExportedProductionNode::Destroy().

Example A: Exporter for a node which requires a physical device

Let's take for example a device node which represent some USB physical device. The enumeration uses the operating system to find out which devices are connected right now, takes the path to each of them, and create a production graph for each. The exporter places the device path in the creation info, so it would know the right device to connect to.

The Create() method takes the creation info and passes it to the device node constructor.

class MyDevice : public virtual xn::ModuleDevice
{
public:
    MyDevice(const XnChar* strDevicePath);
    ...
};

class MyDeviceExporter : public virtual xn::ModuleExportedProductionNode
{
public:
    virtual void GetDescription(XnProductionNodeDescription* pDescription) 
    {
        pDescription->Type = XN_NODE_TYPE_DEVICE;
        strcpy(pDescription->strVendor, "New Devices Inc.");
        strcpy(pDescription->strName, "MyDevice");
        pDescription->Version.nMajor = 1;
        pDescription->Version.nMinor = 0;
        pDescription->Version.nMaintenance = 0;
        pDescription->Version.nBuild = 7;
    }

    virtual XnStatus EnumerateProductionTrees(Context& context, NodeInfoList& TreesList, EnumerationErrors* pErrors) 
    {
        XnStatus nRetVal = XN_STATUS_OK;

        XnProductionNodeDescription description;
        GetDescription(&description);
        
        // find which USB device are connected
        const XnUSBConnectionString* astrDevicePaths;
        XnUInt32 nCount;

        nRetVal = xnUSBEnumerateDevices(MY_VENDOR_ID, MY_PRODUCT_ID, astrDevicePaths, &nCount);
        XN_IS_STATUS_OK(nRetVal);
        
        if (nCount == 0)
        {
            // no device was found. return an error
            return XN_STATUS_DEVICE_NOT_CONNECTED;
        }

        // add a production graph alternative for each connected device
        for (XnUInt32 i = 0; i < nCount; ++i)
        {
            nRetVal = TreesList.Add(description, astrDevicePaths[i], NULL);
            XN_IS_STATUS_OK(nRetVal);
        }

        xnUSBFreeDevicesList(astrDevicePaths);

        return (XN_STATUS_OK);
    }

    virtual XnStatus Create(Context& context, const XnChar* strInstanceName, const XnChar* strCreationInfo, 
                            NodeInfoList* pNeededTrees, const XnChar* strConfigurationDir, ModuleProductionNode** ppInstance) 
    {
        *ppInstance = new MyDevice(strCreationInfo);
        if (*ppInstance == NULL)
        {
            return XN_STATUS_ALLOC_FAILED;
        }

        return XN_STATUS_OK;
    }

    virtual void Destroy(ModuleProductionNode* pInstance) 
    {
        delete pInstance;
    }
};

Example B: Exporter for a node which requires one input node

Let's take for example a hands generator that works on an RGB image. The exporter will declare the node needs an image generator as input.

class MyHandsGenerator : public virtual xn::ModuleHandsGenerator
{
public:
    MyHandsGenerator(ImageGenerator imageGen);
    ...
};

class MyHandsExporter : public virtual xn::ModuleExportedProductionNode
{
public:
    virtual void GetDescription(XnProductionNodeDescription* pDescription) 
    {
        pDescription->Type = XN_NODE_TYPE_DEVICE;
        strcpy(pDescription->strVendor, "New Algorithms Inc.");
        strcpy(pDescription->strName, "MyHandsGenerator");
        pDescription->Version.nMajor = 1;
        pDescription->Version.nMinor = 0;
        pDescription->Version.nMaintenance = 0;
        pDescription->Version.nBuild = 7;
    }

    virtual XnStatus EnumerateProductionTrees(Context& context, NodeInfoList& TreesList, EnumerationErrors* pErrors) 
    {
        XnStatus nRetVal = XN_STATUS_OK;

        XnProductionNodeDescription description;
        GetDescription(&description);

        return context.AutoEnumerateOverSingleInput(
            TreesList,          // the list to be filled
            description,        // our description
            NULL,               // creation info. Not needed in this example.
            XN_NODE_TYPE_IMAGE, // type of the single input required
            pErrors,            // the EnumerationErrors object
            NULL                // query. Not needed in this example.
            );
    }

    virtual XnStatus Create(Context& context, const XnChar* strInstanceName, const XnChar* strCreationInfo, 
                            NodeInfoList* pNeededTrees, const XnChar* strConfigurationDir, ModuleProductionNode** ppInstance) 
    {
        XnStatus nRetVal = XN_STATUS_OK;
        
        // take the first needed node
        NodeInfoList::Iterator it = pNeededTrees->Begin();
        if (it == pNeededTrees->End())
        {
            xnLogError("MyHandsGenerator", "Got a production graph different from the one returned in Enumerate()!");
            return XN_STATUS_ERROR;
        }

        NodeInfo imageInfo = *it;

        // make sure its of the right type and that this is the only one
        if (imageInfo.GetDescription().Type != XN_NODE_TYPE_IMAGE || ++it != pNeededTrees->End())
        {
            xnLogError("MyHandsGenerator", "Got a production graph different from the one returned in Enumerate()!");
            return XN_STATUS_ERROR;
        }

        // OpenNI assures us the image node is already created
        ImageGenerator image;
        nRetVal = imageInfo.GetInstance(image);
        XN_IS_STATUS_OK(nRetVal);

        *ppInstance = new MyHandsGenerator(image);
        if (*ppInstance == NULL)
        {
            return XN_STATUS_ALLOC_FAILED;
        }

        return XN_STATUS_OK;
    }

    virtual void Destroy(ModuleProductionNode* pInstance) 
    {
        delete pInstance;
    }
};

Example C: Exporter for a node which requires two different nodes

Let's take for example a hands generator that needs both RGB information and depth information of the scene. The exporter will create production graphs alternatives that require both ImageGenerator and DepthGenerator.

class MyHandsGenerator : public virtual xn::ModuleHandsGenerator
{
public:
    MyHandsGenerator(ImageGenerator& imageGen, DepthGenerator& depthGen);
    ...
};

class MyHandsExporter : public virtual xn::ModuleExportedProductionNode
{
public:
    virtual void GetDescription(XnProductionNodeDescription* pDescription) 
    {
        pDescription->Type = XN_NODE_TYPE_DEVICE;
        strcpy(pDescription->strVendor, "New Algorithms Inc.");
        strcpy(pDescription->strName, "MyHandsGenerator");
        pDescription->Version.nMajor = 1;
        pDescription->Version.nMinor = 0;
        pDescription->Version.nMaintenance = 0;
        pDescription->Version.nBuild = 7;
    }

    virtual XnStatus EnumerateProductionTrees(Context& context, NodeInfoList& TreesList, EnumerationErrors* pErrors) 
    {
        XnStatus nRetVal = XN_STATUS_OK;

        XnProductionNodeDescription description;
        GetDescription(&description);

        // find production graph alternatives for image
        NodeInfoList imageList;
        nRetVal = context.EnumerateProductionTrees(XN_NODE_TYPE_IMAGE, NULL, imageList, pErrors);
        XN_IS_STATUS_OK(nRetVal);

        // find production graph alternatives for depth
        NodeInfoList depthList;
        nRetVal = context.EnumerateProductionTrees(XN_NODE_TYPE_DEPTH, NULL, depthList, pErrors);
        XN_IS_STATUS_OK(nRetVal);

        // now for each combination, we create one alternative
        for (NodeInfoList::Iterator imageIt = imageList.Begin(); imageIt != imageList.End(); ++imageIt)
        {
            for (NodeInfoList::Iterator depthIt = depthList.Begin(); depthIt != depthList.End(); ++depthIt)
            {
                // create needed nodes list
                NodeInfoList neededNodes;

                nRetVal = neededNodes.AddNodeFromAnotherList(imageIt);
                XN_IS_STATUS_OK(nRetVal);

                nRetVal = neededNodes.AddNodeFromAnotherList(depthIt);
                XN_IS_STATUS_OK(nRetVal);

                nRetVal = TreesList.Add(
                    description,    // our description
                    NULL,           // creation info. not needed in this example
                    &neededNodes    // needed nodes list
                    );
                XN_IS_STATUS_OK(nRetVal);
            }
        }

        return XN_STATUS_OK;
    }

    virtual XnStatus Create(Context& context, const XnChar* strInstanceName, const XnChar* strCreationInfo, 
                            NodeInfoList* pNeededTrees, const XnChar* strConfigurationDir, ModuleProductionNode** ppInstance) 
    {
        XnStatus nRetVal = XN_STATUS_OK;
        
        // take the first needed node
        NodeInfoList::Iterator it = pNeededTrees->Begin();
        if (it == pNeededTrees->End())
        {
            xnLogError("MyHandsGenerator", "Got a production graph different from the one returned in Enumerate()!");
            return XN_STATUS_ERROR;
        }

        NodeInfo imageInfo = *it;

        // take the second needed node
        ++it;
        if (it == pNeededTrees->End())
        {
            xnLogError("MyHandsGenerator", "Got a production graph different from the one returned in Enumerate()!");
            return XN_STATUS_ERROR;
        }

        NodeInfo depthInfo = *it;

        // make sure types are correct and that no more nodes were received
        if (imageInfo.GetDescription().Type != XN_NODE_TYPE_IMAGE || 
            depthInfo.GetDescription().Type != XN_NODE_TYPE_DEPTH ||
            ++it != pNeededTrees->End())
        {
            xnLogError("MyHandsGenerator", "Got a production graph different from the one returned in Enumerate()!");
            return XN_STATUS_ERROR;
        }

        // OpenNI assures us the nodes are already created
        ImageGenerator image;
        nRetVal = imageInfo.GetInstance(image);
        XN_IS_STATUS_OK(nRetVal);

        DepthGenerator depth;
        nRetVal = depthInfo.GetInstance(depth);
        XN_IS_STATUS_OK(nRetVal);

        *ppInstance = new MyHandsGenerator(image, depth);
        if (*ppInstance == NULL)
        {
            return XN_STATUS_ALLOC_FAILED;
        }

        return XN_STATUS_OK;
    }

    virtual void Destroy(ModuleProductionNode* pInstance) 
    {
        delete pInstance;
    }
};