Code Monkey home page Code Monkey logo

Comments (13)

TIS-Stefan avatar TIS-Stefan commented on August 25, 2024

Hello

Currently I am changing the approach of using tisgrabber, so I changed the tisgrabber.py.

Please download the file(s)

Please see samples/40-qt-stereo.py, maybe it is, what you are asking for.
It shows, how to

  • use qt5 widgets to display live video in
  • select and configure two devices
  • use callbacks and create an opencv mat
  • restore devices from saved xml files
  • capture images from both cameras and save them to hard disc.

There are more samples. The main trick is making the image part of the user data struct used in the callback.

The images are handled in the main loop in this case in the GUI thread.

I hope this helps.

from ic-imaging-control-samples.

nexus1203 avatar nexus1203 commented on August 25, 2024

Thank you for the updated library. It was helpful, however I wonder what happen to Camera.open("camera unique name") function. Its really helpful to be able to open cameras using unique names in multi-camera setup.
I also noticed that the global variables are used in the stereo camera example. It would be really hard to manage such when using signal_slot mechanism in pyqt5 or pyqt6. For example, if the multiple cameras are used with hardware trigger and each camera worker has to run in background and send the acquired image through signal/slot to the main UI.
wouldn't it be much better to simply assign the camera to a class like this and send the acquired image to the main UI? however following example is not working, I suspect it has to ctypes but I cannot debug this since, I would not know how the data is handled in DLL.

'''background worker class for camera data acquisition'''
from PyQt5 import QtCore
from PyQt5.QtCore import QThread, pyqtSignal, QTimer, QObject
import numpy as np 
import sys, os

# for tis grabber library
import ctypes
import tisgrabber as tis
# initialize the module (dll)
# get the current directory absolute path
abs_path_to_directory = os.path.abspath(os.path.dirname(__file__))
# add the directory to the os PATH
os.environ['PATH'] = abs_path_to_directory + os.pathsep + os.environ['PATH']

ic = ctypes.cdll.LoadLibrary("tisgrabber_x64.dll")
tis.declareFunctions(ic)
ic.IC_InitLibrary(0)



class WorkerSignals(QObject):
    '''
    Defines the signals available from a running worker thread.

    Supported signals are:

    finished
        No data
    
    error
        `tuple` (exctype, value, traceback.format_exc() )
    
    result
        `object` data returned from processing, anything    
    
    progress
        `int` to show the progress
    
    info
        `str` to show the messages or update about the procress

    '''
    finished = pyqtSignal()
    error = pyqtSignal(tuple)
    result = pyqtSignal(object)
    progress = pyqtSignal(int)
    info = pyqtSignal(str)
    alive = pyqtSignal(bool)
    connected = pyqtSignal(str)

def CreateUserData(ud, camera):
    ''' Create the user data for callback for the passed camera
    :param ud User data to create
    :param camera The camera connected to the user data
    '''
    ud.width = ctypes.c_long()
    ud.height = ctypes.c_long()
    iBitsPerPixel = ctypes.c_int()
    colorformat = ctypes.c_int()

    # Query the values
    ic.IC_GetImageDescription( camera, ud.width, ud.height, iBitsPerPixel, colorformat )

    ud.BytesPerPixel = int( iBitsPerPixel.value / 8.0 )
    ud.buffer_size = ud.width.value * ud.height.value * ud.BytesPerPixel 
    ud.getNextImage = 0

class CallbackUserdata(ctypes.Structure):
    """ Example for user data passed to the callback function. """
    def __init__(self):
        self.width = 0
        self.height = 0
        self.BytesPerPixel = 0
        self.buffer_size = 0
        self.oldbrightness = 0
        self.getNextImage = 0
        self.cvMat = None

def Callback(hGrabber, pBuffer, framenumber, pData):
    """ 

    :param: hGrabber: This is the real pointer to the grabber object.
    :param: pBuffer : Pointer to the first pixel's first byte
    :param: framenumber : Number of the frame since the stream started
    :param: pData : Pointer to additional user data structure
    """
    if pData.getNextImage == 1:
        pData.getNextImage = 2
        print("State reset")
        if pData.buffer_size > 0:
            print("data available")
            image = ctypes.cast(pBuffer, ctypes.POINTER(ctypes.c_ubyte * pData.buffer_size))

            pData.cvMat = np.ndarray(buffer = image.contents,
                            dtype = np.uint8,
                            shape = (pData.height.value,
                                    pData.width.value,
                                    pData.BytesPerPixel))
        pData.getNextImage = 0
        

