Skip to content
Matthew Brett edited this page Apr 1, 2011 · 3 revisions

nifti-nrrd

Or - things that nifti can learn from NRRD.

These notes are from reading through the NRRD definition.

They are very draftey.

JSON might improve over the very raw 'field: value' format of NRRD by allowing nesting.

Detached header

NRRD can act as a header for raw data files or DICOMS on disk. The header defines the parameters for data, and the datafile: field specifies the files that contain the data. Nifti can't cover this general case because it makes strong assumptions about its data, including:

  • The first three dimensions of the data array are spatial - see around line 306 of nifti1.h
  • The output (embedding) space is RAS (left to right, posterior to anterior, inferior to superior) - see around line 969 in nifti1.h

We could remove these assumptions for a nifti with our own extensions, but then we're getting close to being a NRRD ourselves.

Space

Nifti specifies that the first three output dimensions should be L->R, P->A and I->S (RAS+). If there's a fourth dimension, that will be time.

The sform and qform transformations encode the relationship between array coordinates (i, j, k) and locations in the RAS+ space.

NRRD tries to deal with any embedding space, using the space* fields - see :ref:`nrrd-space`. Specifically, NNRD allows for any dimension of output space, for example, 4D for 3 spatial dimensions and time. There is information to create ND (e.g 4D) affines to encode the array to output space tranformation.

In our case, this might be too general. Specifically, we probably only need to be able to store full transformations from voxel space to R3 (X, Y, Z). That is, we probably don't need to store a general affine such that movement across the (e.g) first spatial axis also modifies what time this voxel refers to.

NRRD allows units that are any string for its output axes (space units field). nifti has codes for output xyzt units, which can therefore only be (m, mm, microm, sec, msec, microsec, hz, ppm, rads, unknown). Given that our first three output coordinates are space, and we've got floating point transformations, (m, mm, micron) seem to fully cover our options, and so we don't need extra flexibility in units for the spatial diemnsions.

For non-spatial dimensions, we have more use for the greater flexibility in giving output axis units. Maybe we can store something with the 'input axes'. That is some transformation from input (voxel) axis units (0..N) to output axis units. The output axis units may be in the nifti not-spatial allowable range of (sec, msec, microsec, hz, ppm, rads, unknown). We should probably try and write the nifti header correctly, and raise an error if the nifti record clashes with the extension (nifti says something other than unknown, and extension has another of the known values but not the same as the nifti).

Measurement frame

NRRD also has the useful concept of measurement_frame. Let us say that there are 3 output axes for the NRRD - see :ref:`nrrd-space` - then the measurement frame is a 3 by 3 matrix. The measurement frame has the idea that the measurement (say, vectors, or tensors) have natural coordinate axes in which they were made. For example, vectors stored in the image could be (3,) vectors of coordinates relative to 3 axes. In this case, the first column of the measurement frame matrix would be the orientation of the first measurement axis relative to the output space axes. In our case, where nifti assumes RAS+ axes, this will therefore be the distance (usually unit) moved in the RL, PA, IS directions when moving one unit along the first vector coordinate system axis.

See more about measurement frame on the NAMIC wiki.

We do need this to express, in particular, the relationship of DWI gradient tables to RAS+ space, and therefore voxel space. In our case the measurement frame will be a 3 by N matrix, where N is the number of axes of the measurement - typically also 3.

NRRD space

There may be less output space dimensions than input dimensions of some of the input dimensions are e.g. RGB vectors - i.e. not space.

Fields are:

  • space
  • space dimension
  • space units
  • space origin
  • space directions

Types

NRRD has a block data type - that is, the units in the array are opaque binary blocks of a certain size - e.g B bytes. The things may be c structs. Nifti does not have this generality. In fact, because the first 3 dimensions have to be spatial, the c struct would have to be split up by making the nifti be of a single byte type like uchar and have a last axis of length B. Because the axis can't be first (it's not spatial) and because niftis have row-major storage, the bytes of each B length unit will be full volumes apart on disk.

So block: probably cannot be supported with a valid nifti.

Content

We could have a content: field which allows a string of arbitrary length instead of the nifti 80 character descrip.

Min / max

  • min
  • max
  • oldmin
  • oldmax

Not in standard nifti. There are nifti glmin and glmax, and calmin and calmax. The gl variables are marked as "UNUSED", and they are ints, so they can't be used to store either the min or max of a floating point datatype or the old min and old max of a floating point datatype. The cal variables are recorded as being for display.

Sample units

  • sampleunits

nifti has the intent code, but it seems to cover statistics in the main (t tests etc). So the intent can tell you the scalars are t values, but not that the scalars are ml min-1 or similar.

Per axis

Covered by nifti:

  • sizes (dim)
  • spacings (voxdim)

Not covered, and probably best handled in a JSON array (one entry per axis):

{"input_axes": [
    {"min": -72.0,
    "max": 72.0,
    "centering": "node",
    "labels": ["X", "phase"],
    "units", "mm",
    "kind", "space",
    },
    {"min": -80.0,
    "max": 60.0,
    "centering": "node",
    "labels": ["Y", "frequency"],
    "units", "mm",
    "kind", "space",
    },
    {"thickness": 2.4,
    "min": -40.0,
    "max": 64.0,
    "centering": "node",
    "labels": ["Z", "slice"],
    "units", "mm",
    "kind", "space",
    },
    {"min": 0.0,
    "centering": "node",
    "labels": ["time", "t"],
    "units", "sec",
    "kind", "time",
    },
]
}

There is some overlap with the interpretations of the various slice-related fields in nifti - which can give information that constrains how long a volume should take. The nifti toffset field is the same as the min field in the last entry in the JSON above. The nifti dim_info code allows the nifti to say which is the slice, frequency and phase axes in the first three axes. NRRD here is obviously more expressive.

We may also benefit from axis tick labels stored directly. So, the fourth axis might be time, but we've sample discontinuously at t=(0, 2, 2.4, 7, 11) or similar. In this case we'd replace min and max with centers, where centers would be a vector of the same length as the axis.

Clone this wiki locally