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:
- Node Type (Depth Generator / Device / Hands Generator, etc.)
- Vendor Name
- Produce 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 our 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 our if a specific USB device is connected right now. It can also check if this hardware is not already in use by another software, and so cannot be used. If more than one device is connected, it can return two different production graphs, 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 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.
{
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;
}
};
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.
{
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;
}
};
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.
{
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;
}
};