class CameraWorker(QThread):
    signals = WorkerSignals()

    def __init__(self,parent=None,camera_id='tis',hardware_trigger = False, exposure = 1/30, fps = 30.0, video_format ="Y800 (1280x720)", gain= 1):
        super(CameraWorker, self).__init__(parent)
        
        self.ok = False
        self.data = None
        
        self.threadactive = True
        self.camera_id = camera_id
        
        self.click_image= False
        self.name = 'Camera{}'.format(camera_id)
        print('from thread',self.camera_id)
        
        # initialize the camera as the worker class attribute
        self.Camera =  ic.IC_CreateGrabber()
        
        
        ic.IC_OpenVideoCaptureDevice(self.Camera, camera_id.encode("utf-8")) # it opens the camera with camera model but the unique id (with serial number would have been more stable for multi-camera setup)

        if ic.IC_IsDevValid(self.Camera) == 1:
            # the code passes the DevValid check point no problem until here.

            print('camera is valid')
            ic.IC_SetFrameRate(self.Camera, ctypes.c_float(fps))
            ic.IC_SetVideoFormat(self.Camera, video_format.encode("utf-8"))
            
            
            ic.IC_SetPropertySwitch(self.Camera, "Exposure".encode("utf-8"), "Auto".encode("utf-8"), 0)
            ic.IC_SetPropertyAbsoluteValue(self.Camera, "Exposure".encode("utf-8"), "Value".encode("utf-8"), ctypes.c_float(exposure))
            
            ic.IC_SetPropertySwitch(self.Camera, "Gain".encode("utf-8"), "Auto".encode("utf-8"), 0)
            ic.IC_SetPropertyAbsoluteValue(self.Camera, "Gain".encode("utf-8"), "Value".encode("utf-8"), ctypes.c_float(gain))
            
            if hardware_trigger:
                ic.IC_SetPropertySwitch(self.Camera, "Trigger".encode("utf-8"), "Enable".encode("utf-8"), 1) #switch to harware trigger mode
            else:
                ic.IC_SetPropertySwitch(self.Camera, "Trigger".encode("utf-8"), "Enable".encode("utf-8"), 0)   
            
            # passes the set properties 
            ic.IC_SetContinuousMode(self.Camera, 1)
            ic.IC_StartLive( self.Camera, 0)
            # camera starts 
            # ########
            # this part passes as well
            Callbackfunc =  ic.FRAMEREADYCALLBACK(Callback)
            self.CameraState = CallbackUserdata()  
            ic.IC_SetFrameReadyCallback(self.Camera, Callbackfunc, self.CameraState)
            CreateUserData(self.CameraState, self.Camera)
           
       
    @QtCore.pyqtSlot()
    def run(self):
               
        """start the camera acquisition"""
        i = 0
        fps = []
        self.CameraState.getNextImage = 1
        while self.threadactive:
            
            if self.CameraState.getNextImage == 0:
                # ########
                # this should return (emit) the image data to the main UI but nothing happens.

                print('State Changed')
                image = self.CameraState.cvMat
                self.data = {"id":self.camera_id, "image":image}
                self.signals.result.emit(self.data)
                self.CameraState.getNextImage = 1
            
            QThread.msleep(1)
            
            
        ic.IC_StopLive(self.Camera)
        ic.IC_ReleaseGrabber(self.Camera)
        self.signals.finished.emit()
        # self.stop()
        self.signals.alive.emit(False)
        
    
        
    def stop(self):
        self.threadactive = False
        QThread.msleep(100)
        self.quit()
        self.wait()
        self.exit()

from ic-imaging-control-samples.

TIS-Stefan avatar TIS-Stefan commented on August 25, 2024

The function is IC_OpenDevByUniqueName() please see the updated tisgrabber.chm in the tisgrabber directory of this reposity. I uploaded it yesterday.

wouldn't it be much better to simply assign the camera to a class like this and send the acquired image to the main UI?
Yes. The tisgrabber.dll C wrapper is not object oriented, therefore, I did not implement, rather removed, a camera class. The problem is, if I change the tisgrabber.dll, I also have to change the Python classes. This is a little bit too much work overload for me.
But you can implement that class.

example is not working

What does that mean in detail? Which error message do you receive?
I am sure, my stereo sample works, so you may missed something?

Stefan

from ic-imaging-control-samples.

nexus1203 avatar nexus1203 commented on August 25, 2024

example is not working
I am working on a multi-camera setup (4 cameras DFM 42BUC03-ML). I am trying to run the four CameraWorker class in the background thread and get the data out of the pyqt5 signal/slot method (see the "def run" method). Because the default structure of I have to assign the camera and CallbackUserdata to an object in CameraWorker class like following:

            self.CameraState = CallbackUserdata()  
            ic.IC_SetFrameReadyCallback(self.Camera, Callbackfunc, self.CameraState)
            CreateUserData(self.CameraState, self.Camera)

