CVB Python Multi-Camera Acquisition

Hi,

I’m looking for some help with acquiring images using CVB Python from multiple cameras.

Hardware, 2 USB cameras:
Alvium 1800U-158C-CH-C

There is no hardware trigger, and I’d like to keep it this way. I accept this will lead to mis-matches between the camera acquisition, but would like to still get some initial results to assess this.

System software versions:
Windows 11
Common Vision Blox Image Manager v13.04.005
Python 3.9
CVB python 1.4

When setup in GenICam Browser, the cameras are achieving 250 FPS. I have saved those settings into user sets for each camera, and saved them as configured devices.

I’ve run the streaming simple, streaming parallel and streaming async examples succesfully with one camera plugged in. Problems occur when I add in the second camera.

The below is an attempt to use a MultiStreamHandler. I have also attempted similar with the async acquire example. In both cases, the software hangs and doesn’t acquire any images (but was working with the Mock drivers):

import datetime

import cvb
import time
import os


class MyStreamHandler(cvb.MultiStreamHandler):

    def __init__(self, stream_list):
        super().__init__(stream_list)
        self.rate_counter = cvb.RateCounter()

    # called from the aqusition thread
    def handle_async_stream(self, stream_list):
        super().handle_async_stream(stream_list)
        print("handle_async_stream")

    # called from the aqusition thread
    def handle_async_wait_result(self, wait_result_list):
        super().handle_async_wait_result(wait_result_list)
        self.rate_counter.step()
        image1, status1 = wait_result_list[0]
        image2, status2 = wait_result_list[1]
        print("1 image: " + image1.__class__.__name__ + " " + str(image1) + " | Status: " + str(status1) + " | Buffer Index: " + str(image1.buffer_index))
        print("2 image: " + image2.__class__.__name__ + " " + str(image2) + " | Status: " + str(status2) + " | Buffer Index: " + str(image2.buffer_index))

    # print messurement results
    def eval(self):
        print("Acquired with: " + str(self.rate_counter.rate) + " fps")

device1 = cvb.DeviceFactory.open(os.path.join(cvb.install_path(), "drivers", "GenICam.vin"), port=0)
device2 = cvb.DeviceFactory.open(os.path.join(cvb.install_path(), "drivers", "GenICam.vin"), port=1)

stream1 = device1.stream()
stream2 = device2.stream()

stream1.ring_buffer.change_count(200, 0)
stream2.ring_buffer.change_count(200, 0)

print("Setup done")

start_time = datetime.datetime.now()
with MyStreamHandler([stream1, stream2]) as handler:
    handler.run()
    time.sleep(5)
    handler.finish()
print("Took {} seconds".format(datetime.datetime.now() - start_time))

Is there anything that you can spot that I’m doing incorrectly?

Thanks,

Alex

Hi Alex,

Thank you for the report. I have just run the same code with you and confirmed the phenomenon that you reported.

I’m afraid, the issue does not seem to be trivial and may take some time for investigation and it would probably require us to prepare a fix.

I am afraid, would it be possible to work with the original MutiStreamHandler for a while?

Hi Kazunari,

Thanks for getting back to me.

From what I can gather in the docstrings, it looks like I’d need to overload the handle_async_wait_result method to use the MultiStreamHandler?

        """
        Asynchronously called for all acquired images.
        
        The default implementation does nothing.
        
         This method is called from a dedicated thread!
        
        Parameters
        ----------
        wait_result_list : cvb.AsyncWaitResult
            List with synchronous acquired WaitResults.
        """

Unless there’s another way of accessing the images from MultiStreamHandler?

Thanks,

Alex

Hi Alex,

Ah, that’s my bad; please forget the previous stupid suggestion that I made. Yes, you need to override it anyway. I guess I need to tell you that MultiStreamHandler keeps you blocked until the issue is fixed. I am sorry for the inconvenience.

Hi again,

Have you already seen the script that I proposed in another thread? That would be a possible approach that you can take as of today. As long as your application needs an external trigger, then MultiStreamHandler would not have any chance to be used because it does not offer you a chance to trigger the cameras even though you can acquire images at the same time.

Again, we find the reported issue must be resolved anyway but we would appreciate it if you could push your project further with the suggested approach. :slight_smile: Thank you!

Hi,

Thanks for the speedy responses and suggestions - really very much appreciated! I will try the other example on the USB cameras and let you know on that issue thread.

A separate question - in my case where we have a software application that needs to trigger the cameras, and then have them acquire for 2 seconds, would the MultiStreamHandler work for GigE cameras with PTP?

Thanks,

Alex

Hi Alex,

would the MultiStreamHandler work for GigE cameras with PTP?

