AlexShkarin / pyLabLib

Python package for device control and experiment automation
http://pylablib.readthedocs.io
GNU General Public License v3.0
142 stars 34 forks source link

Async read from Bitflow board #31

Open spoorgholi74 opened 1 year ago

spoorgholi74 commented 1 year ago

Hi I have a BitFlow frame grabber and 2 cameras and I am trying to read from the cameras Async at the same time. I was wondering if this is possible.

This is how the cameras are setup: self.cam_1.setup_acquisition(mode="sequence", nframes=4, frame_merge=1) self.cam_2.setup_acquisition(mode="sequence", nframes=4, frame_merge=1)

and This is how they are read (Currently sequential): self.cam_1.wait_for_frame() self.cam_2.wait_for_frame()

AlexShkarin commented 1 year ago

Hello!

If the question is regarding opening connection to two cameras simultaneously, then the answer is generally yes. As far as I can tell, if these are two different frame grabbers (each with one camera), this should not be an issue. You simply create two different camera connections:

from pylablib.devices import BitFlow
cam_1 = BitFlow.BitFlowCamera(bitflow_idx=0)
cam_2 = BitFlow.BitFlowCamera(bitflow_idx=1)
frame_1 = cam_1.snap()
frame_2 = cam_2.snap()
cam_1.close()
cam_2.close()

However, if it is a single frame grabber with two separate CameraLink ports and two independent cameras (which some manufacturers have sometimes), then I can not comment on that, as I have no experience here. Unless I missed something in the SDK documentation, one grabber only supports one acquisition frame buffer, i.e., one camera.

If the question is regarding simultaneous acquisition, then this should already be happening automatically. Once you start acquisition, it runs in a separate SDK-provided thread, and your code can do other things in the meantime. To check on the acquisition status you can use get_new_images_range method, which returns a tuple (first, last) with a range of frames which have been acquired and not read, or None if there are any no new frames. You can then use this range as an argument for read_multiple_images method to read these frames. Hence the code would look something like this:

# start acquisition for both cameras (it can take the same arguemtns as setup_acquistion)
cam_1.start_acquisition(mode="sequence", nframes=100)  # better to have a larger buffer, e.g., 100 frames
cam_2.start_acquisition(mode="sequence", nframes=100)
frames_1=[]
frames_2=[]
max_frames=100
while len(frames_1)<max_frames or len(frames_2)<max_frames: # wait until we've acquired 100 frames from each camera
    rng=cam_1.get_new_images_range()
    if rng is not None:  # got new frames
        # read new frames and append to the list
        # frames from rng are marked as read, so they will not be included in the next get_new_images_range call
        frames_1+=cam_1.read_multiple_images(rng)
    rng=cam_2.get_new_images_range()
    # same for the second camera
    if rng is not None:
        frames_2+=cam_2.read_multiple_images(rng)
    time.sleep(1E-3)  # sleep for 1ms between checks to reduce CPU load
cam_1.stop_acquisition()
cam_2.stop_acquisition()

Let me know if this works for you!

Sincerely,

Alexey.

spoorgholi74 commented 1 year ago

Thanks for the response.

So my case is the second one with a single frame grabber and two cameras. I am aware that the writing to the buffers happens async in the SDK but I was wondering if reading from the Buffers (reading frames of cam1 and cam2) can be done async. Which I think is not possible. So I guess you read from the buffers one after each other?

AlexShkarin commented 1 year ago

I'm not completely sure what you're referring to when you talk about async reading from buffers, but I'll try to comment on that.

Pylablib uses the BitFlow-provided Python SDK directly (as opposed to a bare C interface, which I generally prefer in such cases), since it's not straightforward to access the SDK documentation or even header files. As far as I can judge, the Python interface is realtively limited, and there are some workaround I needed to implement to make it more user-friendly.

One of these workarounds is related to the buffer management. The coded uses clsCircularAcquisition class, which indeed doesn't seem to provide an easy way to query the buffer state without waiting for a new frame. To get around it, the code spins up a new thread which repeatedly waits for a new frame and updates the frame counter when successful. The rest of the buffer interaction, including accessing the frame data, is done in a normal way in whatever thread calls the device methods. Hence, querying the buffer fill status is done asynchronously, but reading the data (in the sense of copying it from the SDK internal buffer into a new numpy array) is done synchronously.

Since data copying is CPU/RAM-bound, I wouldn't expect to get any performance gains from doing it asynchronously, so that's why I settled on this approach. It seems have worked relatively well, and could generally achieve several kFPS without dropping frames provded large enough frame buffer size (usually about 1 second worth of data). That being said, I never tried the code on a system with two frame grabbers, or with a single frame grabber with two cameras, so can't guarantee that there are no unexpected problems there. I'm also not sure how one would approach a single frame grabber with two cameras, and whatever documentation I could find does not seem to address that either. I guess, it would show up as two separate board indices?

Let me know if that helps. If not, maybe you can say a bit more specifically what you are aiming to do, so that I could understand your problem better.

Sincerely,

Alexey.