perf(cv): cache last-encoded JPEG per frame in healthd's camera snapshot#61
Merged
Conversation
The wall page's live-camera tiles poll /api/v1/cameras/{name}/snapshot
every ~1s per camera (plus admin panel + calibration wizard when open),
and healthd re-ran cv2.imencode on every request against the same
_latest_frame — a 6ms JPEG encode of a 720p BGR frame per hit, per
viewer, per poll.
YOLOPoseSource now increments a monotonic _frame_id whenever a frame
lands and exposes a latest_jpeg(quality) that caches the encoded bytes
keyed on (frame_id, quality). The capture loop's finally-block drops
the cache when the camera disconnects so /snapshot 404s instead of
serving stale JPEG from a dead stream.
Benchmark on a 720p random frame: 6.1 ms cold encode → 0.6 µs cache
hit (~10 000× speedup). New unit tests cover cache hit, invalidation
on new frame, quality-parameter differentiation, disconnect
invalidation, and JPEG round-trip. 37 pass / 1 skip (was 30).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 tasks
rwlove
added a commit
that referenced
this pull request
Jul 2, 2026
…ve sources) (#64) For live sources (RTSP, HTTP, anything not is_file), _stream_one_capture now runs cap.read() and model.predict() as independent asyncio tasks communicating through a size-1 slot. Previously they were serialised: capture waited for predict, predict waited for capture, so a 15 fps camera + 100 ms/frame model ran end-to-end at ~6 fps. Drop-newest slot semantics: if the reader outpaces the predictor, the older queued frame is discarded and replaced with the newest one. The rep detector only ever needs the most recent frame, so backing up a FIFO would just add latency between what's happening in the gym and what the pipeline sees. Reader keeps publishing _latest_frame and _frame_id (and by extension the JPEG cache from PR #61) on every decode so the wall preview stays fresh even between predictions. File sources still run the original sequential path (renamed to _stream_sequential) — file replay is bounded by the predictor rather than a real-time camera, so double-buffering would race through the file and drop most frames. Shutdown carefully: the outer finally-block signals the reader to stop, drains the slot to unblock any in-flight put(), cancels the reader task, awaits it, and only THEN releases the VideoCapture — so we never release cap while a cap.read() is still on the thread pool. Benchmark (mocked cap + model, wall-clock accurate): - 15 fps camera + 100 ms predict: 5.98 fps → 12.80 fps (2.14x) - Fast reader + 200 ms predict: 4.60 fps → 23.93 fps (5.20x) Also reverts a speculative vectorisation of pose_sequence_to_features that was in my working tree: benchmarking showed the NumPy version was 0.6-0.7x the loop version at realistic T=150-500 clip lengths (per-call overhead dominates), and that function is called once per completed set anyway, not per frame. 5 new async tests cover happy-path yield, drop-newest under a slow predictor, clean shutdown on aiter close (VideoCapture released, no zombie reader), EOF sentinel handling, and fps counting still working. 42 pass / 1 skip (was 37/1). Ruff clean. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wall preview tiles poll
/api/v1/cameras/{name}/snapshotevery ~1s per camera; healthd was runningcv2.imencodeon every request against the same_latest_frame. NowYOLOPoseSourcetracks a monotonic_frame_idandlatest_jpeg(q)caches encoded bytes keyed on(frame_id, quality). Capture-loop disconnect drops the cache so a dead stream 404s instead of serving stale JPEG.Numbers
On a 720p random frame:
Test plan
Tag when ready:
pump-cv-v0.5.2.🤖 Generated with Claude Code