# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # See COPYING file distributed along with the NiBabel package for the # copyright and license terms. # ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## ''' Read / write access to NIfTI2 image format Format described here: https://www.nitrc.org/forum/message.php?msg_id=3738 Stuff about the CIFTI file format here: https://www.nitrc.org/plugins/mwiki/index.php/cifti:ConnectivityMatrixFileFormats ''' import numpy as np from .analyze import AnalyzeHeader from .batteryrunners import Report from .spatialimages import HeaderDataError, ImageFileError from .nifti1 import Nifti1Header, Nifti1Pair, Nifti1Image r""" Header struct from : https://www.nitrc.org/forum/message.php?msg_id=3738 /*! \struct nifti_2_header \brief Data structure defining the fields in the nifti2 header. This binary header should be found at the beginning of a valid NIFTI-2 header file. */ /*************************/ /************/ struct nifti_2_header { /* NIFTI-2 usage */ /* offset /*************************/ /************/ int sizeof_hdr; /*!< MUST be 540 */ /* 0 */ char magic[8] ; /*!< MUST be valid signature. /* 4 */ short datatype; /*!< Defines data type! */ /* 12 */ short bitpix; /*!< Number bits/voxel. */ /* 14 */ int64_t dim[8]; /*!< Data array dimensions.*/ /* 16 */ double intent_p1 ; /*!< 1st intent parameter. */ /* 80 */ double intent_p2 ; /*!< 2nd intent parameter. */ /* 88 */ double intent_p3 ; /*!< 3rd intent parameter. */ /* 96 */ double pixdim[8]; /*!< Grid spacings. */ /* 104 */ int64_t vox_offset; /*!< Offset into .nii file */ /* 168 */ double scl_slope ; /*!< Data scaling: slope. */ /* 176 */ double scl_inter ; /*!< Data scaling: offset. */ /* 184 */ double cal_max; /*!< Max display intensity */ /* 192 */ double cal_min; /*!< Min display intensity */ /* 200 */ double slice_duration;/*!< Time for 1 slice. */ /* 208 */ double toffset; /*!< Time axis shift. */ /* 216 */ int64_t slice_start; /*!< First slice index. */ /* 224 */ int64_t slice_end; /*!< Last slice index. */ /* 232 */ char descrip[80]; /*!< any text you like. */ /* 240 */ char aux_file[24]; /*!< auxiliary filename. */ /* 320 */ int qform_code ; /*!< NIFTI_XFORM_* code. */ /* 344 */ int sform_code ; /*!< NIFTI_XFORM_* code. */ /* 348 */ double quatern_b ; /*!< Quaternion b param. */ /* 352 */ double quatern_c ; /*!< Quaternion c param. */ /* 360 */ double quatern_d ; /*!< Quaternion d param. */ /* 368 */ double qoffset_x ; /*!< Quaternion x shift. */ /* 376 */ double qoffset_y ; /*!< Quaternion y shift. */ /* 384 */ double qoffset_z ; /*!< Quaternion z shift. */ /* 392 */ double srow_x[4] ; /*!< 1st row affine transform. */ /* 400 */ double srow_y[4] ; /*!< 2nd row affine transform. */ /* 432 */ double srow_z[4] ; /*!< 3rd row affine transform. */ /* 464 */ int slice_code ; /*!< Slice timing order. */ /* 496 */ int xyzt_units ; /*!< Units of pixdim[1..4] */ /* 500 */ int intent_code ; /*!< NIFTI_INTENT_* code. */ /* 504 */ char intent_name[16]; /*!< 'name' or meaning of data. */ /* 508 */ char dim_info; /*!< MRI slice ordering. */ /* 524 */ char unused_str[15]; /*!< unused, filled with \0 */ /* 525 */ } ; /**** 540 bytes total ****/ typedef struct nifti_2_header nifti_2_header ; """ # nifti2 flat header definition for first 540 bytes # First number in comments indicates offset in file header in bytes header_dtd = [ ('sizeof_hdr', 'i4'), # 0; must be 540 ('magic', 'S4'), # 4; must be 'ni2\0' or 'n+2\0' ('eol_check', 'i1', (4,)), # 8; must be 0D 0A 1A 0A ('datatype', 'i2'), # 12; it's the datatype ('bitpix', 'i2'), # 14; number of bits per voxel ('dim', 'i8', (8,)), # 16; data array dimensions ('intent_p1', 'f8'), # 80; first intent parameter ('intent_p2', 'f8'), # 88; second intent parameter ('intent_p3', 'f8'), # 96; third intent parameter ('pixdim', 'f8', (8,)), # 104; grid spacings (units below) ('vox_offset', 'i8'), # 168; offset to data in image file ('scl_slope', 'f8'), # 176; data scaling slope ('scl_inter', 'f8'), # 184; data scaling intercept ('cal_max', 'f8'), # 192; max display intensity ('cal_min', 'f8'), # 200; min display intensity ('slice_duration', 'f8'), # 208; time for 1 slice ('toffset', 'f8'), # 216; time axis shift ('slice_start', 'i8'), # 224; first slice index ('slice_end', 'i8'), # 232; last slice index ('descrip', 'S80'), # 240; any text ('aux_file', 'S24'), # 320; auxiliary filename ('qform_code', 'i4'), # 344; xform code ('sform_code', 'i4'), # 348; xform code ('quatern_b', 'f8'), # 352; quaternion b param ('quatern_c', 'f8'), # 360; quaternion c param ('quatern_d', 'f8'), # 368; quaternion d param ('qoffset_x', 'f8'), # 376; quaternion x shift ('qoffset_y', 'f8'), # 384; quaternion y shift ('qoffset_z', 'f8'), # 392; quaternion z shift ('srow_x', 'f8', (4,)), # 400; 1st row affine transform ('srow_y', 'f8', (4,)), # 432; 2nd row affine transform ('srow_z', 'f8', (4,)), # 464; 3rd row affine transform ('slice_code', 'i4'), # 496; slice timing order ('xyzt_units', 'i4'), # 500; inits of pixdim[1..4] ('intent_code', 'i4'),# 504; NIFTI intent code ('intent_name', 'S16'), # 508; name or meaning of data ('dim_info', 'u1'), # 524; MRI slice ordering code ('unused_str', 'S15'), # 525; unused, filled with \0 ] # total 540 # Full header numpy dtype header_dtype = np.dtype(header_dtd) class Nifti2Header(Nifti1Header): """ Class for NIfTI2 header NIfTI2 is a slightly simplified variant of NIfTI1 which replaces 32-bit floats with 64-bit floats, and increases some integer widths to 32 or 64 bits. """ template_dtype = header_dtype pair_vox_offset = 0 single_vox_offset = 544 # Magics for single and pair pair_magic = b'ni2' single_magic = b'n+2' # Size of header in sizeof_hdr field sizeof_hdr = 540 # Quaternion threshold near 0, based on float64 preicision quaternion_threshold = -np.finfo(np.float64).eps * 3 def get_data_shape(self): ''' Get shape of data Examples -------- >>> hdr = Nifti2Header() >>> hdr.get_data_shape() (0,) >>> hdr.set_data_shape((1,2,3)) >>> hdr.get_data_shape() (1, 2, 3) Expanding number of dimensions gets default zooms >>> hdr.get_zooms() (1.0, 1.0, 1.0) Notes ----- Does not use Nifti1 freesurfer hack for large vectors described in :meth:`Nifti1Header.set_data_shape` ''' return AnalyzeHeader.get_data_shape(self) def set_data_shape(self, shape): ''' Set shape of data If ``ndims == len(shape)`` then we set zooms for dimensions higher than ``ndims`` to 1.0 Parameters ---------- shape : sequence sequence of integers specifying data array shape Notes ----- Does not apply nifti1 Freesurfer hack for long vectors (see :meth:`Nifti1Header.set_data_shape`) ''' AnalyzeHeader.set_data_shape(self, shape) @classmethod def default_structarr(klass, endianness=None): ''' Create empty header binary block with given endianness ''' hdr_data = super(Nifti2Header, klass).default_structarr(endianness) hdr_data['eol_check'] = (13, 10, 26, 10) return hdr_data ''' Checks only below here ''' @classmethod def _get_checks(klass): # Add our own checks return (super(Nifti2Header, klass)._get_checks() + (klass._chk_eol_check,)) @staticmethod def _chk_eol_check(hdr, fix=False): rep = Report(HeaderDataError) if np.all(hdr['eol_check'] == (13, 10, 26, 10)): return hdr, rep if np.all(hdr['eol_check'] == 0): rep.problem_level = 20 rep.problem_msg = 'EOL check all 0' if fix: hdr['eol_check'] = (13, 10, 26, 10) rep.fix_msg = 'setting EOL check to 13, 10, 26, 10' return hdr, rep rep.problem_level = 40 rep.problem_msg = ('EOL check not 0 or 13, 10, 26, 10; data may be ' 'corrupted by EOL conversion') if fix: hdr['eol_check'] = (13, 10, 26, 10) rep.fix_msg = 'setting EOL check to 13, 10, 26, 10' return hdr, rep class Nifti2PairHeader(Nifti2Header): ''' Class for NIfTI2 pair header ''' # Signal whether this is single (header + data) file is_single = False class Nifti2Pair(Nifti1Pair): """ Class for NIfTI2 format image, header pair """ header_class = Nifti2PairHeader class Nifti2Image(Nifti1Image): """ Class for single file NIfTI2 format image """ header_class = Nifti2Header def load(filename): """ Load NIfTI2 single or pair image from `filename` Parameters ---------- filename : str filename of image to be loaded Returns ------- img : Nifti2Image or Nifti2Pair nifti2 single or pair image instance Raises ------ ImageFileError if `filename` doesn't look like nifti2; IOError if `filename` does not exist. """ try: img = Nifti2Image.load(filename) except ImageFileError: return Nifti2Pair.load(filename) return img def save(img, filename): """ Save NIfTI2 single or pair to `filename` Parameters ---------- filename : str filename to which to save image """ try: Nifti2Image.instance_to_filename(img, filename) except ImageFileError: Nifti2Pair.instance_to_filename(img, filename)