-
Notifications
You must be signed in to change notification settings - Fork 258
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.
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.
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).
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.
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
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.
We could have a content:
field which allows a string of arbitrary length
instead of the nifti 80 character descrip
.
- 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.
- 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.
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.