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
4 changes: 2 additions & 2 deletions GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ Seed terms for the plane-projection domain. Refine and expand via `/domain-model
## Terms

- **Matrix (`M`)** — the 3D input array of voxels. Dimensions `sx, sy, sz` each satisfy `1 <= s < 2^10`. Stored as `.nrrd` files; values are unsigned 8-, 16-, or 32-bit integers.
- **Voxel (`v`)** — a single element of `M`, i.e. `v = M[x][y][z]`, always an unsigned integer.
- **Voxel (`v`)** — a single element of `M`, i.e. `v = M[x][y][z]`, an integer value: unsigned 8-, 16-, or 32-bit per the brief's contract; signed integer types (e.g. the corpus's `int16` MRHead) are tolerated input per SRS FR-4.
- **Plane (`p`)** — a flat geometric target defined by 3 or 4 coordinates. A 3-point plane satisfies `|dist(a,b) - dist(a,c)| < 0.001`; a 4-point plane satisfies the three validity rules in `AGENTS.md`. An invalid plane raises an exception.
- **Plane set** — 1+ planes from a `.csv` input file, combined for one output video.
- **Projection** — the mapping of `M`'s voxel values onto a plane `p`; the per-plane result is a 2D frame of voxel values.
- **Volume** — the loaded 3D matrix M as an input file (or the array it parses to); the NRRD-input synonym for Matrix.
- **Closest voxel** — the selection rule: when computing a projection, choose the nearest voxel rather than a more complex sampling solution.
- **Frame** — one rendered plane projection; frames are combined into an output video (`.mp4`).
- **Error frame** — a black video frame with a red banner in a fixed corner giving the CSV row index and the specific reason a plane could not produce a frame (failed validity rule or degenerate geometry). The video continues with the remaining planes; the same line is logged to the CLI.
- **Canonical space** — voxel-index space: all plane coordinates and lookups live in `M[x][y][z]` array coordinates. World-space header fields (`space origin`, `space directions`) are deliberately unused; see ADR 0002 (to be written).
- **Canonical space** — voxel-index space: all plane coordinates and lookups live in `M[x][y][z]` array coordinates. World-space header fields (`space origin`, `space directions`) are deliberately unused; see [ADR 0002](docs/adr/0002-canonical-voxel-index-space.md).
- **Frame** *(refined)* — a 2D image one sample per voxel-length unit: height = leg length in index units (rounded), width = base length (rounded). Apex `a` at top-center, legs running downward to b/c, rows centered on the canvas; no mirroring.
- **Canvas** — the fixed video frame size: the maximum frame width and height across all planes in one CSV, all frames padded top-left with black to fit, since one video requires one fixed frame size.
- **Sample rule** — closest voxel by half-to-even rounding of the sample point's index coordinates.
3 changes: 2 additions & 1 deletion bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import numpy

import Ex0
from plane_validation import parse_planes


Clock = Callable[[], float]
Expand Down Expand Up @@ -85,7 +86,7 @@ def timed_pipeline(
start = clock()
volume = Ex0.load_volume(volume_path)
after_load = clock()
records = Ex0.parse_planes(plane_list_path)
records = parse_planes(plane_list_path)
rendered = Ex0.render_frames(volume, records)
canvas = Ex0.canvas_size(rendered)
after_render = clock()
Expand Down
Binary file added docs/images/example-run/croissant-row1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/testing-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ change to the sampled values shows up immediately, byte-for-byte.

- **Real timing.** `tests/test_bench.py` injects a scripted clock. FPS,
hardware, drift and the NFR-1 verdict belong to `bench.py` and
[`benchmarks/results-2026-11-06.md`](../benchmarks/results-2026-11-06.md);
[`benchmarks/results-2026-11-07.md`](../benchmarks/results-2026-11-07.md);
asserting wall time in the suite would only make it flaky.
- **Encoder-exact pixels.** mp4v is lossy, so video tests assert decoded
properties (frame count, size, fps, presence/absence of content, black
Expand Down
22 changes: 12 additions & 10 deletions site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@ <h2>What it does</h2>
<span class="tag">INPUT</span>
<h3>NRRD volume loader</h3>
<p>Reads 8-, 16- and 32-bit unsigned volumes up to 1024³ per axis
(<code>v = M[x][y][z]</code>), with the geometry cached for fast repeat runs.</p>
(<code>v = M[x][y][z]</code>), rendered with a fully vectorized
numpy path.</p>
</div>
<div class="card">
<span class="tag">VALIDATE</span>
<h3>Plane validation</h3>
<p>3-point planes must be equidistant from a common origin; 4-point planes must form a
proper rectangle. Invalid specs raise and produce a visible error frame.</p>
<p>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.</p>
</div>
<div class="card">
<span class="tag">RENDER</span>
Expand Down Expand Up @@ -187,19 +189,19 @@ <h2>Quickstart</h2>
<h2>Benchmarks</h2>
<p class="section-sub">Min-of-repeats wall time via <code>time.perf_counter()</code>; throughput =
rendered frames ÷ (load + render); encoding reported separately. Full methodology and all
31 runs in <a href="https://github.com/ofeq-dev/plane-projection/blob/master/benchmarks/results-2026-11-06.md">benchmarks/results-2026-11-06.md</a>.
Machine: Intel i7-4650U (4 cores), Python 3.12.</p>
31 runs in <a href="https://github.com/ofeq-dev/plane-projection/blob/master/benchmarks/results-2026-11-07.md">benchmarks/results-2026-11-07.md</a>.
Machine: Intel i7-4650U (4 cores), Python 3.15.</p>
<div class="table-wrap">
<table>
<thead>
<tr><th>Input (volume × planes)</th><th>Frames</th><th>Load s</th><th>Render s</th><th>FPS (load+render)</th></tr>
</thead>
<tbody>
<tr><td>croissant.nrrd × croissant.csv <em>(canonical, 500³ uint8)</em></td><td class="num">100</td><td class="num">1.016</td><td class="num">1.276</td><td class="num"><span class="badge-pass">43.6 ✓ NFR-1 hard bar</span></td></tr>
<tr><td>onion.nrrd × head.csv</td><td class="num">280</td><td class="num">0.061</td><td class="num">0.672</td><td class="num">382.0</td></tr>
<tr><td>MRHead_1.nrrd × head.csv</td><td class="num">280</td><td class="num">0.086</td><td class="num">0.649</td><td class="num">381.3</td></tr>
<tr><td>biscuit_cake.nrrd × biscuit_cake.csv</td><td class="num">120</td><td class="num">0.095</td><td class="num">0.336</td><td class="num">278.3</td></tr>
<tr><td>swiss_roll.nrrd × biscuit_cake.csv</td><td class="num">120</td><td class="num">0.245</td><td class="num">0.381</td><td class="num">191.8</td></tr>
<tr><td>croissant.nrrd × croissant.csv <em>(canonical, 500³ uint8)</em></td><td class="num">100</td><td class="num">0.878</td><td class="num">1.116</td><td class="num"><span class="badge-pass">50.1 ✓ NFR-1 hard bar</span></td></tr>
<tr><td>onion.nrrd × head.csv</td><td class="num">280</td><td class="num">0.050</td><td class="num">0.596</td><td class="num">433.5</td></tr>
<tr><td>MRHead_1.nrrd × head.csv</td><td class="num">280</td><td class="num">0.087</td><td class="num">0.650</td><td class="num">380.2</td></tr>
<tr><td>biscuit_cake.nrrd × biscuit_cake.csv</td><td class="num">120</td><td class="num">0.091</td><td class="num">0.339</td><td class="num">278.6</td></tr>
<tr><td>swiss_roll.nrrd × biscuit_cake.csv</td><td class="num">120</td><td class="num">0.180</td><td class="num">0.343</td><td class="num">229.3</td></tr>
</tbody>
</table>
</div>
Expand Down
Loading