TheImagingSource / IC-Imaging-Control-Samples

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

Taking images after external trigger for a fixed time #62

Closed ODMRklaus closed 1 year ago

ODMRklaus commented 1 year ago

Hey, I have a question regarding an external trigger and the subsequent image capture. I am trying to activate image capture for a certain time via an external trigger. The trigger seems to work so far but I use time.sleep to set the time although there is duration as a setting. For example, after the external trigger I try to capture images for 2 seconds with maximum framerate (15fps) and save them in a list. This should correspond to 30 images. My trigger repeats every 2.4 seconds and my code should only warn the first received trigger as initiating signal. My code doesn't really seem to work as it seems to repeat every 6 frames.

import ctypes
import numpy as np
import tisgrabber as tis
import cv2 as cv
import pickle
import time
ic = ctypes.cdll.LoadLibrary("./tisgrabber_x64.dll")
tis.declareFunctions(ic)

#The list where my images should be appended to
img =[]

class CallbackUserdata(ctypes.Structure):
    """ Example for user data passed to the callback function. """
    def __init__(self):
        self.Value1 = 1

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
    """
    #controlling how many images are received
    print(framenumber)

    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))
        #append every image as an array which is received
        img.append(cvMat)

Userdata = CallbackUserdata()

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

ic.IC_InitLibrary(0)
#changing the setup activating the trigger
hGrabber = ic.IC_ShowDeviceSelectionDialog(None)

#ic.IC_SetPropertyAbsoluteValue(hGrabber, "Duration".encode("utf-8"), "Value".encode("utf-8"), ctypes.c_float(1.0))

if(ic.IC_IsDevValid(hGrabber)):

    ic.IC_SetFrameReadyCallback(hGrabber, Callbackfuncptr, Userdata)
    ic.IC_SetContinuousMode(hGrabber, 0)
    #enable the trigger after the first trigger signal is received
    ic.IC_SetPropertySwitch(hGrabber, tis.T("Trigger"), tis.T("Enable"), 0)

    ic.IC_StartLive(hGrabber, 1)
    #two seconds of data aquisition , araound 30 images
    time.sleep(2)

    ic.IC_StopLive(hGrabber)
#looking for the repetion in my frames iamge 0 and 5 are the same
img_dif = img [0]-img[5]
cv.imshow('0',img[0])
cv.imshow("5",img[5])
cv.imshow("dif", img_dif)
cv.waitKey(0) & 0xFF
cv.destroyWindow('i')

#write python file to save the images
with open('img.txt', 'wb') as temp:
    pickle.dump(img, temp)

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

Hello

The trigger is made for one image per trigger. But if you have have a frame rate of 15 fps and you want to have 2 seconds recording, then you can set the Trigger Burst Count property to 30. Doing so, one trigger will acquire 30 frames. You will do

ic.IC_SetPropertySwitch(hGrabber, tis.T("Trigger"), tis.T("Burst Count"), 30)
ic.IC_SetPropertySwitch(hGrabber, tis.T("Trigger"), tis.T("Enable"), 1)
ic.IC_StartLive(hGrabber, 1)
time.sleep(2.3)

(I hope, I got the "Burst Count" correctly. You can check with IC Capture, whether this works for your camera.) If your camera does not support Trigger Burst Count, you can do that with GP In instead. That means, you do not use the trigger mode at all. You start the stream and poll the GP In. If it is "High", you pass a value to th frame callback. If this value is true, you copy the incoming image into your array. If you got 30 images, you set this value to false again and you are done. I currently do not have a sample for this.

Stefan

ODMRklaus commented 1 year ago

Thanks for your fast answer. Using this i receive 31 images. But unfortunately after five images i get the same images as before so i have a repeating structure. Could it be that the way i am saving my images is wrong? this "ic.IC_SetPropertySwitch(hGrabber, tis.T("Trigger"), tis.T("Enable"), 1)" works for me just with 0, otherwise i just receive two images.

TIS-Stefan commented 1 year ago

Hello

Thanks for your fast answer. Using this i receive 31 images. But unfortunately after five images i get the same images as before so i have a repeating structure. Could it be that the way i am saving my images is wrong?

You code looks ok to me (I am not a Python expert). But I do not know, what this "pickle" is doing. I would create a normal loop and save the images with cv2:imwrite()

this "ic.IC_SetPropertySwitch(hGrabber, tis.T("Trigger"), tis.T("Enable"), 1)" works for me just with 0, otherwise i just receive two images.

Does this mean, you get two image only? Your trigger source is working, so you get a trigger signal to the camera? Trigger Burst Count exist? Did you check with IC Capture? Which camera model do you use?

Stefan

ODMRklaus commented 1 year ago

Pickel just copies the list and secure the structure but i also checked before i use pickel how my images looks like. If i use it with 1 i only receive two images. Yes i get a trigger signal to my camera i ckeck that with ic capture and i receive every 2.4 seconds (trigger signal repeats every 2.4 sec) a frame. I use DMx 41BU02 camera. I dont know how to check with ic capture if trigger burst count exist.

TIS-Stefan commented 1 year ago

Hi

I am very sorry, the DMx 41BU02 has no trigger burst mode for sure.

I dont know how to check with ic capture if trigger burst count exist.

"Device" menu, select "Properties". Look into the "Special" tab. However... your camera does not have this property, which makes things too complicated. First of all, you can not use the trigger input for what you want to do with your camera. Thus, no trigger mode. You need the GP In on the Hirose connector. Pin 8 and 7. See at https://s1-dl.theimagingsource.com/api/2.5/packages/publications/whitepapers-cameras/wp21314151trigo/a851b96c-0c9f-5ea0-b5de-bfbbd747f603/wp21314151trigo_1.3.en_US.pdf on page 7. The sample at https://github.com/TheImagingSource/IC-Imaging-Control-Samples/blob/master/Python/tisgrabber/samples/30-read-GPIn.py shows, how to read the GP In. You may try the sample and see, whether you can read the changes of this property.

Stefan

ODMRklaus commented 1 year ago

Ok thanks. I think i will skip the trigger and try to programm it manually. One question regarding to snap an image and save it in a list. I try " import ctypes import tisgrabber as tis import cv2 as cv ic = ctypes.cdll.LoadLibrary("./tisgrabber_x64.dll")

tis.declareFunctions(ic)

ic.IC_InitLibrary(0)

hGrabber = hGrabber = ic.IC_ShowDeviceSelectionDialog(None) img = [] if(ic.IC_IsDevValid(hGrabber)): ic.IC_StartLive(hGrabber, 1) img.append(ic.IC_SnapImage(hGrabber)) ic.IC_StopLive(hGrabber) else: ic.IC_MsgBox(tis.T("No device opened"), tis.T("Simple Live Video"))

ic.IC_ReleaseGrabber(hGrabber) print(img) " but i think i have to convert it to an array.

TIS-Stefan commented 1 year ago

Hello

IC_SnapImage returns an integer, showing, whether there was success or not. In your case, I suppose "1" was printed. If you want to process the snapped image, you need to do, what is in https://github.com/TheImagingSource/IC-Imaging-Control-Samples/blob/master/Python/tisgrabber/samples/11-image-processing.py.

Stefan

ODMRklaus commented 1 year ago

One last thought on the triggering possibilities. How should the programm look like if i want to snap an image each time the camera recieve a trigger or is this even possible? Because i can set my trigger pulses also up to 1/15 Hz wich would correspond to the maximal frame rate of my camera. So would it be possible to take 30 images in 2 seconds by externaly trigger in this speed ?

TIS-Stefan commented 1 year ago

Because i can set my trigger pulses also up to 1/15 Hz wich would correspond to the maximal frame rate of my camera. So would it be possible to take 30 images in 2 seconds by externaly trigger in this speed ?

It would look like your first attempt, only that the trigger mode is enabled.

   ic.IC_SetFrameReadyCallback(hGrabber, Callbackfuncptr, Userdata)
    ic.IC_SetContinuousMode(hGrabber, 0)
    #enable the trigger after the first trigger signal is received
    ic.IC_SetPropertySwitch(hGrabber, tis.T("Trigger"), tis.T("Enable"), 1)

For each trigger signal one image is exposed and sent to your callback. However, if I remember correctly, you can trigger with half of fps on your camera only. so 7.5 Hz, if the camera is set to 15 fps. That is a limitation of your camera model, because in trigger mode the exposure and delivery of the frames run in sequence, while in free running exposure and delivery run in parallel.

Stefan

ODMRklaus commented 1 year ago

Hey again, i am back trying to trigger my camera. I am now able to get the right triggering sequence to my camera. I trigger my camera with a 5 hz puls sequence. I use the code which i send you before with enabled trigger. I get the expected amount of images but unfortunatly my images repeating. Every fifth image is the same. Could this come from the buffer or the way i try to save my images. I just append cv.mat at the end of the callback function.

TIS-Kevin commented 1 year ago

Dear ODMRklaus,

following code worked for me:

"""
This samples shows how to enable trigger mode, use software trigger
and how to use a callback, that is called automatically for each
incoming image on a trigger signal.
"""
import ctypes 
import tisgrabber as tis
import cv2 as cv
import numpy as np
import pickle
import time

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

ic.IC_InitLibrary(0)

hGrabber = ic.IC_CreateGrabber()

class CallbackUserdata(ctypes.Structure):
    """ Example for user data passed to the callback function. 
    """
    def __init__(self, ):
        self.Value1 = 1

def frameReadyCallback(hGrabber, pBuffer, framenumber, pData):
    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))

        # Do something with cvMat here.
        img.append(cvMat)

# Manage the callbacks
# Create the function pointer.
frameReadyCallbackfunc = ic.FRAMEREADYCALLBACK(frameReadyCallback)
userdata = CallbackUserdata()

hGrabber = ic.IC_ShowDeviceSelectionDialog(None)

if ic.IC_IsDevValid(hGrabber):
    ic.IC_SetFrameReadyCallback(hGrabber, frameReadyCallbackfunc, userdata)
    ic.IC_SetContinuousMode(hGrabber, 0)
    #enable the trigger after the first trigger signal is received
    ic.IC_SetPropertySwitch(hGrabber, tis.T("Trigger"), tis.T("Enable"), 1)

    ic.IC_StartLive(hGrabber, 1)
    #two seconds of data aquisition , araound 30 images
    time.sleep(2)

    ic.IC_StopLive(hGrabber)
    #looking for the repetion in my frames iamge 0 and 5 are the same
img_dif = img [0]-img[5]
cv.imshow('1',img[5])
cv.imshow("5",img[5])
cv.imshow("dif", img_dif)
cv.waitKey(0) & 0xFF
cv.destroyWindow('i')

#write python file to save the images
with open('img.txt', 'wb') as temp:
    pickle.dump(img, temp)

ic.IC_ReleaseGrabber(hGrabber)

Let me know if that helped you out.

Best Regards,

Kevin

Ezreal105 commented 1 year ago

I noticed in the document that the default size of the image ring buffer is 5. Is this related?

TIS-Stefan commented 1 year ago

I noticed in the document that the default size of the image ring buffer is 5. Is this related?

No. The ring buffer size is on the computer, not on the camera. But, if the property Trigger Burst Count is available, then it can be set to 30, which means, one trigger signal lets the camera provide 30 images. At 15 fps, this would be 2 seconds. Simply check with IC Capture, whether this property is available.

A sample code is like this

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)

Stefan