Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] 'queue=False' appears not to disable queue prepopulation #787

Open
alexw-im opened this issue Aug 30, 2023 · 4 comments
Open

[BUG] 'queue=False' appears not to disable queue prepopulation #787

alexw-im opened this issue Aug 30, 2023 · 4 comments

Comments

@alexw-im
Copy link

alexw-im commented Aug 30, 2023

Describe the bug
While using "queue=False" in i.e. config = picam2.create_still_configuration({"size": (1280, 800)}, raw={'format': 'R10'}, queue=False, controls=controls), it doesn't appear to accomplish the design intent of keeping the output buffer from pre-filling.

To Reproduce
I'm using an OV9281 sensor (arducam module in this case) with a Pi 4B.

import time
from picamera2 import Picamera2, Preview
from libcamera import controls

picam2 = Picamera2()
controls = {"ExposureTime": 100000, "AnalogueGain": 1.0}
config = picam2.create_still_configuration({"size": (1280, 800)}, raw={'format': 'R10'}, queue=False, controls=controls)
picam2.configure(config)
# In fact, picam2.start() would do this anyway for us:
picam2.start_preview(Preview.NULL)

picam2.start()
time.sleep(3)


count = 0
if __name__ == '__main__':
    #r = picam2.capture_request() # Fill the buffer with an initial frame so we know the one we try to get will be fresh
    while True:
        time.sleep(1)
        count += 1
        # metadata = picam2.capture_file(f"test-{count}.jpg")
        start = time.time()
        #r.release()
        r = picam2.capture_request()
        elapsed = time.time()-start
        print(f"Elapsed: {elapsed}")
        meta = r.get_metadata()
        print(meta)
        r.save("main", f"test-{count}.jpg")
        r.release()
        # print(metadata) 
        print(f"Triggered {count}")

picam2.stop()

Expected behaviour
This snippet SHOULD take one 100ms capture on-demand once per while loop, then sleep a while (a second). The idea is that I want a single frame as close to right-after-when-I-request-it as possible.

Having read 4.2.1.4 in the Picamera2 manual, I figured I'd use the queue=False option in my configuration to make sure the queue was never pre-filled with prior-to-trigger image data.

As written, the call to picam2.capture_request() should be blocking and take at least as long as the exposure. I'd expect that the "elapsed time" counter should always be >> 100ms: 100ms to expose, plus however much to read out, process, compute, update apt, check stocks, phone home, etc.

In this case, however, my "elapsed" timer SOMETIMES takes >> 100ms, and sometimes returns within single-digit milliseconds. See console output below.

Console Output, Screenshots
Note below that all snapshots take well over 100ms to return, except the 3rd trigger which returns in only 4.9ms

Elapsed: 0.16357421875
{'SensorTimestamp': 7125409563000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.06434440612793, 'FocusFoM': 5392, 'ExposureTime': 99999}
Triggered 1
Elapsed: 0.1396324634552002
{'SensorTimestamp': 7126710165000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.03728675842285, 'FocusFoM': 5391, 'ExposureTime': 99999}
Triggered 2
Elapsed: 0.004918098449707031
{'SensorTimestamp': 7127810681000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.044885635375977, 'FocusFoM': 5403, 'ExposureTime': 99999}
Triggered 3
Elapsed: 0.1906599998474121
{'SensorTimestamp': 7129111285000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.053913116455078, 'FocusFoM': 5368, 'ExposureTime': 99999}
Triggered 4
Elapsed: 0.19505882263183594
{'SensorTimestamp': 7130411888000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.055377960205078, 'FocusFoM': 5328, 'ExposureTime': 99999}
Triggered 5
Elapsed: 0.19681406021118164
{'SensorTimestamp': 7131712491000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.062702178955078, 'FocusFoM': 5463, 'ExposureTime': 99999}
Triggered 6

Workaround :
I believe the above is a bug: perhaps "queue=False" gets ignored if there's only one allocated memory queue, assuming that the user won't want a frame "only very occasionally"?

In any event, the following configuration of the script works around the issue by deliberately holding the queue until right before the desired capture. We start by capturing a request right before entering the loop, then from that point on we only release that request right before making a new one.

The fact that this workaround works as expected makes me think the base case is indeed buggy and I'm not simply misunderstanding usage.

import time
from picamera2 import Picamera2, Preview
from libcamera import controls

