TheImagingSource / IC-Imaging-Control-Samples

Windows Sample in C#, C++, Python, LabVIEW and Java
92 stars 52 forks source link

Implementing hardware trigger code using "bursts" #56

Closed zeesyk closed 1 year ago

zeesyk commented 1 year ago

I'm writing code to activate hardware trigger for my camera. Ideally, for every trigger pulse, I want the camera to take 10 images and save them into a folder. I've looked at the posted examples however I'm still struggling to implement this portion. I'm rather new to python. I know that using ic.IC_SnapImage() isn't effective for triggered images, so I want to use the bursts function ic.IC_SetPropertyValue(hGrabber,tis.T("Trigger"),tis.T("Burst Count"), 10).


"""
External trigger code
"""
import ctypes 
import tisgrabber as tis
import numpy as np

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

ic.IC_InitLibrary(0)

hGrabber = ic.IC_CreateGrabber()

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):

    def __init__(self,):
        self.width = 0
        self.height = 0
        self.BytesPerPixel = 0
        self.buffer_size = 0
        self.getNextImage = 0
        self.cvMat = None

def frameReadyCallback(hGrabber,pBuffer, framenumber, pData):
    """
    Parameters
    ----------
    hGrabber : This is the real pointer to the grabber object
    pBuffer :  Pointer to the first pixel's first byte
    framenumber : Number of frames since stream started.
    pData : Pointer to additional user data structure.

    """
    if pData.getNextImage == 1:
       pData.getNextImage == 2
       print("State rest")
       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.BytersPerPixel))
          pData.getNextImage = 0

##########################################################################
frameReadyCallbackfunc = ic.FRAMEREADYCALLBACK(frameReadyCallback)

userdata = CallbackUserdata() #create instance of CallbackUserdata class

ic.IC_SetCallbacks = [hGrabber,frameReadyCallbackfunc,userdata] 
#set call back function

#############################################################################

hGrabber = ic.IC_CreateGrabber()
frame = ic.IC_OpenVideoCaptureDevice(hGrabber,
                                     tis.T("DMK 33UX287"))#enter name of camera

index = 0 
if ic.IC_IsDevValid(hGrabber):
        ##Camera Properties
        ic.IC_SetFrameReadyCallback(hGrabber, frameReadyCallbackfunc, userdata)
        ic.IC_SetVideoFormat(hGrabber, tis.T("RGB24 (640x480)")) # resolution
        ic.IC_SetFrameRate(hGrabber, ctypes.c_float(500.0)) #framerate
        ic.IC_SetPropertyValue(hGrabber,tis.T("Exposure"), tis.T("Value"),
                               ctypes.c_float(1/400))
        #Exposure time t =1/1000 = 0.001 seconds or 1ms
        ic.IC_SetPropertySwitch(hGrabber, tis.T("Trigger"),
                                tis.T("Enable"), 1) # hardware trigger: 1
        ic.IC_SetPropertyValue(hGrabber,tis.T("Trigger"),
                               tis.T("Burst Count"), 10) 

        ic.IC_SetContinuousMode(hGrabber, 0)
        ic.IC_StartLive(hGrabber, 1)

        """
        Camera would begin taking 10 images and saving them?
        """

else: 
       print("Could not activate hardware trigger")

ic.IC_StopLive(hGrabber)
ic.IC_ReleaseGrabber(hGrabber)
TIS-Stefan commented 1 year ago

I suggest to create an array of 10 numpy objects and pass that to the callback. In the callback you convert the incoming buffer into the array elements and after you got 10 images, you can save the image safely to harddisc using OpenCV. How well this works depends on the camera's frame rate and the speed of your computer. The speed of your hard disc does not matter using that approach. You may look at https://github.com/TheImagingSource/IC-Imaging-Control-Samples/blob/master/Python/tisgrabber/samples/32-trigger-callback.py for a rudimentary example.

Stefan

zeesyk commented 1 year ago

Thank you for your quick response, Stefan! Instead, I did a for loop rather than creating an array. Maybe your suggestion might be better because when I run the program, nothing gets captured nor saved. Also, do you prefer that I don't use the "burst" function? I'm referring to ic.IC_SetPropertyValue(hGrabber,tis.T("Trigger"),tis.T("Burst Count"), 10). I'm not entirely sure how I would implement this either if I'm simply setting the camera to take 10 images rather actually telling it to take this burst.

