The DICOM row/column trap
You wrote arr[x, y] to index column x, row y of a CT — and the anatomy came
out transposed. Or you read PixelSpacing, assumed the first number was the x
spacing, and your resampling stretched the image the wrong way. Both are the same
bug wearing two disguises, and both are failure modes of the confusion this chapter
opened with: indices are positions in an array, and nothing forces the names of
those index axes to line up across tools. DICOM’s row/column naming crosses itself,
and it crosses again against NumPy and SimpleITK. This section is the one place to
nail the conventions down so the transpose happens on paper instead of in a pipeline.
The same voxel, three naming systems
One physical voxel is described by three different tools using three different index orders. They all mean the same voxel, but they index it differently:
DICOM pixel (i, j) = (column i, row j)SimpleITK index (x, y, z) = (column i, row j, slice k) # NOT NumPy orderNumPy array [k, j, i] = (slice k, row j, column i)# ^ for the array returned by sitk.GetArrayFromImage on a scalar 3D imageNumPy and SimpleITK index the same voxel in opposite orders. That is the bite —
with one scoping rule: NumPy itself assigns no slice/row/column meaning to array
axes. arr[k, j, i] is the order of the scalar 3D array produced by
sitk.GetArrayFromImage(img), a conversion convention — not a property of every
ndarray. (Likewise, DICOM’s i/j are the zero-based column/row indices of the
transmitted image matrix; “row” and “column” there name matrix positions, not
anatomical directions.)
The repeated V is one physical voxel; only its notation changes across DICOM, NumPy and SimpleITK.
The cross-coupling, spelled out
The confusion has two layers. Layer 1 — the names cross. In the geometry of the previous section:
ImageOrientationPatientfirst triplet = row direction = direction of increasing column indexi.PixelSpacingfirst value = row spacing = step when row indexjincreases by 1.
So the row direction cosine pairs with the column spacing (it is the step along
i), and the column direction cosine pairs with the row spacing (the step along
j). The word “row” appears in both but refers to different axes. This is not
pedantry — getting it backwards resamples or registers along the wrong axis. A
PixelSpacing of [0.8, 0.7] makes the point: the step when the column index
i increases by 1 is 0.7 — the second value, the column spacing — because i
increases along a row. Taking the first value instead stretches or compresses the
resample along the wrong axis, a silent error that looks fine until you overlay a
mask.
Layer 2 — the index orders cross between libraries. A DICOM pixel at
(i = column, j = row) lives, after sitk.GetArrayFromImage(img) converts a
scalar 3D image, in a NumPy array (slices-first, image order) as
numpy[k, j, i], i.e. numpy[slice, row, column]. So arr[0] is slice 0, and
within a slice arr[k, j, i] indexes row j, column i — the transpose of the
naive arr[x, y].
DICOM: pixel (i=col, j=row) | same voxel +--> NumPy: arr[k, j, i] = slice, row, column | +--> SimpleITK: GetPixel(x=i, y=j, z=k) = column, row, slice
NumPy and SimpleITK: opposite index orderWhen in doubt, hold this in your head:
DICOM
iis column,jis row;PixelSpacingis[row, col]; NumPy is[k, j, i]— for the scalar 3D arrayGetArrayFromImagereturns; SimpleITK is(x=i, y=j, z=k).
And the operational version: always print GetSpacing()/GetOrigin()/GetDirection()
rather than assuming, and convert explicitly with sitk.GetArrayFromImage /
GetImageFromArray at the NumPy↔SimpleITK boundary. GetImageFromArray creates a
new image with default origin, spacing, and direction: copy or explicitly set the
intended origin, spacing, and direction before treating it as spatially
corresponding to the array’s source.
A concrete check you can run
import SimpleITK as sitkimg = sitk.ReadImage("series_dir") # or build from DICOMarr = sitk.GetArrayFromImage(img) # -> NumPy [k, j, i]
print(img.GetSize()) # (x=cols, y=rows, z=slices) -- SimpleITK orderprint(arr.shape) # (slices, rows, cols) -- NumPy order (reversed)print(img.GetSpacing()) # (sx, sy, sz) in mm, LPSNote the reversal: img.GetSize() is (x, y, z) while arr.shape is (z, y, x).
The voxel at SimpleITK (x=10, y=20, z=5) is arr[5, 20, 10]. If you forget the
reversal you have silently transposed the volume. This reversal is the
GetArrayFromImage conversion convention for a scalar 3D image — not an intrinsic
NumPy memory-order or indexing rule for arbitrary arrays.
Recognising orientations from the cosines
The two IOP triplets tell you which way the row/column axes point in the patient.
For the standard axial orientation, increasing the column index i points toward
patient left (+x in LPS — right-to-left across the patient), and increasing the
row index j points toward posterior (+y), giving the axial IOP used in the
worked example of the
previous section:
ImageOrientationPatient = [1, 0, 0, 0, 1, 0] # row=+x(L), col=+y(P), axial planeA coronal or sagittal series has different cosines; an oblique series (acquired at a tilt) has non-axis cosines. Never assume “axial” — read the cosines, because the direction matrix is part of the geometry that decides whether two objects share space (the shared-space section).
The conventions in this section keep the in-plane geometry honest. A 3D volume has one more geometric question to settle before it can be trusted: in which order do the slices stack? The answer is geometry again, and the next section derives it.
Sources
- DICOM PS3.3, Image Plane Module (C.7.6.2) and Image Position and Image Orientation (C.7.6.2.1.1, Equation C.7.6.2.1-1) — pixel index (column
i, rowj) semantics,ImageOrientationPatient, andPixelSpacingordering. (current edition) - SimpleITK, fundamental concepts — index/physical-space duality,
GetPixel,GetSize,GetSpacing, and the NumPy↔SimpleITK array/image conversion boundary with its opposite dimension order. (docs)
© 2026 Adrian Tkacz · BioMedical-IT
Original text and figures are licensed under the Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0), unless otherwise noted. Please credit Adrian Tkacz / BioMedical-IT and link to the original source. Third-party materials retain their respective rights and licenses.