================================
 Defining the DICOM orientation
================================

.. _dicom-pcs:

DICOM patient coordinate system
===============================

First we define the standard DICOM patient-based coordinate system.
This is what DICOM means by x, y and z axes in its orientation
specification.  From section C.7.6.2.1.1 of the `DICOM object
definitions`_ (2009):

   If Anatomical Orientation Type (0010,2210) is absent or has a value
   of BIPED, the x-axis is increasing to the left hand side of the
   patient. The y-axis is increasing to the posterior side of the
   patient. The z-axis is increasing toward the head of the patient.

(we'll ignore the quadupeds for now).

In a way it's funny to call this the 'patient-based' coordinate system.
'Doctor-based coordinate system' is a better name.  Think of a doctor
looking at the patient from the foot of the scanner bed.  Imagine the
doctor's right hand held in front of her like Spiderman about to shoot a
web, with her palm towards the patient, defining a right-handed
coordinate system.  Her thumb points to her right (the patient's left),
her index finger points down, and the middle finger points at the
patient.

.. _dicom-pixel-array:

DICOM pixel data
================

C.7.6.3.1.4 - Pixel Data
   Pixel Data (7FE0,0010) for this image. The order of pixels sent for
   each image plane is left to right, top to bottom, i.e., the upper
   left pixel (labeled 1,1) is sent first followed by the remainder of
   row 1, followed by the first pixel of row 2 (labeled 2,1) then the
   remainder of row 2 and so on.

The resulting pixel array then has size ('Rows', 'Columns'), with
row-major storage (rows first, then columns).  We'll call this the DICOM
*pixel array*.

Pixel spacing
=============

Section 10.7.1.3:  Pixel Spacing
  The first value is the row spacing in mm, that is the spacing between
  the centers of adjacent rows, or vertical spacing.  The second value
  is the column spacing in mm, that is the spacing between the centers
  of adjacent columns, or horizontal spacing.

.. _dicom-orientation:

DICOM voxel to patient coordinate system mapping
================================================

See:

* http://www.dclunie.com/medical-image-faq/html/part2.html;
* `this dicom mailing list post <https://groups.google.com/forum/#!topic/comp.protocols.dicom/-Nms_GgmdRc>`_;

See `wikipedia direction cosine`_ for a definition of direction cosines.

From section C.7.6.2.1.1 of the `DICOM object definitions`_ (2009):

   The Image Position (0020,0032) specifies the x, y, and z coordinates
   of the upper left hand corner of the image; it is the center of the
   first voxel transmitted. Image Orientation (0020,0037) specifies the
   direction cosines of the first row and the first column with respect
   to the patient.  These Attributes shall be provide as a pair. Row
   value for the x, y, and z axes respectively followed by the Column
   value for the x, y, and z axes respectively.

From Section C.7.6.1.1.1 we see that the 'positive row axis' is left to
right, and is the direction of the rows, given by the direction of last
pixel in the first row from the first pixel in that row.  Similarly the
'positive column axis' is top to bottom and is the direction of the
columns, given by the direction of the last pixel in the first column
from the first pixel in that column.

Let's rephrase: the first three values of 'Image Orientation Patient'
are the direction cosine for the 'positive row axis'.  That is, they
express the direction change in (x, y, z), in the DICOM patient
coordinate system (DPCS), as you move along the row.  That is, as you
move from one column to the next.  That is, as the *column* array index
changes. Similarly, the second triplet of values of 'Image Orientation
Patient' (``img_ornt_pat[3:]`` in Python), are the direction cosine for
the 'positive column axis', and express the direction you move, in the
DPCS, as you move from row to row, and therefore as the *row* index
changes.

Further down section C.7.6.2.1.1 (RCS below is the *reference coordinate
system* - see `DICOM object definitions`_ section 3.17.1):

   The Image Plane Attributes, in conjunction with the Pixel Spacing
   Attribute, describe the position and orientation of the image slices
   relative to the patient-based coordinate system. In each image frame
   the Image Position (Patient) (0020,0032) specifies the origin of the
   image with respect to the patient-based coordinate system. RCS and
   the Image Orientation (Patient) (0020,0037) attribute values specify
   the orientation of the image frame rows and columns. The mapping of
   pixel location (i, j) to the RCS is calculated as follows:

   .. math::

      \begin{bmatrix} P_x\\
                      P_y\\
                      P_z\\
                      1 \end{bmatrix} =
      \begin{bmatrix} X_x\Delta{i} & Y_x\Delta{j} & 0 & S_x \\
                      X_y\Delta{i} & Y_y\Delta{j} & 0 & S_y \\
                      X_z\Delta{i} & Y_z\Delta{j} & 0 & S_z \\
                      0   & 0   & 0 & 1 \end{bmatrix}
      \begin{bmatrix} i\\
                      j\\
                      0\\
                      1 \end{bmatrix}
      = M
      \begin{bmatrix} i\\
                      j\\
                      0\\
                      1 \end{bmatrix}

   Where:

   #. $P_{xyz}$ : The coordinates of the voxel (i,j) in the frame's
      image plane in units of mm.
   #. $S_{xyz}$ : The three values of the Image Position (Patient)
      (0020,0032) attributes. It is the location in mm from the origin
      of the RCS.
   #. $X_{xyz}$ : The values from the row (X) direction cosine of the
      Image Orientation (Patient) (0020,0037) attribute.
   #. $Y_{xyz}$ : The values from the column (Y) direction cosine of the
      Image Orientation (Patient) (0020,0037) attribute.
   #. $i$ : Column index to the image plane. The first column is index
      zero.
   #. $\Delta{i}$: Column pixel resolution of the Pixel Spacing
      (0028,0030) attribute in units of mm.
   #. $j$ : Row index to the image plane. The first row index is zero.
   #. $\Delta{j}$ - Row pixel resolution of the Pixel Spacing
      (0028,0030) attribute in units of mm.

.. _ij-transpose:

(i, j), columns, rows in DICOM
==============================

We stop to ask ourselves, what does DICOM mean by voxel (i, j)?

Isn't that obvious?  Oh dear, no it isn't.  See the
:ref:`dicom-orientation` formula above.  In particular, you'll see:

* $i$ : Column index to the image plane. The first column is index zero.
* $j$ : Row index to the image plane. The first row index is zero.

That is, if we have the :ref:`dicom-pixel-array` as defined above, and
we call that ``pixel_array``, then voxel (i, j) in the notation above is
given by ``pixel_array[j, i]``.

What does this mean?  It means that, if we want to apply the formula
above to array indices in ``pixel_array``, we first have to apply a
column / row flip to the indices.  Say $M_{pixar}$ (sorry) is the affine
to go from array indices in ``pixel_array`` to mm in the DPCS.  Then,
given $M$ above:

.. math::

   M_{pixar} = M \left(\begin{smallmatrix}0 & 1 & 0 & 0\\1 & 0 & 0 & 0\\0 & 0 & 1 & 0\\0 & 0 & 0 & 1\end{smallmatrix}\right)

.. _dicom-affines-reloaded:

DICOM affines again
===================

The :ref:`ij-transpose` is rather confusing, so we're going to rephrase
the affine mapping; we'll use $r$ for the row index (instead of $j$
above), and $c$ for the column index (instead of $i$).

Next we define a flipped version of 'ImageOrientationPatient', $F$, that
has flipped columns. Thus if the vector of 6 values in
'ImageOrientationPatient' are $(i_1 .. i_6)$, then:

.. math::

   F =  \begin{bmatrix} i_4 & i_1 \\
                        i_5 & i_2 \\
                        i_6 & i_3 \end{bmatrix}

Now the first column of F contains what the DICOM docs call the 'column
(Y) direction cosine', and second column contains the 'row (X) direction
cosine'.  We prefer to think of these as (respectively) the row index
direction cosine and the column index direction cosine.

Now we can rephrase the DICOM affine mapping with:

.. _dicom-slice-affine:

DICOM affine formula
====================

.. math::

   \begin{bmatrix} P_x\\
                   P_y\\
                   P_z\\
                   1 \end{bmatrix} =
   \begin{bmatrix} F_{11}\Delta{r} & F_{12}\Delta{c} & 0 & S_x \\
                   F_{21}\Delta{r} & F_{22}\Delta{c} & 0 & S_y \\
                   F_{31}\Delta{r} & F_{32}\Delta{c} & 0 & S_z \\
                   0   & 0   & 0 & 1 \end{bmatrix}
   \begin{bmatrix} r\\
                   c\\
                   0\\
                   1 \end{bmatrix}
   = A
   \begin{bmatrix} r\\
                   c\\
                   0\\
                   1 \end{bmatrix}

Where:

* $P_{xyz}$ : The coordinates of the voxel (c, r) in the frame's image
  plane in units of mm.
* $S_{xyz}$ : The three values of the Image Position (Patient)
  (0020,0032) attributes. It is the location in mm from the origin of
  the RCS.
* $F_{:,1}$ : The values from the column (Y) direction cosine of the
  Image Orientation (Patient) (0020,0037) attribute - see above.
* $F_{:,2}$ : The values from the row (X) direction cosine of the Image
  Orientation (Patient) (0020,0037) attribute - see above.
* $r$ : Row index to the image plane. The first row index is zero.
* $\Delta{r}$ - Row pixel resolution of the Pixel Spacing (0028,0030)
  attribute in units of mm.
* $c$ : Column index to the image plane. The first column is index zero.
* $\Delta{c}$: Column pixel resolution of the Pixel Spacing (0028,0030)
  attribute in units of mm.

For later convenience we also define values useful for 3D volumes:

* $s$ : Slice index to the slice plane. The first slice index is zero.
* $\Delta{s}$ - Spacing in mm between slices.

.. _dicom-3d-affines:

Getting a 3D affine from a DICOM slice or list of slices
========================================================

Let us say, we have a single DICOM file, or a list of DICOM files that
we believe to be a set of slices from the same volume.  We'll call the
first the *single slice* case, and the second, *multi slice*.

In the *multi slice* case, we can assume that the
'ImageOrientationPatient' field is the same for all the slices.

We want to get the affine transformation matrix $A$ that maps from voxel
coordinates in the DICOM file(s), to mm in the :ref:`dicom-pcs`.

By voxel coordinates, we mean coordinates of form $(r, c, s)$ - the row,
column and slice indices - as for the :ref:`dicom-slice-affine`.

In the single slice case, the voxel coordinates are just the indices
into the pixel array, with the third (slice) coordinate always being 0.

In the multi-slice case, we have arranged the slices in ascending or
descending order, where slice numbers range from 0 to $N-1$ - where $N$
is the number of slices - and the slice coordinate is a number on this
scale.

We know, from :ref:`dicom-slice-affine`, that the first, second and
fourth columns in $A$ are given directly by the (flipped)
'ImageOrientationPatient', 'PixelSpacing' and 'ImagePositionPatient'
field of the first (or only) slice.

Our job then is to fill the first three rows of the third column of $A$.
Let's call this the vector $\mathbf{k}$ with values  $k_1, k_2, k_3$.

.. _dicom-affine-defs:

DICOM affine Definitions
------------------------

See also the definitions in :ref:`dicom-slice-affine`.   In addition

* $T^1$ is the 3 element vector of the 'ImagePositionPatient' field of
  the first header in the list of headers for this volume.
* $T^N$ is the 'ImagePositionPatient' vector for the last header in the
  list for this volume, if there is more than one header in the volume.
* vector $\mathbf{n} = (n_1, n_2, n_3)$ is the result of taking the
  cross product of the two columns of $F$ from
  :ref:`dicom-slice-affine`.

Derivations
-----------

For the single slice case we just fill $\mathbf{k}$ with $\mathbf{n} \cdot
\Delta{s}$ - on the basis that the Z dimension should be
right-handed orthogonal to the X and Y directions.

For the multi-slice case, we can fill in $\mathbf{k}$ by using the information
from $T^N$, because $T^N$ is the translation needed to take the
first voxel in the last (slice index = $N-1$) slice to mm space.  So:

.. math::

   \left(\begin{smallmatrix}T^N\\1\end{smallmatrix}\right) = A \left(\begin{smallmatrix}0\\0\\N - 1\\1\end{smallmatrix}\right)

From this it follows that:

.. math::

   \begin{Bmatrix}k_{{1}} : \frac{T^{N}_{{1}} - T^{1}_{{1}}}{N - 1}, & k_{{2}} : \frac{T^{N}_{{2}} - T^{1}_{{2}}}{N - 1}, & k_{{3}} : \frac{T^{N}_{{3}} - T^{1}_{{3}}}{N - 1}\end{Bmatrix}

and therefore:

.. _dicom-3d-affine-formulae:

3D affine formulae
------------------

.. math::

   A_{multi} = \left(\begin{smallmatrix}F_{{11}} \Delta{r} & F_{{12}} \Delta{c} & \frac{T^{N}_{{1}} - T^{1}_{{1}}}{N - 1} & T^{1}_{{1}}\\F_{{21}} \Delta{r} & F_{{22}} \Delta{c} & \frac{T^{N}_{{2}} - T^{1}_{{2}}}{N - 1} & T^{1}_{{2}}\\F_{{31}} \Delta{r} & F_{{32}} \Delta{c} & \frac{T^{N}_{{3}} - T^{1}_{{3}}}{N - 1} & T^{1}_{{3}}\\0 & 0 & 0 & 1\end{smallmatrix}\right)

   A_{single} = \left(\begin{smallmatrix}F_{{11}} \Delta{r} & F_{{12}} \Delta{c} & \Delta{s} n_{{1}} & T^{1}_{{1}}\\F_{{21}} \Delta{r} & F_{{22}} \Delta{c} & \Delta{s} n_{{2}} & T^{1}_{{2}}\\F_{{31}} \Delta{r} & F_{{32}} \Delta{c} & \Delta{s} n_{{3}} & T^{1}_{{3}}\\0 & 0 & 0 & 1\end{smallmatrix}\right)

See :download:`derivations/spm_dicom_orient.py` for the derivations and
some explanations.

For a single slice $N=1$ the affine matrix is $A_{single}$. In this
case, the slice spacing $\Delta{s}$ may be obtained by the Spacing
Between Slices (0018,0088) attribute in units of mm, if it exists.

.. _dicom-z-from-slice:

Working out the Z coordinates for a set of slices
=================================================

We may have the problem (see e.g. :ref:`spm-volume-sorting`) of trying
to sort a set of slices into anatomical order.  For this we want to use
the orientation information to tell us where the slices are in space,
and therefore, what order they should have.

To do this sorting, we need something that is proportional, plus a
constant, to the voxel coordinate for the slice (the value for the slice
index).

Our DICOM might have the 'SliceLocation' field (0020,1041).
'SliceLocation' seems to be proportional to slice location, at least for
some GE and Philips DICOMs I was looking at.  But, there is a more
reliable way (that doesn't depend on this field), and uses only the very
standard 'ImageOrientationPatient' and 'ImagePositionPatient' fields.

Consider the case where we have a set of slices, of unknown order, from
the same volume.

Now let us say we have one of these slices - slice $i$.  We have the
affine for this slice from the calculations above, for a single slice
($A_{single}$).

Now let's say we have another slice $j$ from the same volume.  It will
have the same affine, except that the 'ImagePositionPatient' field will
change to reflect the different position of this slice in space. Let us
say that there a translation of $d$ slices between $i$ and $j$.  If
$A_i$ ($A$ for slice $i$) is $A_{single}$ then $A_j$ for $j$ is given
by:

.. math::

   A_j = A_{single} \left(\begin{smallmatrix}1 & 0 & 0 & 0\\0 & 1 & 0 & 0\\0 & 0 & 1 & d\\0 & 0 & 0 & 1\end{smallmatrix}\right)

and 'ImagePositionPatient' for $j$ is:

.. math::

   T^j = \left(\begin{smallmatrix}T^{1}_{{1}} + \Delta{s} d n_{{1}}\\T^{1}_{{2}} + \Delta{s} d n_{{2}}\\T^{1}_{{3}} + \Delta{s} d n_{{3}}\end{smallmatrix}\right)

Remember that the third column of $A$ gives the vector resulting from a
unit change in the slice voxel coordinate.  So, the
'ImagePositionPatient' of slice - say slice $j$ - can be thought of the
addition of two vectors $T^j = \mathbf{a} + \mathbf{b}$, where
$\mathbf{a}$ is the position of the first voxel in some slice (here
slice 1, therefore $\mathbf{a} = T^1$) and $\mathbf{b}$ is $d$ times the
third column of $A$.  Obviously $d$ can be negative or positive. This
leads to various ways of recovering something that is proportional to
$d$ plus a constant.  The algorithm suggested in this `ITK post on
ordering slices`_ - and the one used by SPM - is to take the inner
product of $T^j$ with the unit vector component of third column of
$A_j$ - in the descriptions here, this is the vector $\mathbf{n}$:

.. _ITK post on ordering slices: http://www.itk.org/pipermail/insight-users/2003-September/004762.html

.. math::

   T^j \cdot \mathbf{c} = \left(\begin{smallmatrix}T^{1}_{{1}} n_{{1}} + T^{1}_{{2}} n_{{2}} + T^{1}_{{3}} n_{{3}} + \Delta{s} d n_{{1}}^{2} + \Delta{s} d n_{{2}}^{2} + \Delta{s} d n_{{3}}^{2}\end{smallmatrix}\right)

This is the distance of 'ImagePositionPatient' along the slice direction
cosine.

The unknown $T^1$ terms pool into a constant, and the operation has the
neat feature that, because the $n_{123}^2$ terms, by definition, sum to 1,
the whole can be expressed as $\lambda + \Delta{s} d$ - i.e. it is
equal to the slice voxel size ($\Delta{s}$) multiplied by $d$,
plus a constant.

Again, see :download:`derivations/spm_dicom_orient.py` for the derivations.

.. include:: ../links_names.txt
