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

Timestamp capture #851

Merged
merged 2 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions examples/timestamp_capture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/python3

import time

from picamera2 import Picamera2


def check_request_timestamp(request, check_time):
md = request.get_metadata()
# 'SensorTimestamp' is when the first pixel was read out, so it started being
# exposed 'ExposureTime' earlier.
exposure_start_time = md['SensorTimestamp'] - 1000 * md['ExposureTime']
if exposure_start_time < check_time:
print("ERROR: request captured too early by", check_time - exposure_start_time, "nanoseconds")


picam2 = Picamera2()
picam2.start()

time.sleep(1)

# Capture a request that is guaranteed not to have started being exposed before
# we make the request. Do it 30 times as a test. Note that this kind of thing is
# likely cut the apparent framerate because of all the stopping and waiting for sync.

for _ in range(30):
check_time = time.monotonic_ns()
request = picam2.capture_request(flush=True)
check_request_timestamp(request, check_time)
request.release()

# Capture a request where nothing started being exposed until 1/2s in the
# future. Do this 10 times as a test.

for _ in range(10):
check_time = time.monotonic_ns() + 5e8
request = picam2.capture_request(flush=check_time)
check_request_timestamp(request, check_time)
request.release()

# Let's do this last test again, but using asynchronous operation.

for _ in range(10):
check_time = time.monotonic_ns() + 5e8
job = picam2.capture_request(flush=check_time, wait=False)
# Now we can do other stuff while waiting for the capture.
request = picam2.wait(job)
check_request_timestamp(request, check_time)
request.release()
30 changes: 23 additions & 7 deletions picamera2/picamera2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1309,12 +1309,24 @@ def set_frame_drops_(self, num_frames):
return (True, None)

def drop_frames_(self):
if not self.completed_requests:
return (False, None)
if self._frame_drops == 0:
while self.completed_requests:
if self._frame_drops == 0:
return (True, None)
self.completed_requests.pop(0).release()
self._frame_drops -= 1
return (False, None)

def wait_for_timestamp_(self, timestamp_ns):
# No wait requested. This function in the job is done.
if not timestamp_ns:
return (True, None)
self.completed_requests.pop(0).release()
self._frame_drops -= 1
while self.completed_requests:
# Check if frame started being exposed after the timestamp.
md = self.completed_requests[0].get_metadata()
frame_timestamp_ns = md['SensorTimestamp'] - 1000 * md['ExposureTime']
if frame_timestamp_ns >= timestamp_ns:
return (True, None)
self.completed_requests.pop(0).release()
return (False, None)

def drop_frames(self, num_frames, wait=None, signal_function=None):
Expand Down Expand Up @@ -1413,13 +1425,17 @@ def capture_request_(self):
return (False, None)
return (True, self.completed_requests.pop(0))

def capture_request(self, wait=None, signal_function=None):
def capture_request(self, wait=None, signal_function=None, flush=None):
"""Fetch the next completed request from the camera system.

You will be holding a reference to this request so you must release it again to return it
to the camera system.
"""
functions = [self.capture_request_]
# flush will be the timestamp in ns that we wait for (if any)
if flush is True:
flush = time.monotonic_ns()
functions = [partial(self.wait_for_timestamp_, flush),
self.capture_request_]
return self.dispatch_functions(functions, wait, signal_function)

def switch_mode_capture_request_and_stop(self, camera_config, wait=None, signal_function=None):
Expand Down
1 change: 1 addition & 0 deletions tests/test_list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ examples/still_capture_with_config.py
examples/still_during_video.py
examples/switch_mode.py
examples/switch_mode_2.py
examples/timestamp_capture.py
examples/title_bar.py
examples/tuning_file.py
examples/video_with_config.py
Expand Down
Loading