Order slices without trusting filenames
Tonight · ~25 min · read + a calculation · energy: medium · setup: optional Python
You have 320 DICOM files for one series. They are not numbered in any trustworthy
way, and InstanceNumber has gaps. Which order do you stack them in to get a
correct 3D volume? This lesson gives the one robust answer: order by geometry,
not by labels. It is the single most important vector in the chapter.
The anchor: why InstanceNumber and filenames lie
InstanceNumber (0020,0013) is an arbitrary per-series counter assigned by the
scanner or a forge. It is usually ascending, but it is not guaranteed to be
contiguous, monotonic in anatomical position, or stable across re-exports. Two
different series may reuse the same numbers. Filenames are even less reliable — a
re-export, a download, or an OS sort (slice_1, slice_10, slice_2, …) will reorder
them arbitrarily.
If you stack by InstanceNumber or filename and get lucky, the volume looks fine.
If you get unlucky, slices are subtly misordered and every z-dependent measurement
— volume, surface, texture — is wrong, silently. The fix is to stop trusting labels
and compute the order from physical position.
The slice normal
The third axis of a CT volume is the slice normal n — the direction in which
slices stack. It is the cross product of the row and column direction cosines:
n = cross([rx, ry, rz], [cx, cy, cz])For an axial series, n points roughly Superior (+z). It is the direction “up the
stack”. Everything in this lesson reduces to projecting slice positions onto n.
Order by projection onto the slice normal
Each slice carries its own ImagePositionPatient p_k — the physical position of
its top-left voxel. Project that position onto the slice normal to get a single
scalar “height” along the stack direction:
t_k = dot(p_k, n)Then sort slices by t_k ascending. This is your anatomically-correct slice
order. It is robust to random filenames, to InstanceNumber gaps, and to slices
that were re-exported, because it depends only on where each slice physically sits.
flowchart LR
S0["slice A<br/>IPP = [..., 40.0]<br/>t = 40.0"]
S1["slice B<br/>IPP = [..., 41.25]<br/>t = 41.25"]
S2["slice C<br/>IPP = [..., 42.5]<br/>t = 42.5"]
S0 --> S1 --> S2
Note["sort by t ascending<br/>= anatomical order"]
This is exactly what TRACE-CT’s
compute_slice_normal() and project_position() implement, and what
load_ct_volume_explicit() uses to build the volume before validating regularity.
Worked example: three slices
Suppose three slices (axial, so n ≈ [0,0,1]) have these positions:
| Slice file | ImagePositionPatient (z) |
t = dot(p, n) |
|---|---|---|
img_002.dcm |
42.5 | 42.5 |
img_001.dcm |
40.0 | 40.0 |
img_003.dcm |
41.25 | 41.25 |
Filenames say 1, 2, 3 but positions are scrambled. Sorting by t gives the true
stack order: img_001 (40.0) → img_003 (41.25) → img_002 (42.5). The spacing
between consecutive slices is the difference of consecutive t values: 1.25 mm and
1.25 mm — a regular stack.
Irregular spacing: when the stack is not a volume
Once you have the sorted t_k, compute the gaps t_{k+1} − t_k. If they are all
equal (within tolerance), the stack is a regular volume and you can treat it as
a 3D array. If they vary, you have irregular spacing — missing slices, variable
thickness, or an interleaved acquisition. Options then:
- resample onto a regular grid (if the physics justifies it — lesson 7);
- or treat the slices as a non-uniform stack and reject voxel-wise 3D operations that assume regularity.
Either way, measure the gaps; do not assume them. SpacingBetweenSlices
(0018,0088) is a tag that claims a value, but the ground truth is the geometry
you just computed. TRACE-CT’s preflight validates regularity explicitly before
declaring a stack usable.
Stop and think — then reveal
A colleague sorts slices by InstanceNumber and the volume looks correct. You sort
by slice-normal projection and it comes out identical. Does that prove
InstanceNumber is a safe ordering in general?
No — it proves it was safe for this series. InstanceNumber happens to match
anatomical order in many cleanly-exported axial series, which is exactly why the
bug is rare and vicious: it works until the day a series is re-exported, comes from
a different vendor, or has a gap, and then it silently misorders slices. Ordering
by geometry is always correct and costs a few lines of code; ordering by
InstanceNumber is a gamble you stop noticing you are taking. Make geometric
ordering your default and you never have to reason about when the gamble is safe.
What to retain
- The slice normal is
cross(row, col); it points up the stack. - Order slices by projection of
ImagePositionPatientonto the slice normal, never byInstanceNumberor filename. - Once ordered, compute the gaps between consecutive projections: equal gaps ⇒ regular volume; unequal gaps ⇒ irregular spacing to handle deliberately.
SpacingBetweenSlicesis a claim; the measured geometric gap is the truth.
Next: now that a CT volume is geometrically sound, when does a segmentation truly
share its space? — and what FrameOfReferenceUID
does and does not prove.