Skip to content

Add ndr.reader.video: native video reader using only MATLAB's VideoReader - #140

Draft
jesshaley wants to merge 1 commit into
mainfrom
claude/native-video-reader
Draft

jesshaley wants to merge 1 commit into
mainfrom
claude/native-video-reader

Conversation

@jesshaley

Copy link
Copy Markdown
Collaborator

Summary

Until now, the only NDR reader that could open video (.mp4/.avi/.mov) was ndr.reader.imagestack, and it depends on NANSEN. This PR adds ndr.reader.video, which reads video with MATLAB's built-in VideoReader and nothing else. NDI can then read video through ndi.daq.reader.image.ndr('video') with no extra toolboxes.

  • New reader +ndr/+reader/video.m (subclass of ndr.reader.base). It has the same frame API as imagestack/tiffstack: numframes, framesize, dimensionorder, datatype, frametimes, readframes (including SelectC/SelectZ), epochclock, t0_t1, getchannelsepoch and metadata.
    • Dimension model: frames are [Y X C Z T] in 'YXCZT' order, with Z=1. RGB video gives C=3 and greyscale gives C=1. Both C and the datatype are read from the first decoded frame, so they always match what readframes returns.
    • Epoch: one video file. Directories are expanded and non-video companion files (e.g. epochprobemap.ndi) are ignored. The reader raises an error unless exactly one video file remains.
    • Clock and times: the clock is always dev_local_time. frametimes gives seconds from the start of the file, using VideoReader's CurrentTime recorded before each readFrame. This is correct for variable-frame-rate files, where (k-1)/FrameRate would drift. The scan decodes every frame once, so its result is cached for the session, keyed by path, size and modification date. t0_t1 is [first frame time, last frame time], the same convention as tiffstack and imagestack.
    • numframes: uses NumFrames, or counts frames by decoding if the container does not report a count.
    • readframes: random access through read(v, idx), with one read call per run of consecutive indices.
    • metadata: israster=false; only frame_period = 1/FrameRate is filled in.
  • Registration: added to resource/ndr_reader_types.json with types video, videoreader, mp4, avi, mov.
  • Tests: tools/tests/+ndr/+unittest/+reader/TestVideo.m writes 12-frame 24x32 synthetic videos at 4 fps.
    • The RGB video is written as MPEG-4, falling back to Motion JPEG AVI. Because both are lossy, content is checked with per-frame per-channel means.
    • The greyscale video is written as Grayscale AVI, which is lossless, so the C=1 frames are checked exactly.
    • Also covered: the known_readers listing, frame times and spacing, t0_t1, random access with a mixed-order subset, SelectC, companion files being ignored, the errors for zero, multiple or bad indices, and metadata.

Test plan

  • Test code workflow (run_tests.yml) green on this PR

🤖 Generated with Claude Code

https://claude.ai/code/session_01FxKLA8Tbf9cpvDe7fuVeKf


Generated by Claude Code

Reading video (.mp4/.avi/.mov) through NDR previously required NANSEN via
ndr.reader.imagestack. ndr.reader.video uses only MATLAB's built-in
VideoReader and implements the same frame API (numframes, framesize,
dimensionorder, datatype, frametimes, readframes, epochclock, t0_t1,
getchannelsepoch, metadata) with the YXCZT dimension model.

Frame times are VideoReader CurrentTime recorded before each readFrame
(correct for variable frame rate), cached per file. Registered in
ndr_reader_types.json as "video" (aliases videoreader, mp4, avi, mov).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxKLA8Tbf9cpvDe7fuVeKf
Comment thread +ndr/+reader/video.m
%
end % ndr.reader.video.video

function filename = videofile(video_obj, epochstreams)
Comment thread +ndr/+reader/video.m
v = VideoReader(video_obj.videofile(epochstreams));
end % videoobject()

function n = numframes(video_obj, epochstreams, epoch_select)
Comment thread +ndr/+reader/video.m
sz = [v.Height v.Width C 1 T];
end % framesize()

function order = dimensionorder(video_obj, epochstreams, epoch_select)
Comment thread +ndr/+reader/video.m
sz = [v.Height v.Width C 1 T];
end % framesize()

function order = dimensionorder(video_obj, epochstreams, epoch_select)
Comment thread +ndr/+reader/video.m
sz = [v.Height v.Width C 1 T];
end % framesize()

function order = dimensionorder(video_obj, epochstreams, epoch_select)
Comment thread +ndr/+reader/video.m
frames = ndr.reader.base.selectframeCZ(frames, options.SelectC, options.SelectZ);
end % readframes()

function ec = epochclock(video_obj, epochstreams, epoch_select)
Comment thread +ndr/+reader/video.m
end
end % t0_t1()

function channels = getchannelsepoch(video_obj, epochstreams, epoch_select)
Comment thread +ndr/+reader/video.m
end
end % t0_t1()

function channels = getchannelsepoch(video_obj, epochstreams, epoch_select)
Comment thread +ndr/+reader/video.m
end
end % t0_t1()

function channels = getchannelsepoch(video_obj, epochstreams, epoch_select)
Comment thread +ndr/+reader/video.m
channels(1).time_channel = [];
end % getchannelsepoch()

function m = metadata(video_obj, epochstreams, epoch_select)
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Test Results

378 tests  +12   375 ✅ +12   38s ⏱️ +2s
 50 suites + 1     3 💤 ± 0 
  1 files   ± 0     0 ❌ ± 0 

Results for commit 383ece2. ± Comparison against base commit 95b7e2d.

@codecov

codecov Bot commented Oct 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.44068% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.71%. Comparing base (95b7e2d) to head (383ece2).

Files with missing lines Patch % Lines
+ndr/+reader/video.m 86.44% 16 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #140      +/-   ##
==========================================
+ Coverage   53.26%   53.71%   +0.45%     
==========================================
  Files         160      161       +1     
  Lines        9190     9308     +118     
==========================================
+ Hits         4895     5000     +105     
- Misses       4295     4308      +13     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This branch has not been deployed

No deployments
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

Successfully merging this pull request may close these issues.

3 participants