What are you looking for?

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;
    }
}
  1. 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.
  2. Allocate 3 buffers. The grabber automatically determines the required buffer size.
  3. 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.

  4. 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).
  5. 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 *>.

  6. 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.

  7. 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).