TheImagingSource / ic4-examples

IC4 Example Programs
Apache License 2.0
5 stars 3 forks source link

Question re `framesQueued` and `popOutputBuffer`, and a suggestion ? #4

Open g40 opened 7 months ago

g40 commented 7 months ago

At first glance, it is not clear that the callback function framesQueued runs in a different thread. Might this be documented a little more clearly?

The next question is can you run capture loop in a single thread? The answer seems to be yes, but with the inefficiency of polling.

The example code below is just to illustrate the concept. what would be great here is to make popOutputBuffer() a blocking call when given an optional timeout - this would put the calling thread to sleep until either a frame is ready or the timeout expires. Similar to WaitObject() in the Windows API.

Just a thought. Thanks for listening.


    //
    class NullListener : public ic4::QueueSinkListener {
    public:
        // caution! this callback is in a different thread
        void framesQueued(ic4::QueueSink& qsink) override {
            // do nothing rather than popping a queued image
            // auto image = qsink.popOutputBuffer();
        }
    }

    // N.B. No error checking for brevity
    int main()
    {
        ic4::initLibrary();
        // 1 device. use it
        auto device_list = ic4::DeviceEnum::enumDevices();  
        ic4::DeviceInfo di = device_list[0]
        //
        ic4::Grabber grabber;
        grabber.deviceOpen(di.serial());
        // create a queue sink with the null listener. 
        NullListener nl;
        auto qsink = ic4::QueueSink::create(nl);
        // hook up grabber sink and run grabber.
        grabber.streamSetup(qsink, ic4::StreamSetupOption::AcquisitionStart);
        // loop - this polled model is operating in the *same* thread
        while (true) {
            // image d'tor will handle image recycling
                        // make popOutputBuffer a blocking call?
            auto image = qsink->popOutputBuffer(std::chrono::seconds(10));
            if (!image)
                continue;
            // do work with the image ...
        }
        grabber.streamStop();   
        grabber.deviceClose();
    }
TIS-Tim commented 7 months ago

If you want the simplest possible program and just capture images in a loop, the suggested way is to use the SnapSink.

While SnapSink::snapImage is semantically not identical to QueueSink::popOutputBuffer, for simple programs this should not really matter. As a bonus, a parallel live display will not get hindered by slow buffer processing.

(Yes, in theory a blocking popOutputBuffer can work better if you have inconsistent image processing times, but on the other hand it would be easy to accidently write "wrong" programs with that.)

g40 commented 7 months ago

Hello Tim, thanks again.

Can I get a bit more detail then about popOutputBuffer? Is that thread safe? Is it safe to call once framesQueued has been called?

My requirement is to be able to pull all images in the incoming stream as quickly as possible as push them off to some more complex image processing. This is for industrial inspection purposes running at quite high speeds so dropping frames is really not an option. Thus minimizing both complexity and possible errors on the capture interface is highly desirable.

If it helps I can post a a very compact example of the code I am currently using.

Thanks again for listening.

TIS-Tim commented 7 months ago

If you want to get all images, QueueSink is the way to go.

popOutputBuffer is guaranteed to succeed once inside framesQueued, since there always is at least one buffer available when the function is called. You don't need to loop and framesQueued will be called again immediately if you want to simplify your program.

Yes, it is thread-safe, you can call it from any thread, even multiple times at once.

I think there are basically two ways to go about to solve your application: (1) Call popOutputBuffer in framesQueued, and pass the result to your processing code. You can use the thread framesQueued runs on for this if single-threaded is enough for you. The thread has no other task other than running framesQueued. (2) Flag an event in framesQueued, wait for that in the main thread, and do popOutputBuffer+processing there. For each event, you will have to loop popOutputBuffer until it fails, since you cannot know whether you missed an event during processing.

Both should yield very similar image throughput.

PS: I think at the moment framesQueued is called repeatedly forever until there are no buffers queued. This behavior will cause a busy loop on the framesQueued-thread in solution 2 above. This feels like a bug and will likely get changed in a future release.

g40 commented 7 months ago

Hello again and thanks for the detailed reply. Much appreciated. My minimal test bed uses precisely the second variety - the framesQueued thread simply sets an event, upon which the main capture loop is waiting. The clarification around popOutputBuffer is especially reassuring.