From plane
to pixel

A flat plane cuts through a block of voxels. Every pixel of that plane asks which cube is closest? and copies that cube's brightness. This page plays the answer out one sample at a time, on the five hand-checked examples in docs/algorithm.md.

The idea, in five steps

Think of the volume as a block of small cubes, each holding one brightness number. A plane from the CSV is a flat sheet placed inside — or running past — that block. The renderer turns the sheet into a frame of one pixel per sample. The whole job is the five steps below.

  1. 01Size the frame One row per voxel-length step down the legs, one column per step across the base.
  2. 02Slide the rows down Each row's ends sit on the two legs. No row is wider than the base, and the last row spans it exactly — triangles widen the whole way down; a rectangle keeps every row full width.
  3. 03Place the pixel centres Pixels sit one step apart, symmetric about the row's centre. The row stays centred as its width changes.
  4. 04Find the nearest cube Round the sample point to whole x, y, z indices. An exact half goes to the even index.
  5. 05Copy the value Map the cube's value over its storage type's full range to 0–255. A sample that misses the plane or the volume stays black.

Walk it through

Interactive walkthrough 5 examples · ← → step pixels · ↑ ↓ step rows · space plays

01 · the plane

The plane is drawn flat, as if unfolded. Rows run left → right; the legs run top → bottom. On the row being sampled: the dashed verticals are the walls between cubes along the axis the row travels, each placed dot carries the cube its sample lands on, and the pixel being placed shows its original (pre-rounding) value with the cube that value rounds to.

02 · the frame

One cell per pixel, filled in reading order. The number is the pixel's gray level, 0–255.

03 · the nearest cube rounding one sample point to whole indices

The formulas, with plain names

These are the same formulas as docs/algorithm.md, using the names the code in Ex0.py uses. Coordinates are voxel indices: no origin, spacing, or world-space offset is applied.

leg_length(dist(top_left, bottom_left) + dist(top_right, bottom_right)) / 2the two legs agree within EPS = 0.001
base_lengthdist(bottom_left, bottom_right)
frame_heightmax(1, round(leg_length))one row per voxel-length unit
frame_widthmax(1, round(base_length))one column per voxel-length unit
row_fraction(row_index + 1) / frame_heightone leg step taken before each row
left_edge[row]top_left + row_fraction × (bottom_left − top_left)slides down the left leg
right_edge[row]top_right + row_fraction × (bottom_right − top_right)slides down the right leg
row_centre(left_edge + right_edge) / 2
row_widthdist(left_edge, right_edge)
row_direction(right_edge − left_edge) / row_widtha one-unit step across the row
offset_from_centrecolumn_index + 0.5 − frame_width / 2pixel centres, symmetric about the row centre
sample_pointrow_centre + offset_from_centre × row_direction
voxel_indexrint(sample_point)nearest cube; an exact half goes to the even index
inside_row|offset_from_centre| ≤ row_width / 2plus a tiny tolerance for floating-point error
inside_volume0 ≤ voxel_index < volume.shapea miss stays black; it is never clamped to the edge
gray_levelinside ? round(255 × (value − type_min) / (type_max − type_min)) : 0the type's full range, never the frame's own brightest value

The names used on this page

The CSV letters stay in error messages. Every computed quantity has a plain name instead — the same one the code uses.

namemeaningCSV letter(s)
top_left top_right The plane's two top corners. For a triangle they are the same point, the apex. a1, a2 (triangle: a)
bottom_left bottom_right The two bottom corners. The base runs between them. b, c
leg_length Average of the two leg lengths, top corner to opposite bottom corner.—
base_lengthDistance between the two bottom corners.—
frame_height, frame_width The frame's row and column counts, clamped to at least 1.—
row_index, row_fraction Which row, and how far down the legs it sits.—
left_edge, right_edge, row_centre, row_width, row_direction The row's two ends, its midpoint, its length, and a one-unit step across it.—
column_index, offset_from_centre Which pixel in the row, and its signed distance from the row centre.—
sample_point, voxel_index The pixel's 3D sample point, and that point rounded to whole cube indices.—
volume The 3D input array volume[x][y][z] — the brief's M. Each value is an integer of one fixed bit width (numpy's dtype).M[x][y][z]

From 16-bit values to gray levels

Gray levels only run 0 to 255, so every value is mapped over its storage type's full span — not over the picture's own brightest value. An 8-, 16- or 32-bit unsigned type maps its whole range (0..255, 0..65535, or 0..4294967295) onto 0..255. A signed type maps its minimum..maximum, so int16 takes −32768..32767 onto 0..255 and zero lands at mid-gray.

A dark pixel really is dark in the video. Nothing is stretched to make the frame look brighter, and a sample that misses stays black.

The numbers come from the hand-checked tables in docs/algorithm.md and the byte-identical goldens in tests/golden/. Every time a walk finishes, the page recomputes the frame and compares it to the fixture.