Description
PAR files have a field in the image information definition called "image pixel size (in bits)".
We currently assume that this value refers to the number of bits of a signed integer (int16, int32).
Bennett Landman has a PAR / REC toolbox at http://iacl.ece.jhu.edu/~bennett/research.shtml that includes the following code (in loadREC.m
):
switch(par.scn.pix_bits)
case 32
type = 'float32';
case 16
type = 'uint16'; % fixed?
case 8
type='uint8';
otherwise
error('Unsupported data type');
end
So, he thinks the ints are unsigned and that '32' means float not (u)int32.
We have test images at:
- http://psydata.ovgu.de/philips_achieva_testfiles/conversion/
- http://psydata.ovgu.de/philips_achieva_testfiles/conversion2/
- https://github.com/yarikoptic/nitest-balls1
All of the images are '16' bit width, so we can't check what '8' or '32' mean from these images.
These sets of PAR / REC data files have matching NIfTI files written by the Philips software. These all have NIfTI image data type <i2
(signed little-endian int16). The unscaled data array is identical between our PAR / REC conversion and the Philips conversion to NIfTI. Here's loading in the nitest-balls1
directory:
In [24]: import numpy as np
In [25]: import nibabel as nib
In [27]: n_img = nib.load('NIFTI/T1.nii.gz')
In [28]: p_img = nib.load('PARREC/T1.PAR')
In [29]: np.all(n_img.dataobj.get_unscaled() == p_img.dataobj.get_unscaled())
Out[29]: memmap(True, dtype=bool)
Only one of all the sample images has a negative minimum both on Philips conversion and our conversion - nitest-balls1/PARREC/fieldmap.PAR
/ nitest-balls1/NIFTI1/fieldmap.nii.gz
:
In [30]: p_img = nib.load('PARREC/fieldmap.PAR')
In [31]: n_img = nib.load('NIFTI/fieldmap.nii.gz')
In [32]: np.all(n_img.dataobj.get_unscaled() == p_img.dataobj.get_unscaled())
Out[32]: False
In [33]: n_img.get_data().min()
Out[33]: -500.0
In [34]: p_img.get_data().min()
Out[34]: memmap(-500.0)
EDIT - these negative values in fact came from the data scaling and not the raw data.
In this case, the written NIfTI image is 3D shape (80, 80, 10), and our image is 4D with two volumes, shape (80, 80, 10, 2).
Loading the equivalent data in DICOM:
In [50]: import nibabel.nicom.dicomreaders as dcr
In [51]: img = dcr.wrapper_from_file('DICOM/fieldmap.dcm')
In [52]: res = img.get_pixel_array()
In [53]: res.shape
Out[53]: (20, 80, 80)
In [54]: res.dtype
Out[54]: dtype('uint16')
Investigating further, the T1 image also has 'uint16' DICOM data:
In [63]: t1_img = dcr.wrapper_from_file('DICOM/T1.dcm')
In [64]: t1_res = t1_img.get_pixel_array()
In [65]: t1_res.shape
Out[65]: (10, 80, 80)
In [66]: t1_res.dtype
Out[66]: dtype('uint16')
In [67]: t1_p_img = nib.load('PARREC/T1.PAR')
In [68]: np.all(t1_res.T == t1_p_img.dataobj.get_unscaled())
Out[68]: memmap(True, dtype=bool)
So far, it is possible that we and the Philips NIfTI conversions are wrong in interpreting the datatype as 'int16' instead of 'uint16'.