Handling EVENT_NEW_BUFFER

You have learned how to handle events in the previous chapter. In this chapter, we demonstrate a more practical example: How about to learn how we can interact with EVENT_NEW_BUFFER event?

The following class diagram shows the relationship between relevant classes. Note that relationship with the parent object classes of DataStream class were intentionally omitted to save space in the diagram. The image is stretched following the browser size, or you could see it in the original size opening the image in another tab/window.

The event data of EVENT_NEW_BUFFER can be accessed through EventManagerNewBuffer class object. So let’s walk through the following steps to see how we can handle event data of EVENT_NEW_BUFFER.

The following import statements are required for this tutorial.

from genicam.gentl import EventManagerNewBuffer, GenTLProducer, BufferToken, Port
from genicam.gentl import TimeoutException
from genicam.gentl import DEVICE_ACCESS_FLAGS_LIST, EVENT_TYPE_LIST, \
    ACQ_START_FLAGS_LIST, ACQ_STOP_FLAGS_LIST, ACQ_QUEUE_TYPE_LIST
from genicam.genapi import NodeMap, AbstractPort, EAccessMode

First, instantiate a NodeMap object of the target remote device like we did in a previous chapter.

concrete_port = ConcretePort(device.remote_port)
url_index = 0
url = device.remote_port.url_info_list[url_index].url
location, others = url.split(':', 1)

# Check if the URL contains a schema version.
if '?' in others:
    others, schema = others.split('?')
    file_name, address, size = others.split(';')
else:
    file_name, address, size = others.split(';')

# Download the device description file content from the
# remote device.
address = int(address, 16)
size = int(size, 16)
file_content = device.remote_port.read(address, size)

# Instantiate a Node Map object and load the device description.
remote_device = NodeMap()
_, extension = os.path.splitext(file_name)
if '.zip' == extension.lower():
    with open(file_name, 'wb') as zip_file:
        zip_file.write(file_content[1])
        remote_device.load_xml_from_zip_file(file_name)
        zip_file.close()
else:
    remote_device.load_xml_from_string(file_content[1])

remote_device.connect(concrete_port, device.remote_port.name)

Note that we used the following class deriving from AbstractPort class of genapi module.

class ConcretePort(AbstractPort):
    def __init__(self, gentl_port=None):
        super().__init__()
        self._assign_port_impl(gentl_port)

    def is_open(self):
        return False if self._gentl_port is None else True

    def write(self, address, value):
        self._gentl_port.write(address, value)

    def read(self, address, length):
        buffer = self._gentl_port.read(address, length)
        return buffer[1]

    def open(self, gentl_port):
        self._assign_port_impl(gentl_port)

    def close(self):
        self._gentl_port = None

    def get_access_mode(self):
        return EAccessMode.RW if self.is_open else EAccessMode.NA

    def _assign_port_impl(self, obj):
        # Check if the object is a GenTL Port module object.
        if isinstance(obj, Port):
            self._gentl_port = obj
        else:
            raise TypeError('Supplied object is not a GenTL Port object.')

In general having instantiated a NodeMap object for your remote device you would set up the device through the object so that it fits to your application. After setting up the device, in other words if the device is ready for working on your image acquisition task, accessing data_stream_ids property of the Device class, instantiate a DataStream object calling open_data_stream() method. In the following example, it opens the first DataStream object.

data_stream = device.create_data_stream()
data_stream.open(device.data_stream_ids[0])

Next, determine the minimum number of buffers to be prepared. You must prepare sufficient numbers of buffers anyway. Anyway!

num_buffers = data_stream.buffer_announce_min

So, let’s determine the buffer size. It must not be less than the required at least. If the DataStream object defines payload size, you can determine the buffer size using its payload_size property. If the DataStream object defines the payload size, its defines_payload_size property returns True. If it doesn’t, the remote device would provide GenICam PayloadSize node to let you know the required buffer size.

if data_stream.defines_payload_size():
    buffer_size = data_stream.payload_size
else:
    buffer_size = remote_device.PayloadSize.value

After that, instantiate a BufferToken object. It’s used to announce a raw buffer and its tag data on the DataStream object. The constructor of BufferToken class takes bytes object as a raw buffer where the transmitter device delivers images and an arbitrary object object to tag it on the raw buffer; maybe it would be an int object if we go on a simple case.

Instantiating a BufferToken object, announce the raw buffer as the place where images are delivered. To announce a raw buffer, you call announce_buffer() method of the DataStream object. announce_buffer() method takes a BufferToken object as its parameter and the method returns a Buffer object as its return. After announcing the raw buffer, you queue the Buffer object to the acquisition engine calling queue_buffer() method to get prepared for image acquisition.

