In a previous post, the nifti-1 file format was presented. An update of this format has recently been produced by the Data Format Working Group (dfwg). The updated version retains generally the same amount of information as the previous, with the crucial difference that it allows far more datapoints in each dimension, thus permitting that the same overall file structure is used to store, for instance, surface-based scalar data, or large connectivity matrices. Neither of these had originally been intended at the time the analyze or the nifti-1 formats were developed. While packages as FreeSurfer developed their own formats for surface-based scalar data, a more general solution was still pending.
Compatible, but not as before
Users who participated of the transition from analyze to nifti-1 may remember that the same libraries used to read analyze would read nifti, perhaps with a few minor difficulties, but the bulk of the actual data would be read by most analyze-compliant applications. This was possible because a large part of the relevant information in the header was kept exactly in the same position counted from the beginning of the file. An application could read information at a given byte position and locate it without error, or without finding something else.
This time things are different. While a large degree of compatibility exists, this compatibility helps more the developer than the end user. If before, an application made to read only analyze could read nifti-1, this time an application made to read nifti-1 will not read nifti-2 without a few, even if minor, changes to the application source code. To put in other words, the new version of the format is not bitwise compatible with the previous one. The reasons for this “almost compatibility” will become clear below.
Changing types
The limitation that became evident with the new uses found for the nifti format refer particularly to the maximum number of points (e.g., voxels) in each dimension. This limitation stems from the field short dim[8]
, which allows only 2 bytes (16 bits) for each dimension; since only positive values are accepted (short
is signed), this imposes a cap: no more than 215-1 = 32767 voxels per dimension. In the nifti-2 format, this was replaced by int64_t dim[8]
, which guarantees 8 bytes (64 bits) per dimension, and so, a much larger number of points per dimension, that is, 263-1 = 9,223,372,036,854,775,807.
This change alone already renders the nifti-2 not bitwise compatible with the nifti-1. Yet, other changes were made, some as a consequence of the modifications to dim[8]
, such as slice_start
and slice_end
, both too promoted from short
to int64_t
. Other changes were made so as to improve the general ability to store data with higher precision. A complete table listing the modifications of the field types is below:
NIFTI-1 | NIFTI-2 |
---|---|
short dim[8] |
int64_t dim[8] |
float intent_p1 |
double intent_p1 |
float intent_p2 |
double intent_p2 |
float intent_p3 |
double intent_p3 |
float pixdim[8] |
double pixdim[8] |
float vox_offset |
int64_t vox_offset |
float scl_slope |
double scl_slope |
float scl_inter |
double scl_inter |
float cal_max |
double cal_max |
float cal_min |
double cal_min |
float slice_duration |
double slice_duration |
float toffset |
double toffset |
short slice_start |
int64_t slice_start |
short slice_end |
int64_t slice_end |
char slice_code |
int32_t slice_code |
char xyzt_units |
int32_t xyzt_units |
short intent_code |
int32_t intent_code |
short qform_code |
int32_t qform_code |
short sform_code |
int32_t sform_code |
float quatern_b |
double quatern_b |
float quatern_c |
double quatern_c |
float quatern_d |
double quatern_d |
float srow_x |
double srow_x |
float srow_y |
double srow_y |
float srow_z |
double srow_z |
char magic[4] |
char magic[8] |
Fields removed, fields reordered, fields added
Seven fields that only existed in the nifti-1 for compatibility with the old analyze format were removed entirely. These are:
char data_type[10]
char db_name[18]
int extents
short session_error
char regular
int glmin
int glmax
Another change is that the fields were reordered, which is an improvement over the nifti-1: the magic string, for instance, is now at the beginning of the file, which helps testing what kind of file it is. All constraints that were imposed on the nifti-1 to allow compatibility with the analyze were finally dropped. At the far end of the header, a field with 15 bytes was included for padding the header to a total size of 540, and to ensure 16-byte alignment after the 4 final bytes that indicate extra information are included.
Overview of the new header structure
With the modifications above, the overall structure of the he nifti-2 became:
Type | Name | Offset | Size | Description |
---|---|---|---|---|
int |
sizeof_hdr |
0B | 4B | Size of the header. Must be 540 (bytes). |
char |
magic[8] |
4B | 8B | Magic string, defining a valid signature. |
int16_t |
data_type |
12B | 2B | Data type. |
int16_t |
bitpix |
14B | 2B | Number of bits per voxel. |
int64_t |
dim[8] |
16B | 64B | Data array dimensions. |
double |
intent_p1 |
80B | 8B | 1st intent parameter. |
double |
intent_p2 |
88B | 8B | 2nd intent parameter. |
double |
intent_p3 |
96B | 8B | 3rd intent parameter. |
double |
pixdim[8] |
104B | 64B | Grid spacings (unit per dimension). |
int64_t |
vox_offset |
168B | 8B | Offset into a .nii file. |
double |
scl_slope |
176B | 8B | Data scaling, slope. |
double |
scl_inter |
184B | 8B | Data scaling, offset. |
double |
cal_max |
192B | 8B | Maximum display intensity. |
double |
cal_min |
200B | 8B | Minimum display intensity. |
double |
slice_duration |
208B | 8B | Time for one slice. |
double |
toffset |
216B | 8B | Time axis shift. |
int64_t |
slice_start |
224B | 8B | First slice index. |
int64_t |
slice_end |
232B | 8B | Last slice index. |
char |
descrip[80] |
240B | 80B | Any text. |
char |
aux_file[24] |
320B | 24B | Auxiliary filename. |
int |
qform_code |
344B | 4B | Use the quaternion fields. |
int |
sform_code |
348B | 4B | Use of the affine fields. |
double |
quatern_b |
352B | 8B | Quaternion b parameter. |
double |
quatern_c |
360B | 8B | Quaternion c parameter. |
double |
quatern_d |
368B | 8B | Quaternion d parameter. |
double |
qoffset_x |
376B | 8B | Quaternion x shift. |
double |
qoffset_y |
384B | 8B | Quaternion y shift. |
double |
qoffset_z |
392B | 8B | Quaternion z shift. |
double |
srow_x[4] |
400B | 32B | 1st row affine transform. |
double |
srow_y[4] |
432B | 32B | 2nd row affine transform. |
double |
srow_z[4] |
464B | 32B | 3rd row affine transform. |
int |
slice_code |
496B | 4B | Slice timing order. |
int |
xyzt_units |
500B | 4B | Units of pixdim[1..4]. |
int |
intent_code |
504B | 4B | nifti intent. |
char |
intent_name[16] |
508B | 16B | Name or meaning of the data. |
char |
dim_info |
524B | 1B | Encoding directions. |
char |
unused_str[15] |
525B | 15B | Unused, to be padded with with zeroes. |
Total size | 540B |
NIFTI-1 or NIFTI-2?
For the developer writing input/output functions to handle nifti files, a simple check can be used to test the version and the endianness of the file: the first four bytes are read (int sizeof_hdr
): if equal to 348, then it is a nifti-1 file; if equal to 540, then it is a nifti-2 file. If equal to neither, then swap the bytes, as if reading in the non-native endianness, and repeat the test; if this time the size of the header is found as 348 or 540, the version is determined, and this also implies that all bytes in the file need to be swapped to match the endianness of the current architecture. If, however, the first four bytes do not contain 348 or 540 in either endianness, then it is not a valid nifti file.
Once the version and the endianness have been determined, if it is a nifti-1 file, jump to byte 344 and check if the content is 'ni1'
(or '6E 69 31 00'
in hexadecimal), indicating a pair .hdr/.img
, or if it is 'n+1'
('6E 2B 31 00'
), indicating a single .nii
file. If, however, it is a nifti-2 file, just read the next 8 bytes and check if the content is 'n+2'
('6E 2B 32 00'
) followed by '0D 0A 1A 0A'
(hex).
Storing extra information
Just like the nifti-1, the four bytes after the end of the nifti-2 header are used to indicate extensions and more information. Thus, the actual data begins after the byte 544. See the post on the nifti-1 for details. The cifti-2 file format (used extensively by the Human Connectome Project) is built on top of the nifti-2 format, and uses this extra information.
More information
The official definition of the nifti-2 format is available as a c header file (nifti2.h
) here and mirrored here.
Pingback: The NIFTI file format | Brainder.
Thanks for this excellent write-up, which helped my implement my NIFTI2 reading and writing functions in R. Just one minor note, there is a typo in the list of ‘Fields removed, fields reordered, fields added’: the last entry should (obviously) be ‘glmin’ instead of ‘glmax’.
Hi Tim,
Thanks for catching. Just fixed!
All the best,
Anderson