Once you have completed your node implementation class, you must create a node exporter class to export your new node to OpenNI. The node exporter is a factory that allows (i) enumerating existing nodes, (ii) creating new nodes, and (iii) destroying nodes.
Creating a node exporter is done by defining a new class that inherits from xn::ModuleExportedProductionNode.
Node Description
Each node implementation has a description. The description contains the following information:
- Node Type (Depth Generator / Device / Hands Generator, etc.)
- Vendor Name
- Node Name (to distinguish two products from the same vendor)
- Version
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 your node implementation as its root.
The enumeration process is the opportunity to:
- Check if a specific hardware is attached and ready for operation. For example, an exporter of a device node will usually
- Query the operating system to find out if a specific USB device is connected right now.
- Check if this hardware is not already in use by another software component, and so cannot be used.
- If more than one device is connected, it can return two different production graph alternatives, one for each such device.
- Check that a valid license exists to use the node implementation.
- Enumerate nodes from another type that are required for this node implementation to function.
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 dependent 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 (your 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 request the exporter to destroy it by calling xn::ModuleExportedProductionNode::Destroy()
. OpenNI ensures that a node is destroyed before any of the nodes on which it depends in the production graph are destroyed.
Example A: Exporter for a node which requires a physical device
Let's take for example a device node that represents some USB physical device. The enumeration uses the operating system to find out which devices are connected right now, obtains the path to each device node, and creates a production graph for each device node. The exporter places the device path in the creation info. This allows easy access to the device path so the exporter knows which is the correct device to connect to.
The Create() method takes the creation info and passes it to the device node constructor.
{
public:
MyDevice(const XnChar* strDevicePath);
...
};
{
public:
{
strcpy(pDescription->
strVendor,
"New Devices Inc.");
strcpy(pDescription->
strName,
"MyDevice");
}
{
XnUInt32 nCount;
if (nCount == 0)
{
return XN_STATUS_DEVICE_NOT_CONNECTED;
}
for (XnUInt32 i = 0; i < nCount; ++i)
{
nRetVal = TreesList.Add(description, astrDevicePaths[i], NULL);
}
}
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;
}
}
virtual void Destroy(ModuleProductionNode* pInstance)
{
delete pInstance;
}
};
#define XN_IS_STATUS_OK(x)
Definition XnMacros.h:59
XnUInt32 XnStatus
Definition XnStatus.h:33
#define XN_STATUS_OK
Definition XnStatus.h:36
@ XN_NODE_TYPE_DEVICE
Definition XnTypes.h:105
XN_C_API void XN_C_DECL xnUSBFreeDevicesList(const XnUSBConnectionString *astrDevicePaths)
XnChar XnUSBConnectionString[XN_FILE_MAX_PATH]
Definition XnUSB.h:63
XN_C_API XnStatus XN_C_DECL xnUSBEnumerateDevices(XnUInt16 nVendorID, XnUInt16 nProductID, const XnUSBConnectionString **pastrDevicePaths, XnUInt32 *pnCount)
Definition XnModuleCppInterface.h:191
Definition XnModuleCppInterface.h:102
virtual XnStatus EnumerateProductionTrees(Context &context, NodeInfoList &TreesList, EnumerationErrors *pErrors)=0
virtual void Destroy(ModuleProductionNode *pInstance)=0
virtual void GetDescription(XnProductionNodeDescription *pDescription)=0
virtual XnStatus Create(Context &context, const XnChar *strInstanceName, const XnChar *strCreationInfo, NodeInfoList *pNeededTrees, const XnChar *strConfigurationDir, ModuleProductionNode **ppInstance)=0
XnVersion Version
Definition XnTypes.h:173
XnChar strVendor[XN_MAX_NAME_LENGTH]
Definition XnTypes.h:169
XnProductionNodeType Type
Definition XnTypes.h:167
XnChar strName[XN_MAX_NAME_LENGTH]
Definition XnTypes.h:171
XnUInt8 nMinor
Definition XnTypes.h:156
XnUInt8 nMajor
Definition XnTypes.h:155
XnUInt16 nMaintenance
Definition XnTypes.h:157
XnUInt32 nBuild
Definition XnTypes.h:158
Example B: Exporter for a node which requires one input node
For example, if you want to build a hands generator that works over an RGB image map. The exporter must declare that the node needs an image generator as input.
{
public:
MyHandsGenerator(ImageGenerator imageGen);
...
};
{
public:
{
strcpy(pDescription->
strVendor,
"New Algorithms Inc.");
strcpy(pDescription->
strName,
"MyHandsGenerator");
}
{
return context.AutoEnumerateOverSingleInput(
TreesList,
description,
NULL,
pErrors,
NULL
);
}
virtual XnStatus Create(Context& context,
const XnChar* strInstanceName,
const XnChar* strCreationInfo,
NodeInfoList* pNeededTrees, const XnChar* strConfigurationDir, ModuleProductionNode** ppInstance)
{
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;
{
xnLogError("MyHandsGenerator", "Got a production graph different from the one returned in Enumerate()!");
return XN_STATUS_ERROR;
}
ImageGenerator image;
nRetVal = imageInfo.GetInstance(image);
*ppInstance = new MyHandsGenerator(image);
if (*ppInstance == NULL)
{
return XN_STATUS_ALLOC_FAILED;
}
}
virtual void Destroy(ModuleProductionNode* pInstance)
{
delete pInstance;
}
};
@ XN_NODE_TYPE_IMAGE
Definition XnTypes.h:111
Definition XnModuleCppInterface.h:452
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 graph alternatives that require both an ImageGenerator node and a DepthGenerator node.
{
public:
MyHandsGenerator(ImageGenerator& imageGen, DepthGenerator& depthGen);
...
};
{
public:
{
strcpy(pDescription->
strVendor,
"New Algorithms Inc.");
strcpy(pDescription->
strName,
"MyHandsGenerator");
}
{
NodeInfoList imageList;
NodeInfoList depthList;
for (NodeInfoList::Iterator imageIt = imageList.Begin(); imageIt != imageList.End(); ++imageIt)
{
for (NodeInfoList::Iterator depthIt = depthList.Begin(); depthIt != depthList.End(); ++depthIt)
{
NodeInfoList neededNodes;
nRetVal = neededNodes.AddNodeFromAnotherList(imageIt);
nRetVal = neededNodes.AddNodeFromAnotherList(depthIt);
nRetVal = TreesList.Add(
description,
NULL,
&neededNodes
);
}
}
}
virtual XnStatus Create(Context& context,
const XnChar* strInstanceName,
const XnChar* strCreationInfo,
NodeInfoList* pNeededTrees, const XnChar* strConfigurationDir, ModuleProductionNode** ppInstance)
{
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;
++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;
++it != pNeededTrees->End())
{
xnLogError("MyHandsGenerator", "Got a production graph different from the one returned in Enumerate()!");
return XN_STATUS_ERROR;
}
ImageGenerator image;
nRetVal = imageInfo.GetInstance(image);
DepthGenerator depth;
nRetVal = depthInfo.GetInstance(depth);
*ppInstance = new MyHandsGenerator(image, depth);
if (*ppInstance == NULL)
{
return XN_STATUS_ALLOC_FAILED;
}
}
virtual void Destroy(ModuleProductionNode* pInstance)
{
delete pInstance;
}
};
@ XN_NODE_TYPE_DEPTH
Definition XnTypes.h:108