morefigs / pymba

Python wrapper for Allied Vision's Vimba C API
MIT License
105 stars 84 forks source link

captureFrameQueue's callback argument not yet implemented #8

Closed npyoung closed 7 years ago

npyoung commented 9 years ago

I was working on doing this myself, and actually managed to get the C API to accept and call my python function, but this leaves me inside a function with nothing but a pointer to the frame. I don't have enough experience with ctypes to know what to do from here. Is anyone working on this?

There's a note in the source about a callback example in pico (vimbaframe.py:96). What does that refer to?

morefigs commented 9 years ago

Yes I didn't ever implement the callback as waitFrameCapture was adequate for my needs. Would be cool if you got that working. Never mind about the pico reference, that's just an old comment, I'll remove it.

Can you link to the code where you got the callback working? As a wild guess (I don't have a camera to test with) you would need to do something like:

In vimbaframe.py, add a C function type definition outside the class block so the argument and return types are defined (the first argument of CFUNCTYPE is the return type of the call) like this:

C_FRAME_READY_CALLBACK = CFUNCTYPE(None, c_void_p, POINTER(VimbaFrame))

which says the callback function VmbFrameCallback returns None and takes a handle and a pointer to a frame as the args.

Add a private method to the VimbaFrame class like:

def _get_callback_func(self):
    def cb_func(handle, p_frame):
        print 'callback function called!'
    return C_FRAME_READY_CALLBACK(cb_func)

Then in the queueFrameCapture method, update the call to:

self._rfrc_func = self._get_callback_func()

errorCode = VimbaDLL.captureFrameQueue(self._handle,
                                       byref(self._frame),
                                       self._rfrc_func)

In reality that will be a little useless as your higher level application would want to be registering a Python function to be called I imagine, but it would be a good first test to get working. You shouldn't need to do anything with the frame pointer, you can just use the callback as a trigger to ready the contents of that frame in the usual way, e.g. imgData = frame0.getBufferByteData() as it should now be populated.

Hope that helps.

See also: http://stackoverflow.com/questions/7259794/how-can-i-get-methods-to-work-as-callbacks-with-python-ctypes

npyoung commented 9 years ago

My implementation defines the callback signature here and implements passing a callback here. You would do something like

def myfun(frame_ptr):
    print "frame done!"

frame0.queueFrameCapture(vimbadll.frameDoneCallback(myfun))

I could just use the callback as a trigger to get the buffer from a frame and requeue it, but if I have multiple frames, as I anticipate needing for my high-speed application, then you need to know which frame got filled, and the callback only provides you with a pointer.

morefigs commented 9 years ago

Rather than have to reference vimbadll directly from your application, it's probably cleaner to get vimbaframe.py to do it for you:

def queueFrameCapture(self, frameCallback=None):
    """
    Queue frames that may be filled during frame capturing.
    Runs VmbCaptureFrameQueue
    Call after announceFrame and startCapture
    frameCallback function must accept argument of type frame
    """

    # remember user's callback function
    self._frameCallback = frameCallback

    # define here so it's not a bound method (can't use self parameter)
    def frameCallbackIntercept(_handle, p_frame):
        # ignore the handle and frame pointer we get back as we already know which frame this is
        # instead call user's callback function with this frame as the argument
        self._frameCallback(self)

    if self._frameCallback is None:
        self._frameCallbackIntercept_C = None
    else:
        # keep reference to prevent garbage collection issues
        self._frameCallbackIntercept_C = VimbaDLL.frameDoneCallback(frameCallbackIntercept)

    errorCode = VimbaDLL.captureFrameQueue(self._handle,
                                           byref(self._frame),
                                           self._frameCallbackIntercept_C)  # callback
    if errorCode != 0:
        raise VimbaException(errorCode)

That way you just call it like:

def myfun(frame):
    print 'frame ready!'
    # do something with the actual frame object (not a pointer), maybe could even requeue it e.g.
    # frame.getBufferByteData()
    # frame.queueFrameCapture(myfun)

frame0.queueFrameCapture(myfun)

Hopefully that works! The CFUNCTYPE definition might need to be outside the class block.

jrast commented 8 years ago

Has anyone had success with callbacks? The solution of @npyoung seems to be broken with Vimba 1.4. and the testfile he once created (https://github.com/npyoung/pymba/blob/f1558fa3e1910eb27a78d9fda669bda218103e4b/pymba/tests/test_frame_callbacks.py) is not runable (and never was?) because of a undefined function.

On my machine (Windows 7, Vimba 1.4, Mantas Camera), Python crashed without a Traceback... If someone can give me any hints im willing to fix this!

jrast commented 8 years ago

I have created a gist with my scripts for this: https://gist.github.com/jrast/eeda7458d8216d9dd73f

Currently the output looks like this:

Created Frame <pymba.vimbaframe.VimbaFrame object at 0x02BADA30>, Struct <pymba.vimbastructure.VimbaFrame object at 0x02BC8A80>
Created Frame <pymba.vimbaframe.VimbaFrame object at 0x02BADBB0>, Struct <pymba.vimbastructure.VimbaFrame object at 0x02BC8C10>
Qeueing Frame <pymba.vimbaframe.VimbaFrame object at 0x02BADA30> with callback <function frame_cb at 0x024DC1F0>
Qeueing Frame <pymba.vimbaframe.VimbaFrame object at 0x02BADBB0> with callback <function frame_cb at 0x024DC1F0>
Start Callback Wrapper for Cam 5923, Frame <pymba.vimbadll.LP_VimbaFrame object at 0x02BC8C60>, Callback <function frame_cb at 0x024DC1F0>
Start Callback <function frame_cb at 0x024DC1F0> with Frame <pymba.vimbaframe.VimbaFrame object at 0x02BADA30>
Qeueing Frame <pymba.vimbaframe.VimbaFrame object at 0x02BADA30> with callback <function frame_cb at 0x024DC1F0>
End Callback <function frame_cb at 0x024DC1F0> with Frame <pymba.vimbaframe.VimbaFrame object at 0x02BADA30>
End Callback Wrapper for Cam 5923, Frame <pymba.vimbadll.LP_VimbaFrame object at 0x02BC8C60>, Callback <function frame_cb at 0x024DC1F0>

Note that the callback wrapper does receive a frame which is not created by me: The address of the frame is at 0x02BC8C60 and at this address none of my frames exist.

Have a look at the gist to see all the changes that I have made to get this output.

jrast commented 8 years ago

OK, probably I have found the solution:

In vibadll.py, line 314, change the frameDoneCallback as shown:

# callback for frame queue
#    frameDoneCallback = CFUNCTYPE(c_void_p,                         # camera handle
#                                  POINTER(structs.VimbaFrame))      # pointer to frame
    frameDoneCallback = WINFUNCTYPE(c_void_p, # Return Type
                                    c_void_p, # Camera Handle
                                    POINTER(structs.VimbaFrame)) # Pointer to frame

In VimbaFrame.queueFrameCapture, change the wrapper function to:

  def frameCallbackWrapper(cam_handle, p_frame):
        self._frameCallback(self)

I will create a pull request as soon as I have made some more tests.

morefigs commented 7 years ago

Presumed fixed!