Repository navigation
Add ndr.reader.video: a reader for mp4/avi/mov video - #140
Conversation
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
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #140 +/- ##
==========================================
+ Coverage 54.26% 54.88% +0.61%
==========================================
Files 163 164 +1
Lines 9420 9598 +178
==========================================
+ Hits 5112 5268 +156
- Misses 4308 4330 +22 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FxKLA8Tbf9cpvDe7fuVeKf
Builds TestHaleyRecordings' fixture, replaces camera 2's placeholder 2022-02-04_12-10-51_2.mp4 with a real 5-frame video of known content, runs import_V2's write path for concentration_0001, then rebuilds camera 2 with daqsystem_load and checks numframes and readframes (size, class, per-channel means within a codec tolerance). Fails, rather than filters, when ndr.reader.video is absent. test-import-v2.yml: a TEMPORARY step puts NDR-matlab's claude/native-video-reader branch first on the path, until VH-Lab/NDR-matlab#140 merges. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FxKLA8Tbf9cpvDe7fuVeKf
t0_t1 went through frametimes, which decodes every frame once to measure its time. NDI asks every epoch for t0_t1 when it lists a system's epochs, so listing a camera decoded every video on it. t0_t1 now uses ndr.reader.video.endtimes: the first frame's time is read before frame 1 is decoded, and the last by seeking a few frame periods before Duration and decoding to the end (widening the margin if the seek lands past the last frame; the full scan only if there is no usable FrameRate or Duration). Both are measured as scanframetimes measures them (CurrentTime before readFrame), so they agree with frametimes, and variable-frame-rate files stay exact. frametimes still scans, only when asked for every frame's time. Tests: t0_t1 on a file neither cache has seen agrees with the full scan taken after, for the RGB and greyscale videos, and a 60-frame video's end is reached by seeking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FxKLA8Tbf9cpvDe7fuVeKf
…unused Code Analyzer flagged the argument after t0_t1 stopped passing it on to frametimes (code scanning alert 4138). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FxKLA8Tbf9cpvDe7fuVeKf
The repository's AGENTS.md asks for MATLAB arguments blocks (validation and tab-completion); the reader had one, on readframes. Every method and static helper now declares its inputs: the reader object, EPOCHSTREAMS as text (char, string or a cell of char vectors), EPOCH_SELECT a positive integer defaulting to 1, FRAMEIND a double index (range-checked as before, so a bad index still raises ndr:reader:video:badframeind), a VideoReader where one is taken. t0_t1 names its EPOCH_SELECT again (an ignored input cannot be declared) and says it is unused. The two test helpers declare theirs too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FxKLA8Tbf9cpvDe7fuVeKf
Code Analyzer (code scanning, 15 alerts on 1929281) flags an input declared in an arguments block and not used after it. Each is the object, the epoch's files or EPOCH_SELECT on a method the reader interface fixes the signature of (an epoch is one file, so EPOCH_SELECT is never needed), so each declaration carries %#ok<INUSA>, as DID-matlab's resolveDatasetEntities does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FxKLA8Tbf9cpvDe7fuVeKf
|
This raises an interesting issue. The videos would be unplayable on python if we make this mean "the MATLAB VideoReader". Maybe we want to make it an 'mp4', 'avi', and 'mov' reader generically, so that that both Matlab and python can play it? On the Matlab side, VideoReader can be the implementation, and on the python side, it can use whatever libraries are appropriate. How does that sound? |
|
I think I’m a bit confused. Are you saying it’s the matlab only video
reader because of the PR title? Or the code itself? Your description sounds
exactly how I was imagining it. The PR title ended up that way because V1
was using a Nansen-style imagestack reader and I wanted to use the native
matlab video reader instead for V2 videos.
*Jess Haley, PhD*
***@***.***
Scientific Programmer and Data Consultant
Waltham Data Science LLC
Makers of NDI Cloud <https://ndi-cloud.com/>
…On Tue, Oct 20, 2026 at 4:52 AM Steve Van Hooser ***@***.***> wrote:
*stevevanhooser* left a comment (VH-Lab/NDR-matlab#140)
<#140 (comment)>
This raises an interesting issue. The videos would be unplayable on python
if we make this mean "the MATLAB VideoReader". Maybe we want to make it an
'mp4', 'avi', and 'mov' reader generically, so that that both Matlab and
python can play it? On the Matlab side, VideoReader can be the
implementation, and on the python side, it can use whatever libraries are
appropriate. How does that sound?
—
Reply to this email directly, view it on GitHub
<#140?email_source=notifications&email_token=AJDALDLFIEXB7HWS5LLFW5L5U5GYNA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTMMRZGAZDCNRYGQZKM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#issuecomment-6290216842>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AJDALDIBMUOY6NXUBTUBQKT5U5GYNAVCNFSNUABFKJSXA33TNF2G64TZHMZTAMZYGEYDGOBRHNEXG43VMU5TKNRWHAYDOOJZHE42C5QC>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/AJDALDJABZM7HPN6HLXB4PD5U5GYNA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTMMRZGAZDCNRYGQZKM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KUZTPN52GK4S7NFXXG>
and Android
<https://github.com/notifications/mobile/android/AJDALDN4IXQR4EJCZOE74HT5U5GYNA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTMMRZGAZDCNRYGQZKM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2K4ZTPN52GK4S7MFXGI4TPNFSA>.
Download it today!
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
|
OK I didn't look carefully, I just read the header. So great, sounds like it is fine, thanks! I'll merge |
|
Agreed, and the code already works that way: the reader type stored in the data is
No change to how frames are read; the tests are unchanged. Generated by Claude Code |
|
Correction to my comment above: this PR was merged at 794a0e8 a minute before 488417d was pushed, so the wording change and the removal of the Generated by Claude Code |
Summary
This PR adds
ndr.reader.video, an NDR reader for.mp4,.aviand.movfiles. Thevideoreader type is defined by the file format, not by a library. It reads the frames of a video container (MPEG-4, AVI, QuickTime) and the time stored for each frame, so any language can implement it. This MATLAB implementation uses MATLAB's built-inVideoReaderand needs no toolbox. A Python implementation can use any library that reads the same containers and their frame timestamps (e.g. PyAV).Until now the only NDR reader that could open video was
ndr.reader.imagestack, which depends on NANSEN. With this reader, NDI can read video throughndi.daq.reader.image.ndr('video')without extra toolboxes. The NDI V2 Haley import records'video'as its cameras' reader.+ndr/+reader/video.m(subclass ofndr.reader.base). It has the same frame API asimagestack/tiffstack:numframes,framesize,dimensionorder,datatype,frametimes,readframes(includingSelectC/SelectZ),epochclock,t0_t1,getchannelsepochandmetadata.[Y X C Z T]in'YXCZT'order, with Z=1. RGB video has C=3 and greyscale video has C=1. Both C and the datatype are read from the first decoded frame.epochprobemap.ndi) are ignored. Anything other than exactly one video file is an error.dev_local_timeclock. They are read, not computed from the frame rate. In MATLAB,frametimesdecodes the file once and recordsVideoReader'sCurrentTimebefore eachreadFrame, then caches the result per file for the session. This matters on real data: a one-hour lab video (10,796 frames) drifts up to 1.33 s from(k-1)/FrameRate.t0_t1reads only a few frames (ndr.reader.video.endtimes). It reads the first frame's time before decoding frame 1. For the last frame it seeks a few frame periods beforeDurationand decodes to the end, widening the margin if the seek overshoots. It falls back to the full scan only when the file reports no usableFrameRateorDuration. NDI asks every epoch fort0_t1when it lists a system's epochs, so a full decode there would decode every video of the system. On the one-hour video this takes 0.25 s, against 10.2 s for the full scan, with identical start and end times.numframesusesNumFrames, and counts frames by decoding only when the container reports no count.readframesreads withread(v, idx), one call per run of consecutive indices.metadata:israster=false; onlyframe_period = 1/FrameRateis filled in.resource/ndr_reader_types.jsonasvideo,mp4,aviandmov. These are format names only, so the stored reader type means the same thing in MATLAB and Python.main, including smartspim: prefer SmartSPIM's stitched All_Channels volume #142. The merge was clean.Tests
tools/tests/+ndr/+unittest/+reader/TestVideo.mwrites synthetic videos: 12-frame 24x32 at 4 fps, plus a 60-frame one. RGB video is written as MPEG-4 where the platform supports it and as Motion JPEG AVI otherwise; greyscale is written as lossless Grayscale AVI. The tests cover:known_readerslisting, the size, dimension order and datatype;t0_t1;SelectC, and greyscale content round-tripping exactly;metadata;t0_t1on a file neither cache has seen, which must agree with the full scan, for both RGB and greyscale;endtimeson a 60-frame video, which reaches the end by seeking and must agree with the full scan.On real data, on macOS with the Haley lab's StreamPix
.mp4files:NumFramesequals the number of frames decoded;uint8.In NDI,
TestHaleyReadVideo(VH-Lab/NDI-matlab#1021) reads a frame throughdaqsystem_load→readframesusing this branch.Test plan
Test code(run_tests.yml) green on 6dfeb51: 409 passed, 0 failed. The 3 incompletes are the CED sonpipe tests, which skip without that tool.Test NDR SymmetryandTest .smrxgreen on 6dfeb51.videoreaderalias only; no code change).After merge
NDI's
test-import-v2.ymlchecks out this branch in a step marked temporary. That step can be removed once this PR is merged.🤖 Generated with Claude Code
https://claude.ai/code/session_01FxKLA8Tbf9cpvDe7fuVeKf
Generated by Claude Code