Skip to content

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

  1. The slice normal is cross(row, col); it points up the stack.
  2. Order slices by projection of ImagePositionPatient onto the slice normal, never by InstanceNumber or filename.
  3. Once ordered, compute the gaps between consecutive projections: equal gaps ⇒ regular volume; unequal gaps ⇒ irregular spacing to handle deliberately.
  4. SpacingBetweenSlices is 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.