genicam / harvesters

Image Acquisition Library for GenICam-based Machine Vision System
Apache License 2.0
500 stars 86 forks source link

Impossible to get a valid buffer in CRAPPY camera class #450

Closed jeffwitz closed 3 months ago

jeffwitz commented 3 months ago

Describe the Issue I want to implement a harvester class for crappy For now, we have gstreamer, opencv, v4l2, ximea and gphoto2 cameras available, and I want to have compatible harvester one available.

I don't succeed in getting a non corrupted buffer on my class, while it works well outside for image to image capture.

I create this code where I embed a camera class. this is basic just to try to have a workflow that is ok.

Details

```python import platform import numpy as np from crappy.camera.meta_camera import Camera import time from typing import Optional,Tuple, List, Dict, Any from crappy._global import OptionalModule try : from harvesters.core import Harvester from harvesters.util.pfnc import mono_location_formats,rgb_formats from harvesters.util.pfnc import bgr_formats from harvesters.util.pfnc import rgba_formats, bgra_formats except (ModuleNotFoundError, ImportError): harverster_exception = OptionalModule( "Harvester", "To use Genicam cameras, please install the " "harvester module : python -m pip intstall harvesters" ) class GenicamCamera(Camera): def __init__(self) -> None: super().__init__() Camera.__init__(self) self.camera = None if platform.system() == 'Windows': # Windows self.cti_file_path = 'C:/win.cti' if platform.system() == 'Linux': # Linux self.cti_file_path = ('/opt/ImpactAcquire/lib/x86_64/' 'mvGenTLProducer.cti') self.num_image = 0 self.device = None self.h = None def open(self, device: Dict[str,Any] = None, cti_file_path: str = None, **kwargs: any) -> None: self.h = Harvester() if cti_file_path is not None: self.cti_file_path = cti_file_path self.h.add_file(self.cti_file_path) self.h.update() if self.device is None: self.camera = self.h.create() else: self.camera = self.h.create(self.device) expo_time = self.camera.remote_device.node_map.ExposureTime self.camera.remote_device.node_map.ExposureTime =2500 self.camera.start() def get_image(self) -> Tuple[Dict[str, Any], np.ndarray]: if not self.camera: raise RuntimeError("Camera not opened.") buffer = self.camera.fetch(timeout=0.5) if buffer: component = buffer.payload.components[0] width = component.width height = component.height num_comp=component.num_components_per_pixel img_data = np.frombuffer(component.data,np.uint8).reshape((int(height),int(width))) buffer.queue() metadata = { 't(s)': time.time(), 'ImageUniqueID': self.num_image, } self.num_image += 1 return metadata, img_data def close(self): if self.camera: self.camera.stop() self.camera.destroy() self.h.reset() else: print("Camera is not opened or already closed.") def apply_settings(self): if self.camera and 'ExposureTime' in self.settings: self.camera.remote_device.node_map.ExposureTime.value = ( self.ExposureTime) if __name__ == '__main__': import crappy cam1 = crappy.blocks.Camera( 'GenicamCamera', config = True, # Before the test starts, displays a configuration window # for configuring the camera display_images = True, # During the test, the acquired images are # displayed in a dedicated window save_images = False, # Here, we don't want the images to be recorded # Sticking to default for the other arguments freq=100 ) crappy.start() ```

To Reproduce Steps to reproduce the behavior: in your test venv

python -m pip install  numpy,  matplotlib, opencv-python, harvesters, crappy 

Sample Code The main is embedded in the previous class. It takes the first camera and try to display something on the camera configurator.

Expected Behavior a real image to appear

Screenshots image

Configuration

Actions You Have Taken Simple file without crappy that works:

Details

```python from harvesters.core import Harvester import numpy as np h = Harvester() # h.add_file('/opt/XIMEA/lib/ximea.gentl.cti') h.add_file('/opt/ImpactAcquire/lib/x86_64/mvGenTLProducer.cti') h.update() ia = h.create() ia.start() ia.remote_device.node_map.ExposureTime.value = 2500 print(f'Exposure time {ia.remote_device.node_map.ExposureTime.value}') buffer =ia.fetch(timeout = 0.5) if buffer: component = buffer.payload.components[0] width = component.width height = component.height num_comp=component.num_components_per_pixel img_data = np.frombuffer(component.data,np.uint8).reshape((int(height),int(width))) img_data1 = np.empty_like(img_data) img_data1[:] = img_data[:] buffer.queue() # Remet le buffer en queue pour réutilisation ia.stop() ia.destroy() h.reset() import matplotlib.pyplot as plt plt.imshow(img_data1,cmap='gray');plt.colorbar();plt.show() ```

jeffwitz commented 3 months ago

I have done a new example that helps to understand how to reproduce the issue and what I don't understand.

This code doesn't work :

Details

```python from harvesters.core import Harvester import numpy as np import cv2 import time h = Harvester() h.add_file('/opt/ImpactAcquire/lib/x86_64/mvGenTLProducer.cti') h.update() ia = h.create() ia.start() ia.remote_device.node_map.ExposureTime.value = 25000 while(True): with ia.fetch() as buffer: component = buffer.payload.components[0] width = component.width height = component.height num_comp=component.num_components_per_pixel img_data = np.frombuffer(component.data,np.uint8).reshape((int(height),int(width))) cv2.namedWindow('frame', cv2.WINDOW_NORMAL) cv2.imshow('frame',img_data) if cv2.waitKey(1) & 0xFF == ord('q'): ia.stop() ia.destroy() h.reset() cv2.destroyAllWindows() cap.release() break ```

while this one works :

Details

```python from harvesters.core import Harvester import numpy as np import cv2 import time h = Harvester() h.add_file('/opt/ImpactAcquire/lib/x86_64/mvGenTLProducer.cti') h.update() ia = h.create() ia.start() ia.remote_device.node_map.ExposureTime.value = 25000 while(True): with ia.fetch() as buffer: component = buffer.payload.components[0] width = component.width height = component.height num_comp=component.num_components_per_pixel img_data = np.frombuffer(component.data,np.uint8).reshape((int(height),int(width))) cv2.namedWindow('frame', cv2.WINDOW_NORMAL) cv2.imshow('frame',img_data) if cv2.waitKey(1) & 0xFF == ord('q'): ia.stop() ia.destroy() h.reset() cv2.destroyAllWindows() cap.release() break ```

the only difference are that the creation of the windows is within the scope of with ia.fetch() as buffer :

        cv2.namedWindow('frame', cv2.WINDOW_NORMAL)
        cv2.imshow('frame',img_data) 

What I don't undersrtand is the fact that with img_data = np.frombuffer(component.data,np.uint8).reshape((int(height),int(width))) I should have a copy so there should not have any issues with this.

jeffwitz commented 3 months ago

Error found, np.frombuffer does not copy, you have to add the copy methode to solve it.

sunavlis commented 3 months ago

Perfect, thanks for the update. I can confirm that en explicit copy of the buffer is required before it is dequeued (which is done implicitly when leaving the with ia.fetch() as buffer block in Python)