-Ale

zeesyk commented 1 year ago

This is my current code, it runs with no errors but it still does not save any images.

"""
External trigger code
"""
import ctypes 
import tisgrabber as tis
import numpy as np
import cv2
import os

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

ic.IC_InitLibrary(0)

hGrabber = ic.IC_CreateGrabber()

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):

    def __init__(self,):
        self.width = 0
        self.height = 0
        self.BytesPerPixel = 0
        self.buffer_size = 0
        self.getNextImage = 0
        self.image = None

def frameReadyCallback(hGrabber,pBuffer, framenumber, pData):
    """
    Parameters
    ----------
    hGrabber : This is the real pointer to the grabber object
    pBuffer :  Pointer to the first pixel's first byte
    framenumber : Number of frames since stream started.
    pData : Pointer to additional user data structure.

    """
    imgs_array = np.zeros((pData.height.value,pData.width.value))

    for i in range(10): #take 10 images
       if pData.buffer_size > 0:
          print("data available")
          imagePtr = ic.Ic_GetImagePtr(hGrabber)
          imagedata = ctypes.cast(imagePtr, ctypes.POINTER(ctypes.c_ubyte *
                                                      pData.buffer_size))
          #Image data gets converted into CV Matrix
          pData.cvMat = np.ndarray(buffer = imagedata.contents,
                                   dtype = np.uint8,
                                   shape = (pData.height.value,
                                            pData.width.value,
                                            pData.BytersPerPixel)) #image
          image = pData.cvMat
          imgs_array += image
        #imgs_array.append(image)
       else:
           print("Acquisition failed")
    cv2.imwrite("./image{:04}",imgs_array)

##########################################################################
frameReadyCallbackfunc = ic.FRAMEREADYCALLBACK(frameReadyCallback)

userdata = CallbackUserdata() #create instance of CallbackUserdata class

#############################################################################

hGrabber = ic.IC_CreateGrabber()
frame = ic.IC_OpenVideoCaptureDevice(hGrabber,
                                     tis.T("DMK 33UX287"))#manually opening camera 

index = 0 
if ic.IC_IsDevValid(hGrabber):
        ##Camera Properties
        ic.IC_SetFrameReadyCallback(hGrabber, frameReadyCallbackfunc, userdata)
        ic.IC_SetVideoFormat(hGrabber, tis.T("RGB24 (640x480)")) # resolution
        ic.IC_SetFrameRate(hGrabber, ctypes.c_float(600.0)) #framerate
        ic.IC_SetPropertyValue(hGrabber,tis.T("Exposure"), tis.T("Value"),
                               ctypes.c_float(1/600))
        #Exposure time t =1/1000 = 0.001 seconds or 1ms
        ic.IC_SetPropertySwitch(hGrabber, tis.T("Trigger"),
                                tis.T("Enable"), 1) # hardware trigger: 1

        ic.IC_SetContinuousMode(hGrabber, 0)
        ic.IC_StartLive(hGrabber, 0)#0starts stream but does not show live video, 1 shows live video

        ic.IC_SetPropertySwitch(hGrabber, tis.T("Trigger"),
                                tis.T("Line1"),1)

        ic.IC_StopLive(hGrabber)
else: 
       print("Could not activate hardware trigger")

ic.IC_ReleaseGrabber(hGrabber)
TIS-Stefan commented 1 year ago

pData cvMat is not declared. It should be declared in the CallbackUserdata:

class CallbackUserdata(ctypes.Structure):
    def __init__(self,):
        self.width = 0
        self.height = 0
        self.BytesPerPixel = 0
        self.buffer_size = 0
        self.getNextImage = 0
        self.image = None
       self.cvMat = None

However, I am working on a sample for you, but I was extremely interrupted today. I am sorry.

Stefan

TIS-Stefan commented 1 year ago

Hello

this is my first approach:

'''
This sample demonstrates how to create a callback, which is automatically
called for each incoming frame.
'''
import ctypes
import time
import numpy as np
import cv2 as cv2
import tisgrabber as tis

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

class CallbackUserdata(ctypes.Structure):
    """ Example for user data passed to the callback function. """
    def __init__(self):
        self.count = 0
        self.camera = None      # Reference to a camera/grabber object
        self.images = []

