io.nifti_ref

Module: io.nifti_ref

Inheritance diagram for nipy.io.nifti_ref:

Inheritance diagram of nipy.io.nifti_ref

An implementation of some of the NIFTI conventions as desribed in:

http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1.h

A version of the same file is in the nibabel repisitory at doc/source/external/nifti1.h.

Background

We (nipystas) make an explicit distinction between:

  • an input coordinate system of an image (the array == voxel coordinates)
  • output coordinate system (usually millimeters in some world for space, seconds for time)
  • the mapping between the two.

The collection of these three is the coordmap attribute of a NIPY image.

There is no constraint that the number of input and output coordinates should be the same.

We don’t specify the units of our output coordinate system, but assume spatial units are millimeters and time units are seconds.

NIFTI is mostly less explicit, but more constrained.

NIFTI input coordinate system

NIFTI files can have up to seven voxel dimensions (7 axes in the input coordinate system).

The first 3 voxel dimensions of a NIFTI file must be spatial but can be in any order in relationship to directions in mm space (the output coordinate system)

The 4th voxel dimension is assumed to be time. In particular, if you have some other meaning for a non-spatial dimension, the NIFTI standard suggests you set the length of the 4th dimension to be 1, and use the 5th dimension of the image instead, and set the NIFTI “intent” fields to state the meaning. If the intent field is set correctly then it should be possible to set meaningful input coordinate axis names for dimensions > (0, 1, 2).

There’s a wrinkle to the 4th axis is time story; the xyxt_units field in the NIFTI header can specify the 4th dimension units as Hz (frequency), PPM (concentration) or Radians / second.

NIFTI also has a ‘dim_info’ header attribute that optionally specifies that 0 or more of the first three voxel axes are ‘frequency’, ‘phase’ or ‘slice’. These terms refer to 2D MRI acquisition encoding, where ‘slice’s are collected sequentially, and the two remaining dimensions arose from frequency and phase encoding. The dim_info fields are often not set. 3D acquisitions don’t have a ‘slice’ dimension.

NIFTI output coordinate system

In the NIFTI specification, the order of the output coordinates (at least the first 3) are fixed to be what might be called RAS+, that is (‘x=L->R’, ‘y=P->A’, ‘z=I->S’). This RAS+ output order is not allowed to change and there is no way of specifying such a change in the NIFTI header.

The world in which these RAS+ X, Y, Z axes exist can be one of the recognized spaces, which are: scanner, aligned (to another file’s world space), Talairach, MNI 152 (aligned to the MNI 152 atlas).

By implication, the 4th output dimension is likely to be seconds (given the 4th input dimension is likely time), but there’s a field xyzt_units (see above) that can be used to imply the 4th output dimension is actually frequency, concentration or angular velocity.

NIFTI input / output mapping

NIFTI stores the relationship between the first 3 (spatial) voxel axes and the RAS+ coordinates in an XYZ affine. This is a homogenous coordinate affine, hence 4 by 4 for 3 (spatial) dimensions.

NIFTI also stores “pixel dimensions” in a pixdim field. This can give you scaling for individual axes. We ignore the values of pixdim for the first 3 axes if we have a full (“sform”) affine stored in the header, otherwise they form part of the affine above. pixdim``[3:] provide voxel to output scalings for later axes.  The units for the 4th dimension can come from ``xyzt_units as above.

We take the convention that the output coordinate names are (‘x=L->R’, ‘y=P->A’, ‘z=I->S’,’t’,’u’,’v’,’w’) unless there is no time axis (see below) in which case we just omit ‘t’. The first 3 axes are also named after the output space (‘scanner-x=L->R’, ‘mni-x=L-R’ etc).

The input axes are ‘ijktuvw’ unless there is no time axis (see below), in which case they are ‘ijkuvw’ (remember, NIFTI only allows 7 dimensions, and one is used up by the time length 1 axis).

Time-like axes

A time-like axis is an axis that is any of time, Hz, PPM or radians / second.

We recognize time in a NIPY coordinate map by an input or an output axis named ‘t’ or ‘time’. If it’s an output axis we work out the corresponding input axis.

A Hz axis can be called ‘hz’ or ‘frequency-hz’.

A PPM axis can be called ‘ppm’ or ‘concentration-ppm’.

A radians / second axis can be called ‘rads’ or ‘radians/s’.

Does this NIFTI image have a time-like axis?

We take there to be no time axis if there are only three NIFTI dimensions, or if:

  • the length of the fourth NIFTI dimension is 1 AND
  • There are more than four dimensions AND
  • The xyzt_units field does not indicate time or time-like units.

What we do about all this

For saving a NIPY image to NIFTI, see the docstring for nipy2nifti(). For loading a NIFTI image to NIPY, see the docstring for nifti2nipy().

Class

NiftiError

class nipy.io.nifti_ref.NiftiError

Bases: exceptions.Exception

__init__()

x.__init__(…) initializes x; see help(type(x)) for signature

Functions

nipy.io.nifti_ref.nifti2nipy(ni_img)

Return NIPY image from NIFTI image ni_image

Parameters:

ni_img : nibabel.Nifti1Image

NIFTI image

Returns:

img : Image

nipy image

Raises:

NiftiError : if image is < 3D

Notes