raw_buffers = []  # is a list of raw buffers.
set_of_context = []  # is a list of user data.
buffer_tokens = []  # is a list of Buffer Token objects.
announced_buffers = []  # is a list of announced Buffer objects.
for i in range(num_buffers):
    # Instantiate a Buffer Token object supplying a raw buffer and
    # its user data as a tag.
    raw_buffers.append(bytes(buffer_size))
    set_of_context.append(i)
    buffer_tokens.append(
        BufferToken(
            raw_buffers[i], set_of_context[i]
        )
    )
    # Then announce the raw buffer (it's wrapped by the Buffer Token
    # object. You'll get an announced Buffer class object.
    announced_buffers.append(
        data_stream.announce_buffer(buffer_tokens[i])
    )
    # Finally, queue the announced Buffer object to get prepared
    # for image acquisition.
    data_stream.queue_buffer(announced_buffers[i])

Then register an event calling register_event() method of the DataStream object and get an EventToken object that the method returns. In this example, we register EVENT_NEW_BUFFER event here. Then instantiate an EventManagerNewBuffer object passing the returned EventToken object. You have nothing to interact with EventToken object. Everything will be fine if you just pass it to the constructor of EventManagerNewBuffer class to have a way to access the event data later.

event_token = data_stream.register_event(EVENT_TYPE_LIST.EVENT_NEW_BUFFER)
event_manager = EventManagerNewBuffer(event_token)

Now we can start image acquisition and acquire 10 images.

num_images = 10
data_stream.start_acquisition(
    ACQ_START_FLAGS_LIST.ACQ_START_FLAGS_DEFAULT,
    num_images
)
remote_device.AcquisitionStart.execute()

If you want to keep acquisition until you call stop_acquisition() method of DataStream object, just omit passing the second parameter.

data_stream.start_acquisition(
    ACQ_START_FLAGS_LIST.ACQ_START_FLAGS_DEFAULT
)

Here we start acquiring images and you would manipulate them. You can access the delivered event data over buffer property of the EventManagerNewBuffer object. The buffer property returns a Buffer object then you can read the newly delivered buffer content through raw_buffer property and its user data through user_data property of the Buffer object. In addition, this is just for your information but note that the object that is returned from raw_buffer property and the object that is returned from user_data are identical.

i = 0
while i < num_images:
    try:
        # We have one or more events to process.
        # Update the event data.
        event_manager.update_event_data(3000)
    except TimeoutException as e:
        # Nothing happened during the timeout period.
        print(e)
    else:
        # You would manipulate the buffer content having
        # been supported by the tagged user data.
        print(
            'Buffer #{} has delivered image #{}. ' \
            'The buffer size is {} Bytes.'.format(
                event_manager.buffer.context,
                i,
                len(event_manager.buffer.raw_buffer)
            )
        )

        # Queue the buffer again for the following image
        # acquisition.
        data_stream.queue_buffer(event_manager.buffer)

        # Increments the index.
        i += 1

        # It's not necessary at all but let me demonstrate that
        # the raw buffer returned from Buffer.raw_buffer property and
        # the user data returned from Buffer.user_data property are
        # identical to the ones you instantiated in the beginning.
        assert event_manager.buffer.raw_buffer is \
               raw_buffers[event_manager.buffer.context]
        assert event_manager.buffer.context is \
               set_of_context[event_manager.buffer.context]

        # Note that the raw buffer is identical to the bytes object
        # that you instantiated earlier.

Okay, we have finished to acquire the required number of images. Now we tear it down. First, stop acquisition and flush the event queue.

# Stop image acquisition. Note that TLSimu cannot announce or revoke
# during acquisition, stop before revoking.
data_stream.stop_acquisition(
    ACQ_STOP_FLAGS_LIST.ACQ_STOP_FLAGS_KILL
)
data_stream.flush_buffer_queue(
    ACQ_QUEUE_TYPE_LIST.ACQ_QUEUE_ALL_DISCARD
)

Here’s another tear down. Stop acquisition and stop monitoring EVENT_NEW_BUFFER event. Then revoke the announced raw buffers calling revoke_buffer() method.

# Unregister the registered event.
event_manager.unregister_event()

# Revoke the buffers.
# Stop image acquisition on the transmitter side.
remote_device.AcquisitionStop.execute()

for i in range(num_buffers):
    # You may have something to do with the revoked user data.
    buffer_token = data_stream.revoke_buffer(announced_buffers[i])
    print(
        'Buffer #{0} has been revoked.'.format(buffer_token.context)
    )

That’s all. We hope you have got feeling of handling events with the GenTL-Python Binding.