3D images made of voxels
Read these first if they are new to you
- Vectors: arrows made of numbers Basic
- Distance between two points Basic
- Arrays in NumPy Intermediate
The idea in one paragraph
A 3D image is a stack of flat images. It forms a grid of tiny boxes called voxels, and each voxel holds one number, such as a brightness. A voxel is named by its three grid indices. Its size says how much real space it covers, and that size can differ between axes: a voxel might be 8 nm wide but 40 nm deep. So a count of voxel steps is not a length. To measure real distances, multiply each index step by the voxel size along that axis first.
A 3D image is a stack of 2D images
A photo on a screen is a grid of small squares called pixels. Each pixel holds a number for its brightness, for example from 0 (black) to 255 (white). Now take many photos of thin slices of an object, one under the next, and stack them. The stack is a 3D image, also called a volume.
Each small box in the stack is a voxel, short for "volume pixel." A voxel has three whole-number indices that say where it sits in the grid: column x, row y, and slice z. It holds one value, the brightness of the material inside that box.
A voxel's size can differ between axes
The voxel size gives the real width, height and depth of one voxel. Microscope images of cells use the nanometer (nm), one billionth of a meter. A thousand nanometers make one micrometer (µm), and a human hair is about 70 µm wide.
When a voxel has the same size on all three axes, the image is isotropic. Often it is not. Some electron microscopes see each slice at 8 nm per pixel, but the slices themselves are cut 40 nm thick. Then each voxel is 8 × 8 × 40 nm, and the image is anisotropic: its voxel size depends on the direction.
The grid hides this. In the array, a step of one voxel along z looks just like a step of one voxel along x. In real space, the z step is 5 times longer.
Distances need physical units
To find the real position of a voxel, multiply each index by the voxel size along that axis. Then use the usual distance formula (see distance between two points) on those positions. If you skip the multiplication, you get a count of voxel steps, which mixes 8 nm steps with 40 nm steps.
Voxel A has indices (10, 20, 5). Voxel B has indices (13, 24, 8).
- The steps between them are (13 − 10, 24 − 20, 8 − 5) = (3, 4, 3) voxels.
- Counting in voxel steps gives √(3² + 4² + 3²) = √34 ≈ 5.83. This number has no real unit.
- In nanometers, the steps are (3 × 8, 4 × 8, 3 × 40) = (24, 32, 120) nm.
- The distance is √(24² + 32² + 120²) = √(576 + 1,024 + 14,400) = √16,000 ≈ 126.5 nm.
In voxels, the z step is no bigger than the others. In nanometers it is 120 nm, most of the 126.5 nm distance.
Skipping the conversion can even change which point is nearer. Before you use the figure, predict: point C is 4 voxels from A along x, and point D is 1 voxel from A along z. Which is nearer?
The two lengths match at a depth of 32 nm, because C is 4 × 8 = 32 nm from A. At the real depth of 40 nm, C is nearer. Any program that finds nearest points, measures lengths or compares shapes must first turn voxel indices into physical units.
Axis order. Many programs store a volume as array[z, y, x], with z first.
Others use [x, y, z]. Check the order before you multiply by a voxel size, or you will scale the
wrong axis.
Where a voxel sits. A voxel covers a box, but a single position must stand for it. Some tools use the box's corner and some its center, which differ by half a voxel. A volume may also start at an offset from the origin. Real positions are index × voxel size, plus that offset.
In NumPy, scale the index steps, then measure
voxels.py
import numpy as np
voxel_size = np.array([8.0, 8.0, 40.0]) # nm per voxel along x, y, z
a = np.array([10, 20, 5]) # voxel indices (x, y, z)
b = np.array([13, 24, 8])
print(round(np.linalg.norm(b - a), 2)) # voxel steps: not a real length
print(round(np.linalg.norm((b - a) * voxel_size), 2)) # nanometers
c = np.array([14, 20, 5]) # 4 voxels from a along x
d = np.array([10, 20, 6]) # 1 voxel from a along z
for p in (c, d):
print(np.linalg.norm(p - a), np.linalg.norm((p - a) * voxel_size))
5.83
126.49
4.0 32.0
1.0 40.0
Lines 10 and 11 set up C and D. In voxel steps D is nearer (1 against 4). In nanometers C is nearer (32 against 40).
Check yourself
-
What is the volume of one 8 × 8 × 40 nm voxel? How many such voxels fit along 1 µm in x, and how many along 1 µm in z?
Answer
The volume is 8 × 8 × 40 = 2,560 nm³. One µm is 1,000 nm, so 1,000 ÷ 8 = 125 voxels fit along x, and 1,000 ÷ 40 = 25 along z.
-
With voxels of 8 × 8 × 40 nm, voxel P has indices (0, 0, 0) and voxel Q has indices (5, 0, 1). How far apart are they in nanometers?
Answer
The steps are (5, 0, 1) voxels, which is (40, 0, 40) nm. The distance is √(40² + 40²) = √3,200 ≈ 56.6 nm. In voxel steps it would wrongly be √26 ≈ 5.1.
-
A program stores a volume as
array[z, y, x]and uses the voxel size (8, 8, 40) in that same order. What goes wrong?Answer
It multiplies the z steps by 8 and the x steps by 40. Depth looks 5 times too short and width 5 times too long. The voxel size must be listed in the same axis order as the array, here (40, 8, 8).
Go further
Optional background. This unit covers what you need here.
-
Loads a real microscope volume whose voxels are deeper than wide; the opening section shows how to record and use that "spacing."
-
A short definition, with the other uses of voxels in medical scans and games.
Where this shows up
Units that build on this one
- Segmentation: a label for every voxel Intermediate