If you have GEV cameras that support PTP then it could be the way to go; note that your devices support the GigE Vision Action Command (without PTP, less precise but works) then it’s also an option. However, you’re going to set up your application with U3V cameras, aren’t you?

Hi,

We have USB cameras currently yes, but ordered these mainly due to availability at the time.

We also ordered some GigE cameras (as long term we would prefer to use these), and these have now arrived. Just waiting for a few other bits of hardware to arrive, when they do I’ll try this method with those cameras and let you know the results.

Thanks,

Alex

# CVBpy Example Script
#
# 1. Open the CVMock.vin driver.
# 2. Asynchronously acquire images.
# 3. Measure the frame rate and print results.
#
# Note: can be extended to multiple cameras.


import os
import asyncio
import cvb

rate_counter = {"0": None, "1": None}


async def async_acquire(port):
    global rate_counter
    # with cvb.DeviceFactory.open(os.path.join(cvb.install_path(), "drivers", "CVMock.vin"), port=port) as device:
    with cvb.DeviceFactory.open(os.path.join(cvb.install_path(), "drivers", "GenICam.vin"), port=port) as device:
        stream = device.stream()
        stream.ring_buffer.change_count(200, 0)

        stream.start()

        rate_counter[str(port)] = cvb.RateCounter()

        for i in range(0, 100):
            result = await  stream.wait_async()
            image, status = result.value
            rate_counter[str(port)].step()
            if status == cvb.WaitStatus.Ok:
                print("Port: " + str(port) + ", Buffer index: " + str(image.buffer_index))

        stream.abort()


watch = cvb.StopWatch()

loop = asyncio.get_event_loop()
loop.run_until_complete(
    asyncio.gather(
        async_acquire(port=0),
        async_acquire(port=1),
    )
)
loop.close()

duration = watch.time_span

print("Acquired on port 0 with " + str(rate_counter["0"].rate) + " fps")
print("Acquired on port 1 with " + str(rate_counter["1"].rate) + " fps")
print("Overall measurement time: " + str(duration / 1000) + " seconds")

I had also tried the simple stream with 2 cameras (just for completeness).

Hi Alex,

Thank you for the information. Everything should be okay if you know the action command (with/without PTP) is not available on any U3V devices.

By the way, I have a few things that I would like to make sure of with you:

  1. Do you know there’s a file called GenICam.INI in STEMMER IMAGING\Common Vision Blox\Drivers? Could you make sure that the file contains the pieces of information about your target devices?
  2. Have you already tried to run the original script with CVB 14.0; I do not insist but it should be highly appreciated if you could check the reproducibility.

Thank you for your cooperation.

Here’s another question from another perspective. Concerning the maximum frame rate with an external trigger, it depends on the camera firmware design. The most certain and precise option is to ask the camera manufacturer.

However, you should be able to roughly calculate the slowest frame rate at least.

If the camera does not allow to accept a trigger signal while it’s busy then you would consume the amount of time that is equal to the exposure time plus the reciprocal of the maximum frame rate (i.e., a period consumed for a single image data transmission) at the internal trigger-driven mode.

If the camera allows accepting a trigger signal during the period then the frame rate should be faster than the above calculation but again, the valid triggering timing is defined by the camera firmware design.

If the expected frame rate is given by the manufacturer then we as STEMMER IMAGING should be able to support you to achieve the expected result.

Hi,

The INI file mentioned is not present on my PC, only the driver file.

To setup the device, I had a call with Aaron in the UK team. We went through the settings in the GenICam Browser, and saved them as a user set on both of the configured devices. This was set to be default on each. So both devices are configured in the browser, and were both achieving above 240 FPS, which is in keeping with the manufacturers specifications.

I’ve put together a basic multiprocess example, as I wanted to check that there wasn’t a thread lock happening:

import cvb
import os
import datetime

from multiprocessing import Process


def camera_acquire(port):
    print("Starting device: {}".format(port))
    device = cvb.DeviceFactory.open(os.path.join(cvb.install_path(), "drivers", "GenICam.vin"), port=port)
    stream = device.stream()
    # stream.ring_buffer.change_count(250, 0)
    stream.start()
    start_time = datetime.datetime.now()
    ok_count = 0
    for i in range(250):
        image, status = stream.wait()
        if status == cvb.WaitStatus.Ok:
            # print("1 image: " + image.__class__.__name__ + " " + str(image) + " | Status: " + str(status) + " | Buffer Index: " + str(image.buffer_index))
            ok_count += 1
    total_time = (datetime.datetime.now() - start_time).total_seconds()
    print("DEVICE: {}, IMAGES: {}, SECONDS: {}".format(port, ok_count, total_time))
    stream.stop()