Lacking any other information, we take the input coordinate names for axes 0:7 to be (‘i’, ‘j’, ‘k’, ‘t’, ‘u’, ‘v’, ‘w’).

If the image is 1D or 2D then we have a problem. If there’s a defined (sform, qform) affine, this has 3 input dimensions, and we have to guess what the extra input dimensions are. If we don’t have a defined affine, we don’t know what the output dimensions are. For example, if the image is 2D, and we don’t have an affine, are these X and Y or X and Z or Y and Z? In the presence of ambiguity, resist the temptation to guess - raise a NiftiError.

If there is a time-like axis, name the input and corresponding output axis for the type of axis (‘t’, ‘hz’, ‘ppm’, ‘rads’).

Otherwise remove the ‘t’ axis from both input and output names, and squeeze the length 1 dimension from the input data.

If there’s a ‘t’ axis get toffset and put into affine at position [3, -1].

If dim_info is set coherently, set input axis names to ‘slice’, ‘freq’, ‘phase’ from dim_info.

Get the output spatial coordinate names from the ‘scanner’, ‘aligned’, ‘talairach’, ‘mni’ XYZ spaces (see nipy.core.reference.spaces).

We construct the N-D affine by taking the XYZ affine and adding scaling diagonal elements from pixdim.

If the space units in NIFTI xyzt_units are ‘microns’ or ‘meters’ we adjust the affine to mm units, but warn because this might be a mistake.

If the time units in NIFTI xyzt_units are ‘msec’ or ‘usec’, scale the time axis pixdim values accordingly.

Ignore the intent-related fields for now, but warn that we are doing so if there appears to be specific information in there.

nipy.io.nifti_ref.nipy2nifti(img, data_dtype=None, strict=None, fix0=True)

Return NIFTI image from nipy image img

Parameters:

img : object

An object, usually a NIPY Image, having attributes coordmap and shape

data_dtype : None or dtype specifier

None means try and use header dtype, otherwise try and use data dtype, otherwise use np.float32. A dtype specifier means set the header output data dtype using np.dtype(data_dtype).

strict : bool, optional

Whether to use strict checking of input image for creating NIFTI

fix0: bool, optional

Whether to fix potential 0 column / row in affine. This option only used when trying to find time etc axes in the coordmap output names. In order to find matching input names, we need to use the corresponding rows and columns in the affine. Sometimes time, in particular, has 0 scaling, and thus all 0 in the corresponding row / column. In that case it’s hard to work out which input corresponds. If fix0 is True, and there is only one all zero (matrix part of the) affine row, and only one all zero (matrix part of the) affine column, fix scaling for that combination to zero, assuming this a zero scaling for time.

Returns:

ni_img : nibabel.Nifti1Image

NIFTI image

Raises:

NiftiError: if space axes not orthogonal to non-space axes

NiftiError: if non-space axes not orthogonal to each other

NiftiError: if `img` output space does not match named spaces in NIFTI

NiftiError: if input image has more than 7 dimensions

NiftiError: if input image has 7 dimensions, but no time dimension, because

we need to add an extra 1 length axis at position 3

NiftiError: if we find a time-like input axis but the matching output axis

is a different time-like.

NiftiError: if we find a time-like output axis but the matching input axis

is a different time-like.

NiftiError: if we find a time output axis and there are non-zero non-spatial

offsets in the affine, but we can’t find a corresponding input axis.

Notes

First, we need to create a valid XYZ Affine. We check if this can be done by checking if there are recognizable X, Y, Z output axes and corresponding input (voxel) axes. This requires the input image to be at least 3D. If we find these requirements, we reorder the image axes to have XYZ output axes and 3 spatial input axes first, and get the corresponding XYZ affine.

If the spatial dimensions are not orthogonal to the non-spatial dimensions, raise a NiftiError.

If the non-spatial dimensions are not orthogonal to each other, raise a NiftiError.

We check if the XYZ output fits with the NIFTI named spaces of scanner, aligned, Talairach, MNI. If so, set the NIFTI code and qform, sform accordingly. If the space corresponds to ‘unknown’ then we must set the NIFTI transform codes to 0, and the affine must match the affine we will get from loading the NIFTI with no qform, sform. If not, we’re going to lose information in the affine, and raise an error.

If any of the first three input axes are named (‘slice’, ‘freq’, ‘phase’) set the dim_info field accordingly.

Set the xyzt_units field to indicate millimeters and seconds, if there is a ‘t’ axis, otherwise millimeters and 0 (unknown).

We look to see if we have a time-like axis in the inputs or the outputs. A time-like axis has labels ‘t’, ‘hz’, ‘ppm’, ‘rads’. If we have an axis ‘t’ in the inputs and the outputs, check they either correspond, or both inputs and output correspond with no other axis, otherwise raise NiftiError. Do the same check for ‘hz’, then ‘ppm’, then ‘rads’.

If we do have a time-like axis, roll that axis to be the 4th axis. If this axis is actually time, take the affine[3, -1] and put into the toffset field. If there’s no time-like axis, but there are other non-spatial axes, make a length 1 4th array axis to indicate this.

If the resulting NIFTI image has more than 7 dimensions, raise a NiftiError.

Set pixdim for axes >= 3 using vector length of corresponding affine columns.

We don’t set the intent-related fields for now.