Measure framerate in non-blocking way

This commit is contained in:
jaknapper 2026-05-11 18:28:44 +01:00
parent 7450a0e47c
commit 44300eafd6

View file

@ -17,10 +17,12 @@ https://datasheets.raspberrypi.com/camera/raspberry-pi-camera-guide.pdf
from __future__ import annotations from __future__ import annotations
import copy import copy
import csv
import json import json
import logging import logging
import os import os
import tempfile import tempfile
import threading
import time import time
from contextlib import contextmanager from contextlib import contextmanager
from threading import RLock from threading import RLock
@ -548,6 +550,81 @@ class StreamingPiCamera2(BaseCamera):
with self._streaming_picamera() as cam: with self._streaming_picamera() as cam:
cam.capture_metadata() cam.capture_metadata()
def _framerate_worker(
self,
duration: float,
output_dir: str,
sample_interval: float = 0.1,
) -> None:
"""Worker thread to measure framerate and save capture metadata."""
try:
os.makedirs(output_dir, exist_ok=True)
timestamp = time.strftime("%Y%m%d_%H%M%S")
log_path = os.path.join(
output_dir,
f"framerate_log_{timestamp}.csv",
)
start_time = time.time()
last_sample_time = start_time
last_sample_frames = 0
frames = 0
with open(log_path, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["timestamp", "frame_count", "instant_fps"])
with self._streaming_picamera() as cam:
while time.time() - start_time < duration:
cam.capture_metadata()
frames += 1
now = time.time()
if now - last_sample_time >= sample_interval:
interval = now - last_sample_time
instant_fps = (frames - last_sample_frames) / interval
writer.writerow([now, frames, instant_fps])
f.flush()
LOGGER.info(f"[Framerate test] FPS ~ {instant_fps:.2f}")
last_sample_time = now
last_sample_frames = frames
total_time = time.time() - start_time
avg_fps = frames / total_time
LOGGER.info(
f"[Framerate test complete] "
f"Frames={frames}, Avg FPS={avg_fps:.2f}"
)
except Exception:
LOGGER.exception("Framerate worker crashed")
@lt.action
def record_framerate_async(
self,
duration: float = 5.0,
output_dir: Optional[str] = "framerate",
) -> str:
"""Start a background framerate recording session."""
if output_dir is None:
output_dir = tempfile.mkdtemp(prefix="framerate_test_")
thread = threading.Thread(
target=self._framerate_worker,
args=(duration, output_dir),
daemon=True,
)
thread.start()
LOGGER.info(f"Started framerate test for {duration}s -> {output_dir}")
return output_dir
@contextmanager @contextmanager
def _switch_to_still_capture_mode(self) -> Iterator[Picamera2]: def _switch_to_still_capture_mode(self) -> Iterator[Picamera2]:
"""Get the picamera lock, pause stream and switch into still capture config. """Get the picamera lock, pause stream and switch into still capture config.