When capturing images via Python, each image generates an error log entry in the CVB Logging GUI. But the image capture works perfectly for my use case. Now I just want to check if I can optimise my workflow to get rid of the error before shipping the code to the customers.
You were talking about “each” image.
However I cannot see you doing anything in a loop here.
If we assume your snippet to be a method that is run in a loop and returns the image, it is usually not best practice to load the driver each time you want to acquire a new image.
It would be better to keep the stream as a member and start it once and have it running all the time.
You could also go for get_snapshot() rather than wait() as this basically starts the stream, gets an image and then stops the stream again without killing it.
@Chris is totally right, although I would add:
The “open” implicitely searches up the common TL path for existing TLIs. On Linux, this is implemented somehow like glob does path lookup. The resulting pathlist contains .(current directory), .. (parent directory), and those two elements are also at least tried to be loaded as tl - without success. That is the message is all about. You can ignore it. I’ll look whether we can pipe in a fix for that in the near future.
The application is more or less embedded, as this can then run for days or even weeks, I wanted to be sure to always create a new camera instance instead of leaving the driver/resource open all the time. But I will give it a try. Thanks for the input.
I tried to refactor it according to your input. But in the loop it gets only one image and then it stales.
camara.py
class Camera:
def __init__(self):
self.camera_path: str = str(Path(cvb.install_path()) / "drivers" / "GenICam.vin")
self.device = cvb.DeviceFactory.open(self.camera_path, port=0)
self.stream = self.device.stream()
def take_image(self) -> np.array:
try:
image, status = self.stream.get_snapshot()
with image:
taken_image = cvb.as_array(image, copy=True)
return taken_image
except Exception as e:
raise e
where_it_gets_called.py
camera = Camera()
try:
for i in range(3):
t0 = time.time()
np_image = camera.take_image()
logging.debug(np_image)
logging.debug(f"Image {i} took: {(time.time()-t0):2f}")
except Exception as e:
raise e
What do I miss?
Even with the old stream.start() → stream.wait() → stream.abort() I can only get one image with my camera class.
You have the triggermode activated in your camera (unlikely).
You could check this by opening the camera in the CVBViewer or any of the CVB examples like the streaming_simple example in the ImageManager package:
%cvb%Tutorial\Image Manager\CVBPy
Your loop runs faster than the exposureTime of the camera.
I would also expect this to not be the case because get_snapshot internally falls back to wait() as well as far as I am aware. This means the acquisition should wait for the default timeout (8s I think) for a new image.
You could check the status to see if a timeout occured.
Yes I would also assume, that it is a problem with the ressource management then as we have already seen this behaviour in the linked thread.
Maybe you start from the simplest approach which would be to hold all variables the main routine and from there break it down to your class design.
Somewhere along the way you will eventually see the above described behaviour again.
Maybe it is as simple as putting the creation of the camera object into the try block… I havent tested this though.
with cvb.DeviceFactory.open(camera_path, port=0) as device
everytime I want to get an image inside my loop, but if I try to set the device as a member variable (e.g.: self.device = cvb.DeviceFactory.open(self.camera_path, port=0)) the same behaviour as without the triggered mode occur.
So I don`t understand what the triggered mode improve in my use case.
Yeah this is very likely due to the same ressource management problem.
Stick with your first approach for now (get_snapshot) and let us try getting rid of this problem first.
“Maybe it is as simple as putting the creation of the camera object into the try block… I havent tested this though.”
The software trigger approach is something to keep in mind for the future.
from pathlib import Path
import cvb
import numpy as np
class CameraTest:
def __init__(self):
self.camera_path: str = str(Path(cvb.install_path()) / "drivers" / "GenICam.vin")
def take_image(self):
with cvb.DeviceFactory.open(self.camera_path, port=0) as device:
stream = device.stream()
image, status = stream.get_snapshot()
print(f"The image status was: {status}")
with image:
taken_image = cvb.as_array(image, copy=True)
print(np.shape(taken_image))
if __name__ == "__main__":
try:
camera = CameraTest()
for i in range(3):
camera.take_image()
except Exception as e:
print(e)
and returns:
The image status was: 0
(2048, 2560, 3)
The image status was: 0
(2048, 2560, 3)
The image status was: 0
(2048, 2560, 3)
But when I try to remove the with block directly breaks. So here I only tried to get an member variable which holds the device, to avoid reloading the driver for each image.
from pathlib import Path
import cvb
import numpy as np
class CameraTest:
def __init__(self):
self.camera_path: str = str(Path(cvb.install_path()) / "drivers" / "GenICam.vin")
self.device = cvb.DeviceFactory.open(self.camera_path, port=0)
def take_image(self):
print(self.device)
print(self.device.connection_state)
print("Getting stream")
stream = self.device.stream()
print("Trying to get snapshot")
image, status = stream.get_snapshot()
print(f"The image status was: {status}")
with image:
taken_image = cvb.as_array(image, copy=True)
print(np.shape(taken_image))
if __name__ == "__main__":
try:
camera = CameraTest()
for i in range(3):
camera.take_image()
except Exception as e:
print(e)
This may be a hint:
It hangs on the second get_snapshot and will not recover. And when executing the script again, the first get_snapshot is already hanging.
On the console the error is:
<cvb.VinDevice object at 0x7f1b87b37e40>
1
Getting stream
Trying to get snapshot
Since the image status is non-zero I guess this explains why get_snapshot hangs until eternity.
Solution:
I have to remove and re connect the energy to the camera to get it to do anything again, but then it only gets one picture until it hangs again.
class CameraTest:
def __init__(self):
self.camera_path: str = str(Path(cvb.install_path()) / "drivers" / "GenICam.vin")
print("Init device")
self.device = cvb.DeviceFactory.open(self.camera_path, port=0)
print("Getting stream")
self.stream = self.device.stream()
def take_image(self):
print(self.device)
print(self.device.connection_state)
print("Getting stream")
print("Trying to get snapshot")
image, status = self.stream.get_snapshot()
print(f"The image status was: {status}")
with image:
taken_image = cvb.as_array(image, copy=True)
print(np.shape(taken_image))
Init device
Getting stream
<cvb.VinDevice object at 0x7f46c083be40>
1
Getting stream
Trying to get snapshot
The image status was: 0
(2048, 2560, 3)
<cvb.VinDevice object at 0x7f46c083be40>
1
Getting stream
Trying to get snapshot
Hi @sebwr !
From your description I’m pretty sure there is no need for a class, at least not for the device. Why don’t you just create a new device each time you start this (relatively) rare job? (see example below)
If you consider this, I also recommend to use our new stack. Then you would have to discover your device with the cvb.DeviceFactory.discover_from_root() function and filter out the access token to the desired device.
The simple switch to use our new recommended stack is to pass the cvb.AcquisitionStack.GenTL to the cvb.DeviceFactory.open() function.
The whole job would be the following:
import cvb
import numpy as np
from pathlib import Path
import cvb
from typing import Optional
import time
class CameraTest:
def __init__(self):
# use any unique substring which distinguishes you device from others inside the access token:
self.camera_identifier: str = "CVMock"
all_tokens = cvb.DeviceFactory.discover_from_root(cvb.DiscoverFlags.IgnoreVins)
filtered_token = [token for token in all_tokens if self.camera_identifier in token.access_token]
if not len(filtered_token):
raise RuntimeError("No suitable camera found")
self.token = filtered_token[0].access_token
def rarely_take_image(self) -> Optional[np.ndarray]:
# now open the first one from the filtered list:
with cvb.DeviceFactory.open(self.token, cvb.AcquisitionStack.GenTL) as device:
stream = device.stream(cvb.ImageStream)
stream.start()
image, status, nodemaps = stream.wait()
taken_image = None
if status == cvb.WaitStatus.Ok:
vinbuffer_nodemap = nodemaps["VinBuffer"]
timestamp = vinbuffer_nodemap.Timestamp
print("Timestamp: "+str(timestamp.value))
taken_image = cvb.as_array(image, copy=True)
stream.abort()
return taken_image
if __name__ == "__main__":
camera = CameraTest()
for i in range(3):
try:
image = camera.rarely_take_image()
if image is not None:
print("Acquistion #"+str(i)+" succeded")
pass # do what you want
# simulate long time
time.sleep(10)
except Exception as e:
print("Acquistion #"+str(i)+" failed with error: " +str(e) )
Please be aware that you adapt your “camera identifier” with something, that is unique for your device (here it is “CVMock”, but this can be any substring in the access token, like camera serial number, vendor ID or similar).
The expected output should be something like:
I had implemented it exactly this way when I wrote this thread, but @Chris pointed out that loading the driver often is not good practice. That was the only reason I tried the class approach.
Nevertheless, thanks for the snippet for the new approach. I think you should also put it somewhere where it can be found via Google. Because I started using CVB at the end of last year and only found examples using the old acquisition stack in this forum.
I will accept this answer for now and try the new acquisition stack the next time I am on site.
@Chris@s-woe Thank you very much for your time and for sharing your knowledge.