if __name__ == "__main__":
    # camera_acquire(port=0)

    p_0 = Process(target=camera_acquire, args=(0,))
    p_0.start()
    p_0.join()

    # p_0 = Process(target=camera_acquire, args=(0,))
    # p_1 = Process(target=camera_acquire, args=(1,))
    # p_0.start()
    # p_1.start()
    # p_0.join()
    # p_1.join()

This will allow acquisition on the device at port 0 at around 240 FPS. The device on port 1 doesn’t run, either by itself or when tried with the device on port 0.

Is it possible that there is some kind of configuration, or maybe a driver issue?

Can I install CVB 14 alongside 13? Or is it best to uninstall 13 first? And could you share a link to 14 please? I couldn’t find it here:
https://www.commonvisionblox.com/en/download-cvb-windows-32-bit-and-64-bit/

Thanks,

Alex

Hi Alex,

Thank you for the reply.

We went through the settings in the GenICam Browser, and saved them as a user set on both of the configured devices.

Yes, that’s the procedure that I wanted to ask you. Thanks.

The device on port 1 doesn’t run, either by itself or when tried with the device on port 0.

Could you show me the exact code, please? It’s not necessary to show the whole program but I would like to know the lines that give you the expected result.

Can I install CVB 14 alongside 13?

Please forget about it because I do not want to mess this up; I just wanted to make sure you can reproduce the original issue on CVB 14.

Last but not least, I would recommend knowing the difference between Python threading and multiprocessing because you may find one works for a demo but it does not mean the concept works for your final application because the difference can be very critical; then you will need to go back and start developing it from scratch. (I recommend you check the script that I recently posted on another post.)

Hi,

Could you show me the exact code, please?

In my multiprocess example above, it hangs on the following line (17) for the device at port 1, but works for port 0.

image, status = stream.wait()

I see that all the Stemmer CVB python examples use threading, but given that we have tried all of those unsuccessfully I was just making sure that there wasn’t some sort of thread lock issue. If CVBpy doesn’t work with Python processes and needs threading that’s fine, we can disregard this attempt.

I’ll feedback on the code in the other issue separately.

Thanks,

Alex

Hi again,

Just to update on this, I have now been able to get the USB cameras streaming as desired.

We took delivery of 2 GigE cameras, which after some trial and error setup for PTP acquisition worked with the example below:

import datetime

import cvb
import time
import os

NUM_ACQUIRES = 0
NUM_FAILS = 0

ARRAY_1 = []
ARRAY_2 = []
TIMES_1 = []
TIMES_2 = []


class MyStreamHandler(cvb.MultiStreamHandler):

    def handle_async_wait_result(self, wait_result_list):
        print("handle_async_wait_result start")
        super().handle_async_wait_result(wait_result_list)
        image1, status1 = wait_result_list[0]
        image2, status2 = wait_result_list[1]
        global NUM_ACQUIRES
        global NUM_FAILS
        global ARRAY_1
        global TIMES_1
        global ARRAY_2
        global TIMES_2
        if status1 == cvb.WaitStatus.Ok and status2 == cvb.WaitStatus.Ok:
            NUM_ACQUIRES += 1
            ARRAY_1.append(image1)
            ARRAY_2.append(image2)
            TIMES_1.append(image1.raw_timestamp)
            TIMES_2.append(image2.raw_timestamp)
            print("====================================================")
            print("1 image: " + image1.__class__.__name__ + " " + str(image1) + " | Time: " + str(image1.raw_timestamp))
            print("2 image: " + image2.__class__.__name__ + " " + str(image2) + " | Time: " + str(image2.raw_timestamp))
        else:
            NUM_FAILS += 1


device1 = cvb.DeviceFactory.open(os.path.join(cvb.install_path(), "drivers", "GenICam.vin"), port=2)
device2 = cvb.DeviceFactory.open(os.path.join(cvb.install_path(), "drivers", "GenICam.vin"), port=3)

print("devices")

stream1 = device1.stream()
stream2 = device2.stream()

print("streams")

stream1.ring_buffer.change_count(200, 0)
stream2.ring_buffer.change_count(200, 0)

print("Setup done")

with MyStreamHandler([stream1, stream2]) as handler:
    print("pre-run")
    start_time = datetime.datetime.now()
    handler.run()
    time.sleep(1)
    handler.finish()
print("Took {} seconds".format(datetime.datetime.now() - start_time))

print(NUM_ACQUIRES)
print(NUM_FAILS)

I thought I’d try the USB cameras with the same changes made in the GenICam Browser, and they also worked.

The key parameters seemed to be setting Trigger Selector to Frame Start, and Trigger Mode to Off. We had Trigger Mode On from the examples that had been sent over previously.

Thanks,

Alex