Euresys::EGrabber
Euresys::EGrabber is a library of C++ classes that provide a high-level interface. It is built on top of the Euresys::EGenTL library, and is recommended for most users.
A .NET assembly, built on top of the Euresys::EGrabber C++ classes, is also provided. In this document, we focus mainly on the C++ API. Minor differences between the C++ and .NET interfaces are listed in a dedicated chapter.
To use the classes described here, you need to include the main Euresys::EGrabber file:
#include <EGrabber.h>
Euresys::EGrabber is a header-only library (it isn't provided as a lib or dll file2). It comprises several classes, the most important of which is also named Euresys::EGrabber:
namespace Euresys {
class EGrabber;
}
In this text, we'll refer to this class as a grabber. A grabber encapsulates a set of related GenTL modules:
- An interface: the module that represents global (shared) frame grabber settings and features. This includes digital I/O control, PCIe and firmware status...
- A device (or local device, as opposed to remote device): the module that contains the frame grabber settings and features relating to the camera. This consists mainly of camera and illumination control features: strobes, triggers...
- A data stream: the module that handles image buffers.
- A remote device: the CoaXPress camera.
- A number of buffers.
Go back to the chapter about GenTL modules if these concepts are not clear.
A first example
This example creates a grabber and displays basic information about the interface, device, and remote device modules it contains:
#include <iostream>
#include <EGrabber.h> // 1
static const uint32_t CARD_IX = 0;
static const uint32_t DEVICE_IX = 0;
void showInfo() {
Euresys::EGenTL gentl; // 2
Euresys::EGrabber<> grabber(gentl, CARD_IX, DEVICE_IX); // 3
std::string card = grabber.getString<Euresys::InterfaceModule>("InterfaceID"); // 4
std::string dev = grabber.getString<Euresys::DeviceModule>("DeviceID"); // 5
int64_t width = grabber.getInteger<Euresys::RemoteModule>("Width"); // 6
int64_t height = grabber.getInteger<Euresys::RemoteModule>("Height"); // 6
std::cout << "Interface: " << card << std::endl;
std::cout << "Device: " << dev << std::endl;
std::cout << "Resolution: " << width << "x" << height << std::endl;
}
int main() {
try { // 7
showInfo();
} catch (const std::exception &e) { // 7
std::cout << "error: " << e.what() << std::endl;
}
}
-
Include EGrabber.h, which defines the Euresys::EGrabber class, and includes the other header files we need (such as EGenTL.h and the standard GenTL header file).
-
Create a Euresys::EGenTL object. This involves the following operations:
- locate and dynamically load the Coaxlink GenTL producer (coaxlink.cti);
- retrieve pointers to the functions exported by coaxlink.cti, and make them available via Euresys::EGenTL methods;
- initialize coaxlink.cti (this is done by calling the GenTL initialization function GCInitLib).
-
Create a Euresys::EGrabber object. The constructor needs the gentl object created in step 2. It also takes as optional arguments the indices of the interface and device to use.
The purpose of the angle brackets (<>) that come after EGrabber will become clear later. For now, they can be safely ignored.
-
Use GenApi to find out the ID of the Coaxlink card. Euresys::InterfaceModule indicates that we want an answer from the interface module.
-
Similarly, find out the ID of the device. This time, we use Euresys::DeviceModule to target the device module.
-
Finally, read the camera resolution. Euresys::RemoteModule indicates that the value must be retrieved from the camera.
-
Euresys::EGrabber uses exceptions to report errors, so we wrap our code inside a try ... catch block.
Example of program output:
Interface: PC1633 - Coaxlink Quad G3 (2-camera) - KQG00014
Device: Device0
Resolution: 4096x4096
Acquiring images
This program uses Euresys::EGrabber to acquire images from a camera connected to a Coaxlink card:
#include <iostream>
#include <EGrabber.h>
void grab() {
Euresys::EGenTL gentl;
Euresys::EGrabber<> grabber(gentl); // 1
grabber.reallocBuffers(3); // 2
grabber.start(10); // 3
for (size_t i = 0; i < 10; ++i) {
Euresys::ScopedBuffer buf(grabber); // 4
void *ptr = buf.getInfo<void *>(GenTL::BUFFER_INFO_BASE); // 5
uint64_t ts = buf.getInfo<uint64_t>(GenTL::BUFFER_INFO_TIMESTAMP); // 6
std::cout << "buffer address: " << ptr << ", timestamp: "
<< ts << " us" << std::endl;
} // 7
}
int main() {
try {
grab();
} catch (const std::exception &e) {
std::cout << "error: " << e.what() << std::endl;
}
}
-
Create a Euresys::EGrabber object. The second and third arguments of the constructor are omitted here. The grabber will use the first device of the first interface present in the system.
-
Allocate 3 buffers. The grabber automatically determines the required buffer size.
-
Start the grabber. Here, we ask the grabber to fill 10 buffers. If we don't want the grabber to stop after a specific number of buffers, we can do grabber.start(GenTL::GENTL_INFINITE), or simply grabber.start().
Starting the grabber involves the following operations:
- the AcquisitionStart command is executed on the camera;
- the DSStartAcquisition function is called to start the data stream.
In this example, we assume that the camera and frame grabber are properly configured. For a real application, it would be safer to run a configuration script before starting acquisitions (and before allocating buffers for that matter). This will be shown in another example.
-
Wait for a buffer filled by the grabber. The result is a Euresys::ScopedBuffer. The term scoped is used to indicate that the lifetime of the buffer is the current scope (i.e., the current block).
-
Retrieve the buffer address. This is done by calling the getInfo method of the buffer. This method takes as argument a BUFFER_INFO_CMD. In this case, we request the BUFFER_INFO_BASE, which is defined in the standard GenTL header file:
enum BUFFER_INFO_CMD_LIST { BUFFER_INFO_BASE = 0, /* PTR Base address of the buffer memory. */ BUFFER_INFO_SIZE = 1, /* SIZET Size of the buffer in bytes. */ BUFFER_INFO_USER_PTR = 2, /* PTR Private data pointer of the GenTL Consumer. */ BUFFER_INFO_TIMESTAMP = 3, /* UINT64 Timestamp the buffer was acquired. */ // ... // other BUFFER_INFO definitions omitted // ... BUFFER_INFO_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ }; typedef int32_t BUFFER_INFO_CMD;
Notice that getInfo is a template method, and when we call it we must specify the type of value we expect. BUFFER_INFO_BASE returns a pointer; this is why we use getInfo<void *>.
-
Do the same to retrieve the timestamp of the buffer. This time, we use the uint64_t version of getInfo to match the type of BUFFER_INFO_TIMESTAMP.
Note that, for Coaxlink, timestamps are always 64-bit and expressed as the number of microseconds that have elapsed since the computer was started.
-
We reach the end of the for block. The local variable buf gets out of scope and is destroyed: the ScopedBuffer destructor is called. This causes the GenTL buffer contained in buf to be re-queued (given back) to the data stream of the grabber.
Example of program output:
buffer address: 0x7f3c32c54010, timestamp: 11247531003686 us
buffer address: 0x7f3c2c4bf010, timestamp: 11247531058080 us
buffer address: 0x7f3c2c37e010, timestamp: 11247531085003 us
buffer address: 0x7f3c32c54010, timestamp: 11247531111944 us
buffer address: 0x7f3c2c4bf010, timestamp: 11247531137956 us
buffer address: 0x7f3c2c37e010, timestamp: 11247531163306 us
buffer address: 0x7f3c32c54010, timestamp: 11247531188600 us
buffer address: 0x7f3c2c4bf010, timestamp: 11247531213807 us
buffer address: 0x7f3c2c37e010, timestamp: 11247531239158 us
buffer address: 0x7f3c32c54010, timestamp: 11247531265053 us
We can see that the three buffers that were allocated (let's call them A at 0x7f3c32c54010, B at 0x7f3c2c4bf010, and C at 0x7f3c2c37e010) are used in a round-robin fashion: A → B → C → A → B → C → ... This is the result of:
- the FIFO nature of input and output buffer queues:
- the Coaxlink driver pops a buffer from the front of the input queue, and gives it to the Coaxlink card for DMA transfer;
- when the transfer is complete, the buffer is pushed to the back of the output queue;
- the use of ScopedBuffer:
- the ScopedBuffer constructor pops a buffer from the front of the output queue (i.e., it takes the oldest buffer);
- the ScopedBuffer destructor pushes that buffer to the back of the input queue (hence, this buffer will be used for a new transfer after all buffers already in the input queue).
Configuring the grabber
Configuration is a very important aspect of any image acquisition program.
- The camera and the frame grabber both have to be configured according to the application requirements.
- The camera configuration must be compatible with the frame grabber configuration, and vice versa.
Configuration basically boils down to a series of set/get operations performed on the grabber modules: the remote device (i.e., the camera), the interface, the device, or the data stream modules.
This program configures the grabber for the so-called RG control mode (asynchronous reset camera control, frame grabber-controlled exposure).
#include <iostream>
#include <EGrabber.h>
const double FPS = 150;
void configure() {
Euresys::EGenTL gentl;
Euresys::EGrabber<> grabber(gentl);
// camera configuration
grabber.setString<Euresys::RemoteModule>("TriggerMode", "On"); // 1
grabber.setString<Euresys::RemoteModule>("TriggerSource", "CXPin"); // 2
grabber.setString<Euresys::RemoteModule>("ExposureMode", "TriggerWidth"); // 3
// frame grabber configuration
grabber.setString<Euresys::DeviceModule>("CameraControlMethod", "RG"); // 4
grabber.setString<Euresys::DeviceModule>("CycleTriggerSource", "Immediate"); // 5
grabber.setFloat<Euresys::DeviceModule>("CycleTargetPeriod", 1e6 / FPS); // 6
}
int main() {
try {
configure();
} catch (const std::exception &e) {
std::cout << "error: " << e.what() << std::endl;
}
}
-
Enable triggers on the camera.
-
Tell the camera to look for triggers on the CoaXPress link.
-
Configure the camera to use the TriggerWidth exposure mode.
-
Set the frame grabber's camera control method to RG. In this mode, camera cycles are initiated by the frame grabber, and the exposure duration is also controlled by the frame grabber.
-
Tell the frame grabber to initiate camera cycles itself (at a rate defined by CycleTargetPeriod), without waiting for hardware or software triggers.
-
Configure the frame rate.
But there is a better way to configure the grabber. Using a script file, the program becomes:
#include <iostream>
#include <EGrabber.h>
void configure() {
Euresys::EGenTL gentl;
Euresys::EGrabber<> grabber(gentl);
grabber.runScript("config.js");
}
int main() {
try {
configure();
} catch (const std::exception &e) {
std::cout << "error: " << e.what() << std::endl;
}
}
and the configuration script is:
var grabber = grabbers[0];
var FPS = 150;
// camera configuration
grabber.RemotePort.set("TriggerMode", "On");
grabber.RemotePort.set("TriggerSource", "CXPin");
grabber.RemotePort.set("ExposureMode", "TriggerWidth");
// frame grabber configuration
grabber.DevicePort.set("CameraControlMethod", "RG");
grabber.DevicePort.set("CycleTriggerSource", "Immediate");
grabber.DevicePort.set("CycleTargetPeriod", 1e6 / FPS);
Using a script file has several advantages:
-
The configuration can be changed without recompiling the application. This allows shorter development cycles, and makes it possible to update the configuration in the lab or in the field.
-
The configuration script can be loaded by the GenICam Browser and the command-line gentl tool. This makes is possible to validate the configuration outside of the user application.
-
The configuration script can easily be shared by several applications written in different programming languages: C++, C#, VB.NET...
-
The full power of Euresys GenApi scripts is available.
Events
Background
Coaxlink cards generate different kinds of events:
- New buffer events: events indicating that a buffer has been filled by a data stream.
- Data stream events: events related to a data stream and its frame store.
- Camera and illumination controller events: events related to the real-time control (performed by a device) of a camera and its illumination devices.
- I/O toolbox events: events (coming from the interface) related to digital I/O lines and other I/O tools.
- CoaXPress interface events: events (also coming from the interface) related to the CoaXPress interface.
New buffer events are standard in GenTL. They occur when a buffer is filled by the frame grabber. Information attached to new buffer events include the handle of the buffer and a timestamp.
The other types of events are restricted to Coaxlink and can be viewed as categories of specific events. For example, in the CIC category of events, we have:
- CameraTriggerRisingEdge (start of camera trigger)
- CameraTriggerFallingEdge (end of camera trigger)
- StrobeRisingEdge (start of light strobe)
- StrobeFallingEdge (end of light strobe)
- AllowNextCycle (CIC is ready for next camera cycle)
- ...
and in the I/O toolbox category of events, we have:
- LIN1 (line input tool 1)
- LIN2 (line input tool 2)
- MDV1 (multiplier/divider tool 1)
- ...
Counters
Coaxlink firmware counts each occurrence of each event (except new buffer events) and makes this counter available in a GenApi feature named EventCount. Each event has its own counter, and the value of EventCount depends on the selected event:
// select the CameraTriggerRisingEdge event
grabber.setString<DeviceModule>("EventSelector", "CameraTriggerRisingEdge");
// read the value of the counter
int64_t counter = grabber.getInteger<DeviceModule>("EventCount");
or, using the selected feature notation:
// read the value of the CameraTriggerRisingEdge counter
int64_t counter = grabber.getInteger<DeviceModule>("EventCount[CameraTriggerRisingEdge]");
Notifications
As we've just seen, when an event occurs, a dedicated counter is incremented. Coaxlink can also notify the application of this event by having Euresys::EGrabber execute a user-defined callback function. But first, it is required to enable notifications of one or more events:
grabber.setString<DeviceModule>("EventSelector", "CameraTriggerRisingEdge");
grabber.setInteger<DeviceModule>("EventNotification", true);
grabber.setString<DeviceModule>("EventSelector", "CameraTriggerFallingEdge");
grabber.setInteger<DeviceModule>("EventNotification", true);
...
or:
grabber.setInteger<DeviceModule>("EventNotification[CameraTriggerRisingEdge]", true);
grabber.setInteger<DeviceModule>("EventNotification[CameraTriggerFallingEdge]", true);
...
Using a configuration script, it is easy to enable notifications for all events:
function enableAllEvents(p) { // 1
var events = p.$ee('EventSelector'); // 2
for (var e of events) {
p.set('EventNotification[' + e + ']', true); // 3
}
}
var grabber = grabbers[0];
enableAllEvents(grabber.InterfacePort); // 4
enableAllEvents(grabber.DevicePort); // 5
enableAllEvents(grabber.StreamPort); // 6
-
Define a helper function named enableAllEvents and taking as argument a module (or port) p.
-
Use the $ee function to retrieve the list of values EventSelector can take. This is the list of events generated by module p. (ee stands for enum entry.)
-
For each event, enable notifications. (The + operator concatenates strings, so if e is 'LIN1', the expression 'EventNotification[' + e + ']' evaluates to 'EventNotification[LIN1]'.)
-
Call the enableAllEvents function defined in step 1 for the interface module. This will enable notifications for all events in the I/O toolbox and CoaXPress interface categories.
-
Likewise, enable notifications for all events coming from the device module (CIC events).
-
Finally, enable notifications for all data stream events.
Callback functions
When an event occurs, and event notification is enabled for that event, Euresys::EGrabber executes one of several callback functions.
These callback functions are defined in overridden virtual methods:
class MyGrabber : public Euresys::EGrabber<>
{
public:
...
private:
// callback function for new buffer events
virtual void onNewBufferEvent(const NewBufferData& data) {
...
}
// callback function for data stream events
virtual void onDataStreamEvent(const DataStreamData &data) {
...
}
// callback function for CIC events
virtual void onCicEvent(const CicData &data) {
...
}
// callback function for I/O toolbox events
virtual void onIoToolboxEvent(const IoToolboxData &data) {
...
}
// callback function for CoaXPress interface events
virtual void onCxpInterfaceEvent(const CxpInterfaceData &data) {
...
}
};
As you can see, a different callback function can be defined for each category of events.
In .NET, callback functions are defined by creating delegates rather than overriding virtual methods. An example will be given in the chapter about the .NET assembly.
Event identification
When an event is notified to the application, the callback function that is executed indicates the category of that event. The actual event that occurred is identified by a numerical ID, called numid, and defined in include/GenTL_v1_5_EuresysCustom.h:
enum EVENT_DATA_NUMID_CUSTOM_LIST
{
// EVENT_CUSTOM_IO_TOOLBOX
EVENT_DATA_NUMID_IO_TOOLBOX_LIN1 = ... /* Line Input Tool 1 */
EVENT_DATA_NUMID_IO_TOOLBOX_LIN2 = ... /* Line Input Tool 2 */
EVENT_DATA_NUMID_IO_TOOLBOX_MDV1 = ... /* Multiplier/Divider Tool 1 */
...
// EVENT_CUSTOM_CXP_INTERFACE
...
// EVENT_CUSTOM_CIC
EVENT_DATA_NUMID_CIC_CAMERA_TRIGGER_RISING_EDGE = ... /* Start of camera trigger */
EVENT_DATA_NUMID_CIC_CAMERA_TRIGGER_FALLING_EDGE = ... /* End of camera trigger */
EVENT_DATA_NUMID_CIC_STROBE_RISING_EDGE = ... /* Start of light strobe */
EVENT_DATA_NUMID_CIC_STROBE_FALLING_EDGE = ... /* End of light strobe */
...
// EVENT_CUSTOM_DATASTREAM
EVENT_DATA_NUMID_DATASTREAM_START_OF_CAMERA_READOUT = ... /* Start of camera readout */
EVENT_DATA_NUMID_DATASTREAM_END_OF_CAMERA_READOUT = ... /* End of camera readout */
...
};
For reference, the following table lists the relationships between:
- the module generating events
- the category of events
- the name of the callback function
- the data type passed to the callback function
- the common numid prefix
Module | Category | Callback function | Data type | numid prefix |
---|---|---|---|---|
Data stream | New Buffer | onNewBufferEvent | NewBufferData | - |
Data stream | Data Stream | onDataStreamEvent | DataStreamData | EVENT_DATA_NUMID_DATASTREAM_ |
Device | CIC | onCicEvent | CicData | EVENT_DATA_NUMID_CIC_ |
Interface | I/O Toolbox | onIoToolboxEvent | IoToolboxData | EVENT_DATA_NUMID_IO_TOOLBOX_ |
Interface | CXP Interface | onCxpInterfaceEvent | CxpInterfaceData | EVENT_DATA_NUMID_CXP_INTERFACE_ |
Note that:
-
There is only one event in the new buffer event category, so we don't need a numid there.
-
A simple naming scheme is followed: a category of events named some category has a callback function named onSomeCategoryEvent which takes as argument a SomeCategoryData structure, and uses EVENT_DATA_NUMID_SOME_CATEGORY_ as common numid prefix.
Examples
We'll soon show a few complete example programs illustrating events and callbacks, but there is one more thing we need to explain before we can do that: the context in which callback functions are executed. This is the subject of the next section.
EGrabber flavors
When should the callback functions be called? From which context (i.e., which thread)? Thinking about these questions leads to the definition of several callback models:
-
The application asks the grabber: "Do you have any buffer or event for me? If yes, execute my callback function now." This is a polling, synchronous mode of operation, where callbacks are executed when the application demands it, in the application thread.
We'll refer to this callback model as on demand.
-
The application asks the grabber to create a single, dedicated thread, and to wait for events in this thread. When an event occurs, the grabber executes the corresponding callback function, also in this single callback thread.
We'll refer to this callback model as single thread.
-
The application asks the grabber to create a dedicated thread for each of its callback functions. In each of these threads, the grabber waits for a particular category of events. When an event occurs, the corresponding callback function is executed in that thread.
We'll refer to this callback model as multi thread.
These three callback models all make sense, and each one is best suited for some applications.
-
The on demand model is the simplest. Although its implementation may use worker threads, from the point of view of the user it doesn't add any thread. This means that the application doesn't need to worry about things such as thread synchronization, mutexes, etc.
-
If the user wants a dedicated callback thread, the single thread model creates it for him.
When we have only one thread, things are simple. In this model, the grabber is used in (at least) two threads, so we need to start worrying about synchronization and shared data.
-
In the multi thread model, each category of event gets its own thread. The benefit of this is that events of one type (and the execution of their callback functions) don't delay notifications of events of other types. For example, a thread doing heavy image processing in onNewBufferEvent will not delay the notification of CIC events by onCicEvent (e.g., events indicating that the exposure is complete and that the object or camera can be moved).
In this model, the grabber is used in several thread, so the need for synchronization is present as it is in the single thread model.
Of course, the application must also be aware that it might receive notifications for events of type X that are older than notifications of events of type Y that have already been received and processed. After all, this is the differentiating factor between the single thread and multi thread models.
To give the user maximum flexibility, we support all three callback models. This is why Euresys::EGrabber exists in different flavors. So far, we have eluded the meaning of the angle brackets in EGrabber<>. The EGrabber class is actually a template class, i.e., a class that is parameterized by another type:
-
In this case, the template parameter is the callback model to use: one of CallbackOnDemand, CallbackSingleThread or CallbackMultiThread.
-
The empty <> are used to select the default template parameter, which is CallbackOnDemand.
-
The types of grabber that can be instantiated are:
- EGrabber<CallbackOnDemand>
- EGrabber<CallbackSingleThread>
- EGrabber<CallbackMultiThread>
- EGrabber<> which is equivalent to EGrabber<CallbackOnDemand>
-
The EGrabber header file also defines the following type aliases (synonyms):
typedef EGrabber<CallbackOnDemand> EGrabberCallbackOnDemand; typedef EGrabber<CallbackSingleThread> EGrabberCallbackSingleThread; typedef EGrabber<CallbackMultiThread> EGrabberCallbackMultiThread;
-
The .NET generics don't quite match the C++ templates, so in .NET the template EGrabber class does not exist and we must use one of EGrabberCallbackOnDemand, EGrabberCallbackSingleThread or EGrabberCallbackMultiThread.
Events and callbacks examples
On demand callbacks
This program displays basic information about CIC events generated by a grabber:
#include <iostream>
#include <EGrabber.h>
using namespace Euresys; // 1
class MyGrabber : public EGrabber<CallbackOnDemand> { // 2
public:
MyGrabber(EGenTL &gentl) : EGrabber<CallbackOnDemand>(gentl) { // 3
runScript("config.js"); // 4
enableEvent<CicData>(); // 5
reallocBuffers(3);
start();
}
private:
virtual void onCicEvent(const CicData &data) {
std::cout << "timestamp: " << std::dec << data.timestamp << " us, " // 6
<< "numid: 0x" << std::hex << data.numid // 6
<< " (" << getEventDescription(data.numid) << ")"
<< std::endl;
}
};
int main() {
try {
EGenTL gentl;
MyGrabber grabber(gentl);
while (true) {
grabber.processEvent<CicData>(1000); // 7
}
} catch (const std::exception &e) {
std::cout << "error: " << e.what() << std::endl;
}
}
-
This using directive allows writing Xyz instead of Euresys::Xyz. This helps keep lines relatively short.
-
Define a new class MyGrabber which is derived from EGrabber<CallbackOnDemand>.
-
MyGrabber's constructor initializes its base class by calling EGrabber<CallbackOnDemand>'s constructor.
-
Run a config.js script which should:
- properly configure the camera and frame grabber;
- enable notifications for CIC events.
-
Enable onCicEvent callbacks.
-
The onCicEvent callback function receives a const CicData &. This structure is defined in include/EGrabberTypes.h. It contains a few pieces of information about the event that occurred. Here, we display the timestamp and numid of each event. The numid indicates which CIC event occurred.
-
Call processEvent<CicData>(1000):
- the grabber starts waiting for a CIC event;
- if an event occurs within 1000 ms, the grabber executes the onCicEvent callback function;
- otherwise, a timeout exception will be thrown.
Example of program output:
timestamp: 1502091779 us, numid: 0x8041 (Start of camera trigger)
timestamp: 1502091784 us, numid: 0x8048 (Received acknowledgement for previous CXP trigger message)
timestamp: 1502091879 us, numid: 0x8043 (Start of light strobe)
timestamp: 1502092879 us, numid: 0x8044 (End of light strobe)
timestamp: 1502097279 us, numid: 0x8042 (End of camera trigger)
timestamp: 1502097284 us, numid: 0x8048 (Received acknowledgement for previous CXP trigger message)
timestamp: 1502191783 us, numid: 0x8041 (Start of camera trigger)
timestamp: 1502191783 us, numid: 0x8045 (CIC is ready for next camera cycle)
timestamp: 1502191788 us, numid: 0x8048 (Received acknowledgement for previous CXP trigger message)
timestamp: 1502191883 us, numid: 0x8043 (Start of light strobe)
timestamp: 1502192883 us, numid: 0x8044 (End of light strobe)
timestamp: 1502197283 us, numid: 0x8042 (End of camera trigger)
timestamp: 1502197288 us, numid: 0x8048 (Received acknowledgement for previous CXP trigger message)
timestamp: 1502291788 us, numid: 0x8041 (Start of camera trigger)
timestamp: 1502291788 us, numid: 0x8045 (CIC is ready for next camera cycle)
...
Single thread and multi thread callbacks
This program displays basic information about CIC events generated by a grabber, this time using the CallbackSingleThread model.
#include <iostream>
#include <EGrabber.h>
using namespace Euresys;
class MyGrabber : public EGrabber<CallbackSingleThread> { // 1
public:
MyGrabber(EGenTL &gentl) : EGrabber<CallbackSingleThread>(gentl) { // 2
runScript("config.js");
enableEvent<CicData>();
reallocBuffers(3);
start();
}
private:
virtual void onCicEvent(const CicData &data) {
std::cout << "timestamp: " << std::dec << data.timestamp << " us, "
<< "numid: 0x" << std::hex << data.numid
<< " (" << getEventDescription(data.numid) << ")"
<< std::endl;
}
};
int main() {
try {
EGenTL gentl;
MyGrabber grabber(gentl);
while (true) { // 3
}
} catch (const std::exception &e) {
std::cout << "error: " << e.what() << std::endl;
}
}
There are very few differences between this program and the CallbackOnDemand version:
-
MyGrabber is derived from EGrabber<CallbackSingleThread> instead of EGrabber<CallbackOnDemand>.
-
Consequently, MyGrabber's constructor initializes its base class by calling EGrabber<CallbackSingleThread>'s constructor.
-
EGrabber creates a callback thread in which it calls processEvent, so we don't have to. Here, we simply enter an infinite loop.
As you can see, moving from CallbackOnDemand to CallbackSingleThread is very simple. If instead you want the CallbackMultiThread variant, simply change the base class of MyGrabber to EGrabber<CallbackMultiThread> (and call the appropriate constructor).
New buffer callbacks
This program shows how to access information related to new buffer events. It uses CallbackMultiThread, but it could use another callback method just as well.
#include <iostream>
#include <EGrabber.h>
using namespace Euresys;
class MyGrabber : public EGrabber<CallbackMultiThread> {
public:
MyGrabber(EGenTL &gentl) : EGrabber<CallbackMultiThread>(gentl) {
runScript("config.js");
enableEvent<NewBufferData>();
reallocBuffers(3);
start();
}
private:
virtual void onNewBufferEvent(const NewBufferData &data) {
ScopedBuffer buf(*this, data); // 1
uint64_t ts = buf.getInfo<uint64_t>(GenTL::BUFFER_INFO_TIMESTAMP); // 2
std::cout << "event timestamp: " << data.timestamp << " us, " // 3
<< "buffer timestamp: " << ts << " us" << std::endl;
} // 4
};
int main() {
try {
EGenTL gentl;
MyGrabber grabber(gentl);
while (true) {
}
} catch (const std::exception &e) {
std::cout << "error: " << e.what() << std::endl;
}
}
-
In onNewBufferEvent, create a temporary ScopedBuffer object buf. The ScopedBuffer constructor takes two arguments:
- the grabber owning the buffer: since we are in a class derived from EGrabber, we simply pass *this;
- information about the buffer: this is provided in data.
-
Retrieve the timestamp of the buffer, which is defined as the time at which the camera started to send data to the frame grabber.
-
As explained in the section about event identification, new buffer events are slightly different from the other kinds of events: they are standard (as per GenTL), and don't have an associated numid.
As a consequence, the NewBufferData structure passed to onNewBufferEvent doesn't have a numid field. It does, however, have a timestamp field indicating the time at which the driver was notified that data transfer to the buffer was complete. This event timestamp is inevitably greater than the buffer timestamp retrieved in step 2.
-
We reach the end of the block where the local variable buf has been created. It gets out of scope and is destroyed: the ScopedBuffer destructor is called. This causes the GenTL buffer contained in buf to be re-queued (given back) to the data stream of the grabber.
Example of program output:
event timestamp: 77185931621 us, buffer timestamp: 77185919807 us
event timestamp: 77185951618 us, buffer timestamp: 77185939809 us
event timestamp: 77185971625 us, buffer timestamp: 77185959810 us
event timestamp: 77185991611 us, buffer timestamp: 77185979812 us
event timestamp: 77186011605 us, buffer timestamp: 77185999808 us
event timestamp: 77186031622 us, buffer timestamp: 77186019809 us
event timestamp: 77186051614 us, buffer timestamp: 77186039810 us
event timestamp: 77186071611 us, buffer timestamp: 77186059811 us
event timestamp: 77186091602 us, buffer timestamp: 77186079812 us
event timestamp: 77186111607 us, buffer timestamp: 77186099814 us
Relevant files
include/EGrabber.h | Main header. Includes all the other headers except include/RGBConverter.h. Defines Euresys::EGrabber, Euresys::Buffer, Euresys::ScopedBuffer. |
include/EGrabberTypes.h | Defines data types related to Euresys::EGrabber. |
include/EGenTL.h | Defines Euresys::EGenTL. |
include/GenTL_v1_5.h | Standard GenTL header. Defines standard types, functions and constants. |
include/GenTL_v1_5_EuresysCustom.h | Defines Coaxlink-specific constants. |
include/RGBConverter.h | Defines Euresys::RGBConverter helper class. |