Plane projections of 3D voxel volumes, from NRRD to MP4

One NRRD volume, one CSV plane-list, one MP4 — a frame per plane. Voxels are sampled with a closest-voxel rule, invalid planes fail loudly with an error frame in the video, and a 500³ uint8 volume renders well above the 30 FPS hard bar.

onion.mp4 · 120 frames of a uint8 onion volume

What it does

Everything from the exercise brief, with the engineering around it: typed seams, golden tests, and a reproducible benchmark harness.

INPUT

NRRD volume loader

Reads 8-, 16- and 32-bit unsigned volumes up to 1024³ per axis (v = M[x][y][z]), rendered with a fully vectorized numpy path.

VALIDATE

Plane validation

3-point planes must be isosceles (legs equal from the first point); 4-point planes must be an isosceles trapezoid. Invalid specs raise and produce a visible error frame.

RENDER

Closest-voxel sampling

Each output pixel takes the nearest voxel centre on the plane — deliberately simple, axis-wise fast-path geometry keeps it quick.

ENCODE

MP4 via OpenCV

cv2.VideoWriter with the mp4v codec — pip-installable, no system ffmpeg needed (ADR 0004), canonical 30 fps.

VERIFY

Tested & typed

pytest suite with golden-frame oracles, CLI and validation tests, plus mypy --strict on the whole package.

MEASURE

Benchmark harness

bench.py measures load and render scopes separately, min-of-repeats, with a dedicated encode probe and versioned results files.

Pipeline

One command, one video. Every stage is a seam that is tested in isolation.

INPUT volume.nrrd

3D unsigned voxel array, 8/16/32-bit

INPUT planes.csv

3 or 4 points per plane; validated on load

RENDER project()

Closest-voxel sampling → one 2D frame per plane

OUTPUT out.mp4

One frame per plane; error frames for invalid planes

# the brief's demo contract (SRS FR-1)
$ python Ex0.py assets/nrrd/MRHead_1.nrrd assets/csv/lecturer_example.csv out.mp4

Demo videos

Real outputs of the tool, byte-for-byte as committed in assets/videos/.

lecturer_example.mp4 — the brief's demo: 2 projections + 2 error frames.
head.mp4 — 280 axial slices through MRHead_1.
onion.mp4 — 120 frames of a uint8 onion volume.
Lecturer example frame 1
lecturer example — frame 1 (valid plane)
Lecturer example frame 3
lecturer example — frame 3 (error frame)
Head slice 140
head.mp4 — slice 140 of 280

Full frame-by-frame commentary lives in docs/example-run.md.

Quickstart

Python ≥ 3.12 (numpy 2.5 requires it). Everything is pip-installable.

# clone + set up
$ git clone https://github.com/ofeq-dev/plane-projection.git
$ cd plane-projection
$ python -m venv .venv && . .venv/bin/activate
$ pip install -r requirements.txt

# render a video: volume + plane list → mp4
$ python Ex0.py assets/nrrd/MRHead_1.nrrd assets/csv/lecturer_example.csv out.mp4

# tests, typecheck, benchmark
$ python -m pytest
$ python bench.py assets/nrrd/croissant.nrrd assets/csv/croissant.csv --repeats 3

Benchmarks

Min-of-repeats wall time via time.perf_counter(); throughput = rendered frames ÷ (load + render); encoding reported separately. Full methodology and all 31 runs in benchmarks/results-2026-11-07.md. Machine: Intel i7-4650U (4 cores), Python 3.15.

Input (volume × planes)FramesLoad sRender sFPS (load+render)
croissant.nrrd × croissant.csv (canonical, 500³ uint8)1000.8781.11650.1 ✓ NFR-1 hard bar
onion.nrrd × head.csv2800.0500.596433.5
MRHead_1.nrrd × head.csv2800.0870.650380.2
biscuit_cake.nrrd × biscuit_cake.csv1200.0910.339278.6
swiss_roll.nrrd × biscuit_cake.csv1200.1800.343229.3

Run-to-run drift measured at 5.4% (criterion < 10%). The 100 FPS aspirational target was not met on this machine; the 30 FPS hard bar is met with margin on every comparable run.

Documentation

The full engineering story, from requirements to architecture decisions.