feat(rldb): h264 image storage end-to-end, default on, with format detection - #545
feat(rldb): h264 image storage end-to-end, default on, with format detection#545ElmoPA wants to merge 1 commit into
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
61e9428 to
b724d72
Compare
…tection
Per-frame JPEG codes every frame independently and cannot exploit the temporal
redundancy of 30fps video. Measured on real fold episodes:
JPEG (q75, as shipped) 44.8 KB/frame 8.33 ms/frame decode
h264 crf15 20.2 KB/frame 1.68 ms/frame decode
2.2x smaller 5.0x faster
At equal bytes h264 also wins on PSNR (+2.4 to +2.9 dB in the 4-8 KB range), so
this is not a quality-for-size trade. It is now the DEFAULT; set
EGOVERSE_IMAGE_CODEC=jpeg to opt a run back out.
WRITER (new): one self-contained mp4 per frames_per_chunk frames, stored as the
elements of a VariableLengthBytes array -- not one blob per episode, which would
force a span read to pull the whole episode (~68 MB) to decode any window. Wired
into BOTH write paths: the incremental handle (buffers frames, flushes on chunk
boundaries and at close) and the bulk write() converters use.
READER (new): frame -> chunk resolution before any slice, since a frame-range
read of a chunk-indexed array returns the wrong elements entirely.
Because both encodings now exist across a dataset, the reader DETECTS the format
rather than trusting features[key]["dtype"], escalating only on disagreement:
1. the declared dtype;
2. the element count -- a full-length array is JPEG, a much shorter one is
video. Compared with ">=" because the writer pads past total_frames (a
290-frame episode occupies 300 slots);
3. the magic bytes of element 0, read only to break a tie, and authoritative
when read.
Verified on 72 real episodes across 6 datasets: identical classification and
ZERO payload reads, so the common path costs nothing. Round trip at 450 frames
(a partial tail chunk) over all four write paths: frames come back at the right
indices across chunk seams, worst mean abs error 0.0048.
Also fixes a pre-existing bug this exercised: ZarrWriter.add_frame assigned raw
bytes to a VLenBytes element, which zarr rejects with "Expected bytes, got
numpy.ndarray". Confirmed against stock main with these changes reverted.
add_frames already used the object-array holder; add_frame now does too.
Known gap: egomimic/test_zarr.py validates only dtype=="jpeg" keys, so it
silently skips images on video episodes rather than validating them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012V58H37tmcvgDthELMd5Xk
b724d72 to
2778812
Compare
Claude Code ReviewReview: PR #545 — h264 image storageSummaryIntroduces chunked h.264 storage for image arrays as the new default codec, with format auto-detection on read so JPEG and video episodes coexist. Also fixes a latent Key concerns
Suggestions
Verdict: Request ChangesThe engineering is solid and the writeup is excellent, but flipping the default codec while Reviewed by Claude · Review workflow |

Per-frame JPEG codes every frame independently and cannot exploit the temporal
redundancy of 30fps video. Measured on real fold episodes:
At equal bytes h264 also wins on PSNR (+2.4 to +2.9 dB in the 4-8 KB range), so
this is not a quality-for-size trade. It is now the DEFAULT; set
EGOVERSE_IMAGE_CODEC=jpeg to opt a run back out.
WRITER (new): one self-contained mp4 per frames_per_chunk frames, stored as the
elements of a VariableLengthBytes array -- not one blob per episode, which would
force a span read to pull the whole episode (~68 MB) to decode any window. Wired
into BOTH write paths: the incremental handle (buffers frames, flushes on chunk
boundaries and at close) and the bulk write() converters use.
READER (new): frame -> chunk resolution before any slice, since a frame-range
read of a chunk-indexed array returns the wrong elements entirely.
Because both encodings now exist across a dataset, the reader DETECTS the format
rather than trusting features[key]["dtype"], escalating only on disagreement:
video. Compared with ">=" because the writer pads past total_frames (a
290-frame episode occupies 300 slots);
when read.
Verified on 72 real episodes across 6 datasets: identical classification and
ZERO payload reads, so the common path costs nothing. Round trip at 450 frames
(a partial tail chunk) over all four write paths: frames come back at the right
indices across chunk seams, worst mean abs error 0.0048.
Also fixes a pre-existing bug this exercised: ZarrWriter.add_frame assigned raw
bytes to a VLenBytes element, which zarr rejects with "Expected bytes, got
numpy.ndarray". Confirmed against stock main with these changes reverted.
add_frames already used the object-array holder; add_frame now does too.
Known gap: egomimic/test_zarr.py validates only dtype=="jpeg" keys, so it
silently skips images on video episodes rather than validating them.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_012V58H37tmcvgDthELMd5Xk