After calling the worker class in main thread, the init method works alright without any errors. but calling the "run" method doesn't return the cvMat object as it should in the example. The stereo example works, I believe because global variables are used and no "class" is used. In a more specialized setting, I want to be able to control each camera and their functions individually and not rely on global variables. In my code, no errors occur but also camera state never updates :

    @QtCore.pyqtSlot()
    def run(self):
               
        """start the camera acquisition"""
        i = 0
        fps = []
        self.CameraState.getNextImage = 1
        
        while self.threadactive:
            i+=1
            print("while loop count {}".format(i)) # this counter works and update the value of i 
            if self.CameraState.getNextImage == 0:
                # the self.CameraState.getNextImage  does not update and stays same
                # CallBackUserData assigned to the callback never updates and doesn't shows any errors.
                # 
                # this should return (emit) the image data to the main UI but nothing happens.
                print('State Changed')
                image = self.CameraState.cvMat
                self.data = {"id":self.camera_id, "image":image}
                self.signals.result.emit(self.data)
                self.CameraState.getNextImage = 1
            
            QThread.msleep(1)

from ic-imaging-control-samples.

TIS-Stefan avatar TIS-Stefan commented on August 25, 2024

The stereo sample works, because the grabber objects are in the same thread and there is only one thread used.
In an own multithready application with many cameras I called Initlibrary() in the thread of a camera. It looks like this:

def camerathread( data ):
    '''
    For each camera a thread is created. The in data.configfile saved camera.xml file
    is used to open the camera.
    The main loop runs for ever.
    If a device lost happens, the main loop tries to reopen the camera.

    Image saving to the "files" subdir is done here on demand.
    
    The snapped image data is compressed to jpeg in memory and saved in the passed "data.lockedimage" (limg) object.
    '''
    global ic
    
    ic.IC_InitLibrary(0)
    
    while data.running == True :
        hGrabber = ic.IC_CreateGrabber()
        ic.IC_LoadDeviceStateFromFile(hGrabber,tis.T(data.configfile))

        if( ic.IC_IsDevValid(hGrabber)): 
            # Add callbacks for framerate (not used) and device lost
            cb = ic.FRAMEREADYCALLBACK(Callback)
            devlostfunc = ic.DEVICELOSTCALLBACK(deviceLostCallback)
            ic.IC_SetCallbacks(hGrabber,cb,None,devlostfunc, data )

            ic.IC_StartLive(hGrabber,0)

(Ignore parts of the comment, the code is from a webserver showing many cameras in a browser)
( I am not sure, whether that "gobal ic" is necessary. I am not a Python expert, I am a beginner rather)

This works fine so far.

I wonder, why you run the cameras in own threads. Each grabber (camera) uses its own thread, which calls the callback functions within that thread. Thus it is not necessary to create a new thread for the camera thread that already exists.
Within the callback you can call self.signals.result.emit(self.data). I made that at https://github.com/TheImagingSource/IC-Imaging-Control-Samples/blob/master/Python/Python%20NET/qt5-simple.py

You could create a list containing the grabber objects and additional data in your main thread, open and configure the cameras, set the callback function (which is implemented once only and used by all cameras) and send the signal with the additional data and the cv::Mat (which is part of the additional data). You do not need an extra loop for snapping images in this case. Shall we proceed like this? Please let me know. If yes, I create a sample for you based on your code above.

If you want to stay with your worker threas, I would like to suggest, that I rewrite your code to changed tisgrabber.py
I will use the IC_OpenDevByUniqueName() function instead of IC_LoadDeviceStateFromFile(), So you do not need to create a device state file (IC_SaveDeviceStatetoFile() ).

Please let me know, how to proceed.

Stefan

from ic-imaging-control-samples.

nexus1203 avatar nexus1203 commented on August 25, 2024

Within the callback you can call self.signals.result.emit(self.data).

Yes this what I am trying to figure out how to do that.

You could create a list containing the grabber objects and additional data in your main thread, open and configure the cameras, set the callback function (which is implemented once only and used by all cameras) and send the signal with the additional data and the cv::Mat (which is part of the additional data). You do not need an extra loop for snapping images in this case. Shall we proceed like this? Please let me know. If yes, I create a sample for you based on your code above.

I think this method will be ok as long as the pyqtsignal can be emitted from callback and received in the main UI thread. And I can be able to differentiate which camera the images belong to (probably using unique names?). Thank you very much for your help.

from ic-imaging-control-samples.

TIS-Stefan avatar TIS-Stefan commented on August 25, 2024

