Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/PORT_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ applying the **Mirror Rule**:
| `ndr.reader.tiffstack` | `ndr.reader.tiffstack` | `ndr_reader_tiffstack` |
| `ndr.reader.prairieview` | `ndr.reader.prairieview` | `ndr_reader_prairieview` |
| `ndr.reader.vld` | `ndr.reader.vld` | `ndr_reader_vld` |
| `ndr.reader.video` | `ndr.reader.video` | `ndr_reader_video` |
| `ndr.reader.somecompany_someformat` | `ndr.reader.somecompany_someformat` | `ndr_reader_somecompany__someformat` |

## Reader Status
Expand Down Expand Up @@ -111,6 +112,7 @@ The top-level `ndr_reader` class (`reader_wrapper.py`) wraps any format-specific
| `neo` | ndr\_reader\_ced\_\_smr, ndr\_reader\_neo | Read CED SMR/SON and Blackrock files |
| `pyabf` | ndr\_reader\_axon\_\_abf | Read Axon Binary Format files |
| `tifffile` | ndr\_reader\_tiffstack, ndr\_reader\_prairieview | Read TIFF image stacks |
| `av` (PyAV) | ndr\_reader\_video | Decode video files through its bundled FFmpeg |
| `numpy` | All readers | Array operations |

## Imaging Support and Remaining Gaps
Expand Down Expand Up @@ -151,6 +153,7 @@ Python callers that need the numpy dtype use
|---|---|---|
| `ndr.reader.tiffstack` | **Ported** | Native multipage-TIFF reader, built on `tifffile`. Handles single multipage files, directory epochs, anchor/marker files, and `frametimes.txt` sidecars. |
| `ndr.reader.prairieview` | **Ported** | Native legacy Prairie View reader. Groups channels from `Cycle`/`Ch` file-name markers, reads real per-frame timestamps from the config, and reports raster-scan metadata. Reads legacy `.pcf`, modern PVScan XML, and legacy MM-era XML configs. No NANSEN dependency. |
| `ndr.reader.video` | **Ported** | Native video reader (`.mp4`/`.avi`/`.mov`/...), built on PyAV where MATLAB uses `VideoReader`. One video file per epoch (companion files ignored), measured per-frame presentation times (correct for variable frame rate), `t0_t1` without decoding the whole file, and seek-based random access. |
| `ndr.reader.imagestack` | Not ported | Thin wrapper over NANSEN's `nansen.stack.ImageStack` — see the dependency note below. |

### Other readers and formats
Expand All @@ -167,7 +170,7 @@ Python callers that need the numpy dtype use
### Reader-types resource divergence