picam2 = Picamera2()
controls = {"ExposureTime": 100000, "AnalogueGain": 1.0}
config = picam2.create_still_configuration({"size": (1280, 800)}, raw={'format': 'R10'}, queue=False, controls=controls)
picam2.configure(config)
# In fact, picam2.start() would do this anyway for us:
picam2.start_preview(Preview.NULL)

picam2.start()
time.sleep(3)


count = 0
if __name__ == '__main__':
    r = picam2.capture_request() # Fill the buffer with an initial frame so we know the one we try to get will be fresh
    while True:
        time.sleep(1)
        count += 1
        # metadata = picam2.capture_file(f"test-{count}.jpg")
        start = time.time()
        r.release()
        r = picam2.capture_request()
        elapsed = time.time()-start
        print(f"Elapsed: {elapsed}")
        meta = r.get_metadata()
        print(meta)
        r.save("main", f"test-{count}.jpg")
        #r.release()
        # print(metadata) 
        print(f"Triggered {count}")

picam2.stop()

And the output. Note that the minimum elapsed capture time is 152ms:

Elapsed: 0.1956775188446045
{'SensorTimestamp': 7881759452000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.440120697021484, 'FocusFoM': 5148, 'ExposureTime': 99999}
Triggered 1
Elapsed: 0.15225839614868164
{'SensorTimestamp': 7883060059000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.417917251586914, 'FocusFoM': 5349, 'ExposureTime': 99999}
Triggered 2
Elapsed: 0.18652868270874023
{'SensorTimestamp': 7884360663000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.452198028564453, 'FocusFoM': 5353, 'ExposureTime': 99999}
Triggered 3
Elapsed: 0.19385337829589844
{'SensorTimestamp': 7885661265000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.430110931396484, 'FocusFoM': 5308, 'ExposureTime': 99999}
Triggered 4
Elapsed: 0.18757224082946777
{'SensorTimestamp': 7886961876000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.414443969726562, 'FocusFoM': 5424, 'ExposureTime': 99999}
Triggered 5
Elapsed: 0.19856715202331543
{'SensorTimestamp': 7888262469000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.442598342895508, 'FocusFoM': 5386, 'ExposureTime': 99999}
Triggered 6
Elapsed: 0.1977245807647705
{'SensorTimestamp': 7889563076000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.397911071777344, 'FocusFoM': 5296, 'ExposureTime': 99999}
Triggered 7
Elapsed: 0.2108614444732666
{'SensorTimestamp': 7890863676000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.412031173706055, 'FocusFoM': 5342, 'ExposureTime': 99999}
Triggered 8
Elapsed: 0.20072627067565918
{'SensorTimestamp': 7892164282000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.511882781982422, 'FocusFoM': 5301, 'ExposureTime': 99999}
Triggered 9
Elapsed: 0.18394255638122559
{'SensorTimestamp': 7893464881000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.493953704833984, 'FocusFoM': 5321, 'ExposureTime': 99999}
Triggered 10
Elapsed: 0.2063596248626709
{'SensorTimestamp': 7894765487000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.491409301757812, 'FocusFoM': 5355, 'ExposureTime': 99999}
Triggered 11
Elapsed: 0.19242191314697266
{'SensorTimestamp': 7896066091000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.49312400817871, 'FocusFoM': 5355, 'ExposureTime': 99999}
Triggered 12
Elapsed: 0.2053236961364746
{'SensorTimestamp': 7897366691000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.47867202758789, 'FocusFoM': 5327, 'ExposureTime': 99999}
Triggered 13
Elapsed: 0.20030546188354492
{'SensorTimestamp': 7898667300000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 22.511098861694336, 'FocusFoM': 5376, 'ExposureTime': 99999}
Triggered 14

Hardware :
Pi 4B with ArduCam OV9281 B0165 module.

Additional context
My true motive for this is to timestamp the frame capture as accurately as possible. I know that neither python nor linux are real-time and thus there are any number of preemptions that could cause jitter in my "timestamp-then-snap" delay, but I'm hunting for ways to minimize that. I know the returned "SensorTimestamp" is quite precise and accurate to the true second-shutter timing, but as far as I'm aware there's no way to accurately translate that to system time, correct? Whatever I can do to get a precise time.time() for the start/center/end of the shutter-open, I'd quite like to do, up to and including preempt_rt, but I'm not sure how dramatically that would help.