def FrameCallback(hGrabber, pBuffer, framenumber, pData):
    """ This is an example callback function 
         The image is saved in test.jpg and the pData.Value1 is 
         incremented by one.

    :param: hGrabber: This is the real pointer to the grabber object. Do not use.
    :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
    """

    Width = ctypes.c_long()
    Height = ctypes.c_long()
    BitsPerPixel = ctypes.c_int()
    colorformat = ctypes.c_int()

    # Query the image description values
    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))

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

    pData.images.append(cvMat)
    pData.count = pData.count + 1
    print("Callback called: ", pData.count)

Userdata = CallbackUserdata()
# Create the function pointer.
Callbackfuncptr = ic.FRAMEREADYCALLBACK(FrameCallback)

ic.IC_InitLibrary(0)

hGrabber = ic.IC_CreateGrabber()
ic.IC_OpenVideoCaptureDevice(hGrabber, tis.T("DMK 33GR0521"))#enter name of camera

if(ic.IC_IsDevValid(hGrabber)):
    ic.IC_SetFrameReadyCallback(hGrabber, Callbackfuncptr, Userdata)
    ic.IC_SetContinuousMode(hGrabber, 0)

    ic.IC_SetVideoFormat(hGrabber, tis.T("RGB24 (640x480)")) # resolution
    ic.IC_SetFrameRate(hGrabber, ctypes.c_float(50.0)) #framerate
    ic.IC_SetPropertyValue(hGrabber,tis.T("Exposure"), tis.T("Value"),
                           ctypes.c_float(1/400))
    ic.IC_SetPropertySwitch(hGrabber, tis.T("Trigger"),
                           tis.T("Enable"), 1) 
    ic.IC_SetPropertyValue(hGrabber,tis.T("Trigger"),
                               tis.T("Burst Count"), 10) 

    ic.IC_StartLive(hGrabber, 1)

    while Userdata.count < 10:
        time.sleep(0.1)

    ic.IC_StopLive(hGrabber)
    i = 0
    for image in Userdata.images:
        i += 1
        filename= "img{}.jpg".format(i)
        cv2.imwrite(filename, image)

else:
    ic.IC_MsgBox(tis.T("No device opened"), tis.T("Callback"))

ic.IC_ReleaseGrabber(hGrabber)

But this far to slow. I can use a maximum frame rate of ~50 fps in order to handle all images. So no go for you. Give me a little time, I have a second approach, but I must test it,

Stefan

zeesyk commented 1 year ago

pData cvMat is not declared. It should be declared in the CallbackUserdata:

class CallbackUserdata(ctypes.Structure):
    def __init__(self,):
        self.width = 0
        self.height = 0
        self.BytesPerPixel = 0
        self.buffer_size = 0
        self.getNextImage = 0
        self.image = None
       self.cvMat = None

However, I am working on a sample for you, but I was extremely interrupted today. I am sorry.

Stefan

No problem, I appreciate you taking the time to help me!

TIS-Stefan commented 1 year ago

Here we go. This version works with higher frame rates, it should even work with your 500 fps. I tried 130, because the camera i used is too slow. triggerburstcount2.zip The sample contains a different tisgrabber.dll. This DLL implements membuffer handling In the line ic.IC_SetRingBufferSize(hGrabber,50) you define, how many buffers shall be allocated internally. Allocate only so much buffers, your computer has free RAM for, otherwise Windows starts swapping. In your case, even 12 buffers will be fine, because you want to capture only 10 images per trigger.

The trick is saving the pointers to the memory buffers only and do the numpy conversion stuff later.

The code is like the IC-Express code from https://www.theimagingsource.com/support/downloads-for-windows/end-user-software/icexpress/

Stefan

zeesyk commented 1 year ago

Thank you your input. I ran your code and I set the buffer size to 12 like you had suggested; however, no images were saved. I have my camera connected to a function generator as my trigger source so most likely my issue is related to that.

TIS-Stefan commented 1 year ago

Did you saw the live video window during capture? (I suppose, I passed a "1" to ic.IC_StartLive(hGrabber, 1)

Stefan

zeesyk commented 1 year ago

Yes! But the live window stopped responding almost immediately after running the program.

TIS-Stefan commented 1 year ago

This is expected, because the camera waits for the trigger. After you triggered the camera, you will see it running shortly and then the images are to be saved.

Stefan

zeesyk commented 1 year ago

Thank you for your help. I was able to get it working today.