`resource/ndr_reader_types.json` still differs between the two repositories.
MATLAB registers 14 readers; Python now registers 13. Python is missing only
MATLAB registers 15 readers; Python now registers 14. Python is missing only
`imagestack` (the NANSEN-backed reader). The two files also still disagree on
some type aliases: MATLAB maps `smrx`/`ced-smrx` (the sonpipe-backed 64-bit
CED path, which Python does not implement) and `WMHS`.
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ dev = [
"tifffile>=2021.7.2",
# LZW codec for SmartSPIM All_Channels slices (tests write LZW TIFFs).
"imagecodecs>=2021.11.20",
# ndr.reader.video decodes (and its tests write) video through PyAV.
"av>=12.0",
]
formats = [
# ndr.format.stereoseq.readGEF reads Stereo-seq .gef files, which are HDF5.
Expand All @@ -47,6 +49,8 @@ formats = [
# hand-roll chunk reading or Blosc/Zstd decoding.
"zarr>=2.13",
"numcodecs>=0.10",
# ndr.reader.video reads video through PyAV, whose wheels bundle FFmpeg.
"av>=12.0",
]
# CED Spike2 support runs out of process through the sonpipe CLI, which pulls
# CED's sonpy binding as its own dependency. Not in the base install because
Expand Down
6 changes: 4 additions & 2 deletions src/ndr/example_data/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,9 @@ Keep in mind Neo designates any channel type to one of the: `'analog_input'`, `'

---

## Synthetic fixtures for the vld and image readers
## Synthetic fixtures for the vld, image and video readers

These four fixtures are **generated**, not recorded: they are small,
These fixtures are **generated**, not recorded: they are small,
deterministic files created by `tools/make_example_data.py` in NDR-python and
committed byte-identically to both repositories, so the cross-language
symmetry tests read exactly the same bytes on both sides.
Expand All @@ -376,6 +376,8 @@ symmetry tests read exactly the same bytes on both sides.
| `example_movie.tif` | `ndr.reader.tiffstack` | 5-page uncompressed baseline TIFF, 16x12 `uint16`. Pixel value is `page*1000 + row*10 + col`, so every page and pixel is distinguishable and a transposed or mis-ordered read shows up immediately. |
| `example_movie_frametimes.txt` | `ndr.reader.tiffstack` | One frame time per page (0, 0.05, ... 0.2 s), the sidecar that makes the epoch clocked rather than `no_time`. |
| `prairieview/` | `ndr.reader.prairieview` | 2 channels x 4 frames of 8x6 `uint16` named `example_Cycle001_Ch<c>_<frame>.tif`, plus a legacy `example_Main.pcf`. Pixel value is `chan*1000 + frame*100 + row*10 + col`. |
| `example_video.avi` | `ndr.reader.video` | 6-frame 12x16 uncompressed 24-bit RGB AVI at 10 frames/s, frames stored bottom-up (positive `biHeight`) as MATLAB's `VideoWriter` writes them. R = `20*frame + col`, G = `10*row + frame`, B = `200 - 10*frame - col` (frame, row, col 0-based), so channel order, orientation and frame order are all checked. Uncompressed so MATLAB's `VideoReader` reads it on every platform and both ports must agree pixel for pixel. |
| `example_video_grey.avi` | `ndr.reader.video` | The same layout as 8-bit greyscale with a 256-entry grey palette (MATLAB's 'Grayscale AVI' storage), read as C = 1. Pixel value is `30*frame + 4*row + col`. |

The Prairie View config sets `Frame period (us)` = 16000 and
`ScanLine period (us)` = 1500 — deliberately *not* frame period divided by
Expand Down
Binary file added src/ndr/example_data/example_video.avi
Binary file not shown.
Binary file added src/ndr/example_data/example_video_grey.avi
Binary file not shown.
119 changes: 119 additions & 0 deletions src/ndr/reader/ndr_matlab_python_bridge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1502,6 +1502,125 @@ classes:
- name: t0_t1
- name: metadata

- name: video
type: class
matlab_path: "+ndr/+reader/video.m"
status: regular_port
python_path: "ndr/reader/video.py"
matlab_last_sync_hash: "d2ebe98"
python_class: "ndr_reader_video"
inherits: base
decision_log: >-
Ported 2026-10-20 from NDR-matlab #140; synced to d2ebe98 (NDR-matlab
#143), which defines the 'video' type by the file format rather than
by VideoReader and drops the MATLAB-specific 'videoreader' alias, so
the Python registry drops it too (no change to how frames are read).
Native video reader; sibling of tiffstack, implements the frame API
only. MATLAB uses its built-in
VideoReader; the Python port uses PyAV (`av`, the FFmpeg binding; an
optional dependency, imported lazily). PyAV wheels bundle FFmpeg, so
MPEG-4/QuickTime need no platform media framework as they do for
MATLAB on Linux. Frame times are FFmpeg presentation timestamps
relative to the stream start, the quantity MATLAB reads as
CurrentTime before each readFrame; both are measured per decoded
frame and so are correct for variable-frame-rate files. C follows
VideoReader: grey pixel formats and palettised video with a grey
palette (MATLAB's 'Grayscale AVI') give C=1, everything else RGB;
above 8 bits per sample both give uint16. readframes seeks by pts,
located from the demuxed packet timestamps (no decode) via the
Python-only _framepts helper, where MATLAB's read(v, idx) does the
seek internally. Session caches are module-level dicts where MATLAB
uses persistent containers.Map, keyed the same way (path, size,
modification time). Error identifiers map to ValueError
(novideofile, multiplevideofiles), IndexError (badframeind) and
FileNotFoundError (nofile).
methods:
- name: videofile
input_arguments:
- name: epochstreams
type_python: "list[str] | str"
output_arguments:
- name: filename
type_python: "str"
- name: videoobject
input_arguments:
- name: epochstreams
type_python: "list[str] | str"
output_arguments:
- name: v
type_matlab: "VideoReader"
type_python: "av.container.InputContainer"
decision_log: >-
The PyAV container is the counterpart of the VideoReader
object. The caller owns it and closes it (context manager).
- name: numframes
- name: framesize
- name: dimensionorder
- name: datatype
- name: frametimes
- name: readframes
- name: epochclock
- name: t0_t1
- name: getchannelsepoch
- name: metadata
static_methods:
- name: videoextensions
input_arguments: []
output_arguments:
- name: exts
type_matlab: "cell array of char"
type_python: "list[str]"
- name: videonumframes
input_arguments:
- name: v
type_matlab: "VideoReader"
type_python: "av.container.InputContainer"
output_arguments:
- name: n
type_python: "int | float"
decision_log: "NaN (a float) when the container reports no frame count."
- name: channelsanddatatype
input_arguments:
- name: v
type_matlab: "VideoReader"
type_python: "av.container.InputContainer"
output_arguments:
- name: C
type_python: "int"
- name: dt
type_matlab: "char"
type_python: "str"
decision_log: "The MATLAB class name ('uint8'/'uint16'), as for datatype."
- name: checkframeind
input_arguments:
- name: frameind
type_python: "list[int] | numpy.ndarray"
decision_log: "Semantic Parity: frame indices are 1-based."
- name: n
type_python: "int"
output_arguments: []
- name: endtimes
input_arguments:
- name: folder
type_python: "str"
- name: name
type_python: "str"
output_arguments:
- name: t0t1
type_matlab: "1x2 double"
type_python: "list[float]"
- name: scanframetimes
input_arguments:
- name: folder
type_python: "str"
- name: name
type_python: "str"
output_arguments:
- name: t
type_matlab: "Nx1 double"
type_python: "numpy.ndarray"
decision_log: "1-D array, as frametimes returns throughout the Python port."

- name: imagestack
type: class
matlab_path: "+ndr/+reader/imagestack.m"
Expand Down
Loading
Loading