@alexw-im alexw-im changed the title [BUG] [BUG] 'queue-False' appears not to disable queue prepopulation Aug 30, 2023
@alexw-im alexw-im changed the title [BUG] 'queue-False' appears not to disable queue prepopulation [BUG] 'queue=False' appears not to disable queue prepopulation Aug 30, 2023
@alexw-im
Copy link
Author

Not directly related to the bug but in my mission to get accurate timestamps: I just came across #703 and it seems my notion that the "SensorTimestamp" and time.monotonic_ns() come from different domains (VPU vs CPU) is actually incorrect.

With that knowledge, I modified my test code a little, not WITHOUT the workaround present:

count = 0
if __name__ == '__main__':
    #r = picam2.capture_request() # Fill the buffer with an initial frame so we know the one we try to get will be fresh
    while True:
        time.sleep(1)
        count += 1
        # metadata = picam2.capture_file(f"test-{count}.jpg")
        start = time.monotonic_ns()
        #r.release()
        r = picam2.capture_request()
        end = time.monotonic_ns()
        meta = r.get_metadata()
        print(f"Elapsed1: {(end-start)/1000000} Elapsed since frame: {(end-meta['SensorTimestamp'])/1000000}")
        print(meta)
        r.save("main", f"test-R8-maybenoisereduction-{count}.jpg")
        r.release()
        # print(metadata) 
        print(f"Triggered {count}")

You see it now reports the time.monotonic_ns() elapsed while picam2.capture_request() runs, as well as the time elapsed between the frame timestamp and the very next line after capture_request()

What's throwing me for a loop now is that if you look at the output below, the "frame to next line" delta is always between 17 and 21ms, even when the capture_request() returns in only 4ms.

Elapsed1: 162.409195 Elapsed since frame: 19.740034
{'SensorTimestamp': 9964104953000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.51514434814453, 'FocusFoM': 4854, 'ExposureTime': 99999}
Triggered 1
Elapsed1: 142.64831 Elapsed since frame: 18.66787
{'SensorTimestamp': 9965405554000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.523059844970703, 'FocusFoM': 4723, 'ExposureTime': 99999}
Triggered 2
Elapsed1: 186.57649 Elapsed since frame: 18.562124
{'SensorTimestamp': 9966706159000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.54002571105957, 'FocusFoM': 4742, 'ExposureTime': 99999}
Triggered 3
Elapsed1: 197.941533 Elapsed since frame: 17.099872
{'SensorTimestamp': 9968006758000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.53316307067871, 'FocusFoM': 4899, 'ExposureTime': 99999}
Triggered 4
Elapsed1: 125.71644 Elapsed since frame: 20.704109
{'SensorTimestamp': 9969207322000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.53297996520996, 'FocusFoM': 4892, 'ExposureTime': 99999}
Triggered 5
Elapsed1: 2.547462 Elapsed since frame: 18.628827
{'SensorTimestamp': 9970307830000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.511856079101562, 'FocusFoM': 4842, 'ExposureTime': 99999}
Triggered 6
Elapsed1: 196.682543 Elapsed since frame: 18.538436
{'SensorTimestamp': 9971608433000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.53684425354004, 'FocusFoM': 4913, 'ExposureTime': 99999}
Triggered 7
Elapsed1: 195.320534 Elapsed since frame: 14.01243
{'SensorTimestamp': 9972909031000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.515575408935547, 'FocusFoM': 4885, 'ExposureTime': 99999}
Triggered 8
Elapsed1: 154.029186 Elapsed since frame: 18.789735
{'SensorTimestamp': 9974109589000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.533973693847656, 'FocusFoM': 4696, 'ExposureTime': 99999}
Triggered 9
Elapsed1: 198.9581 Elapsed since frame: 18.578327
{'SensorTimestamp': 9975410196000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.50151252746582, 'FocusFoM': 4874, 'ExposureTime': 99999}
Triggered 10
Elapsed1: 199.78176 Elapsed since frame: 18.536307
{'SensorTimestamp': 9976710795000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.5031681060791, 'FocusFoM': 4611, 'ExposureTime': 99999}
Triggered 11
Elapsed1: 3.880729 Elapsed since frame: 18.518122
{'SensorTimestamp': 9977811310000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.511371612548828, 'FocusFoM': 4838, 'ExposureTime': 99999}
Triggered 12
Elapsed1: 113.08176 Elapsed since frame: 18.759167
{'SensorTimestamp': 9979011864000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.479738235473633, 'FocusFoM': 4793, 'ExposureTime': 99999}
Triggered 13
Elapsed1: 199.974833 Elapsed since frame: 18.773278
{'SensorTimestamp': 9980312469000, 'ScalerCrop': (0, 0, 1280, 800), 'DigitalGain': 1.0000009536743164, 'SensorBlackLevels': (4096, 4096, 4096, 4096), 'AeLocked': True, 'FrameDuration': 100036, 'AnalogueGain': 1.0, 'Lux': 20.447607040405273, 'FocusFoM': 4478, 'ExposureTime': 99999}
Triggered 14