Hello

Well, I will start now. Base will be the stereo sample (I like copy&paste).

can be able to differentiate which camera the images belong to (probably using unique names?). T

That can all be written in the user data. I would not suggest to use different signals for each camera, because without that, you can use as many cameras as you want without changing the code.

Please give me some time. (I will handle my easier support cases first, so I am more concentrated.)

Stefan

from ic-imaging-control-samples.

TIS-Stefan avatar TIS-Stefan commented on August 25, 2024

Hello

Here we go: https://github.com/TheImagingSource/IC-Imaging-Control-Samples/tree/master/Python/tisgrabber/samples
it is 41-qt-triggering.py. The name suggests, there is something with trigger, but I did not implement enabling of trigger (you can do in the properties....)

The sample does:

  • count the number of connected cameras
  • create for each camera a select and properties menu entry
  • create a video window and a brightness progress bar
  • implements a camera class for
    • select device
    • device properties
    • start, stop
    • save device state to file
    • restore from file
    • create callback data
    • set callback
    • create user data
    • still missing: device lost event handler
  • callback class
    • create the cv mat
    • send a signal to main ui thread on new image
  • image processing in main UI thread, show result in progressbar
  • restores last used devices at program start.

I suppose, the sample does, what you want to do.

There is a warning regarding IC_SetHwnd and internal conversion from void* to int. Thus, the sample works with Python 3.8, but not with 3.10. I think, the second parameter must be casted implicitly to int.

I must admit, that signaling was tricky. I think the namespace thing was necessary.

Stefan

from ic-imaging-control-samples.

nexus1203 avatar nexus1203 commented on August 25, 2024

Thank you for the sample. I tried to the run the sample, however, I am getting an error from the frameReadyCallback.

def frameReadyCallback(hGrabber, pBuffer, framenumber, pData):
    """ This is an example callback function for image processing with 
        opencv. The image data in pBuffer is converted into a cv Matrix
        and with cv.mean() the average brightness of the image is
        measuered.
    :param: hGrabber: This is the real pointer to the grabber object.
    :param: pBuffer : Pointer to the first pixel's first byte
    :param: framenumber : Number of the frame since the stream started
    :param: pData : Pointer to additional user data structure
    """
    # print("camera {}". format(pData.index))
    Width = ctypes.c_long()
    Height = ctypes.c_long()
    BitsPerPixel = ctypes.c_int()
    colorformat = ctypes.c_int()

    # Query the image description values
    print(hGrabber) # --> This returns an integer value
    ic.IC_GetImageDescription(hGrabber, Width, Height, BitsPerPixel, colorformat)

    # Calculate the buffer size
    bpp = int(BitsPerPixel.value/8.0)
    buffer_size = Width.value * Height.value * bpp

    if buffer_size > 0:
        image = ctypes.cast(pBuffer, 
                            ctypes.POINTER(
                                ctypes.c_ubyte * buffer_size))

        pData.cvMat = np.ndarray(buffer=image.contents,
                                dtype=np.uint8,
                                shape=(Height.value,
                                        Width.value,
                                        bpp))

        pData.signals.newimage.emit(pData)

The error output is as follow:

Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 232, in 'calling callback function'
  File "///", line 54, in frameReadyCallback
    ic.IC_GetImageDescription(hGrabber, Width, Height, BitsPerPixel, colorformat)
ctypes.ArgumentError: argument 1: <class 'TypeError'>: expected LP_HGRABBER instance instead of int

from ic-imaging-control-samples.

TIS-Stefan avatar TIS-Stefan commented on August 25, 2024

Hello

You are right. Its in line 95 of tisgrabber.py. It must be
ic.FRAMEREADYCALLBACK = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.POINTER(HGRABBER), ctypes.POINTER( ctypes.c_ubyte), ctypes.c_ulong, ctypes.py_object )

I forgot to push this file. (There are some more differences, since it also support device lost events.)
The IC_SetHWnd() argtypes issue for Python 3.10 is not solved yet.

The tisgrabber.py is updated in the repository now.

Stefan

from ic-imaging-control-samples.

nexus1203 avatar nexus1203 commented on August 25, 2024

thank you very much, its working without any errors.

from ic-imaging-control-samples.

TIS-Stefan avatar TIS-Stefan commented on August 25, 2024

And does the sample do, what you have in mind?

from ic-imaging-control-samples.

nexus1203 avatar nexus1203 commented on August 25, 2024

And does the sample do, what you have in mind?

Not out of the box but I have modified it to work with my user inference. And new callback function returns the images in both continuous and hardware trigger mode. Which helps a lot. Thank you very much for the support.

from ic-imaging-control-samples.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.