Does this imply that the "SensorTimestamp" is simply when the frame got returned, rather than being precisely tied to when the frame hit the SOC coming from the camera?

@davidplowman
Copy link
Collaborator

A couple of things to consider. The Pi won't respond in a particularly real-time way, so measuring elapsed times is going to give you very variable results. So you need to use the SensorTimestamp in the metadata.

The SensorTimestamp records the frame start time from the camera, so you can generally expect it to be earlier than the time at which you try to capture the request (even with queue=False). Even this, though, indicates the start of the frame being read out, so the time at which it started being exposed is even earlier. I guess you need to be very clear what you want, because the start of exposure, the start of readout and the end of readout are all different!!

The SensorTimestamp is based on CPU times, so this should line up with what Python's time.monotonic() reports. If you really want to guarantee that the frame starts being read out after a specific time, I think you'd have to capture every request and check that the SensorTimestamp is late enough. If you care about the start of exposure, then you probably need to account for the current exposure time too. You'll need to set the buffer_count to at least 2, otherwise you're guaranteed to drop every other camera frame.

Hope that helps - sorry if it's all a bit complicated!

@alexw-im
Copy link
Author

alexw-im commented Sep 7, 2023

That all jives with info from this issue I opened with libcamera (since their documentation about the timestamp is a little ambiguous and not technically quite accurate) https://bugs.libcamera.org/show_bug.cgi?id=196, thank you!

You'll need to set the buffer_count to at least 2, otherwise you're guaranteed to drop every other camera frame.

I think what I'm trying to do is exactly this. I explicitly don't want a frame that was started earlier than I asked for, and I don't care about dropping frames.

For instance, say my exposure time is a full second, and "processing" takes half a second. I'm trying to minimize total latency from frame start to computation finish, NOT trying to "return data as soon as possible"

For example A, let's say it's T0 and I'm "ready" so I command a capture, wait 1s, get a frame, process for half a second, and 1.5s plus or minus some jitter later, I return data with a total latency of around 1.5s from image start to data out.

For example B, let's say it's T0 and I'm "ready" so I command a capture, but there's already a frame in the buffer that finished exposure 100ms ago, and that frame is returned to me instantly to process on, so I take my half second and do that. In this case, since the frame STARTED 1.1s before it was returned to me, I do my processing and return data with a full latency of 1.6s.

The data in B gets returned sooner than the data in A, BUT the data corresponds to a more-stale sample than in A.

My understanding with the queue=false scenario is that it SHOULD force scenario A, without my silly workaround of "bogart the buffer so the camera can't fill it until I say so" - which it seems not to be doing in my case.

But maybe you're suggesting a best-of-both worlds? I never want to receive a buffer that's already filled, but I wouldn't mind receiving a buffer that's in the process of filling. As long as the buffer corresponds to an exposure-in-progress-or-readout, that still implies a minimized total shutter-to-processed latency, rather than a filled buffer which means "image is no longer fresh"

@davidplowman
Copy link
Collaborator

If you just want to minimise the time from the frame starting to processing it, then I think that setting queue=False probably does this, and the frames you get will be the very next ones to pop out of libcamera. But the behaviour of the system will still be jittery (Linux is not very real-time after all, and Python even less so), and interpreting the timestamps is not so straightforward, as per the other details in this discussion.

Maybe you could set a pre_callback which records the current time in the CompletedRequest object. That should give you the time (as best we can get hold of it) at which the frame pops out of libcamera. Would that help you to sort out what is going on?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants