Skip to content

Commit 15e16b4

Browse files
committed
Merge pull request #420 from matthew-brett/master
MRG: add Gregory Lee's new PARREC data Test parrec data with multiple rotations. Modify translation order to minimize error between our rotations and those from the NIfTI images in the conversion.
2 parents 0c6a551 + 58c748e commit 15e16b4

File tree

5 files changed

+41
-9
lines changed

5 files changed

+41
-9
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@
1010
[submodule "nibabel-data/nitest-freesurfer"]
1111
path = nibabel-data/nitest-freesurfer
1212
url = https://bitbucket.org/nipy/nitest-freesurfer.git
13+
[submodule "nibabel-data/parrec_oblique"]
14+
path = nibabel-data/parrec_oblique
15+
url = https://github.com/grlee77/parrec_oblique.git

nibabel-data/parrec_oblique

Submodule parrec_oblique added at 45d4d44

nibabel/parrec.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@
126126
[0, 0, 0, 1]])
127127
)
128128

129+
DEG2RAD = np.pi / 180.
130+
129131
# General information dict definitions
130132
# assign props to PAR header entries
131133
# values are: (shortname[, dtype[, shape]])
@@ -892,11 +894,13 @@ def get_affine(self, origin='scanner'):
892894
"Unknown slice orientation ({0}).".format(slice_orientation))
893895
# hdr has deg, we need radians
894896
# Order is [ap, fh, rl]
895-
ang_rad = self.general_info['angulation'] * np.pi / 180.0
896-
# euler2mat accepts z, y, x angles and does rotation around z, y, x
897-
# axes in that order. It's possible that PAR assumes rotation in a
898-
# different order, we still need some relevant data to test this
899-
rot = from_matvec(euler2mat(*ang_rad[::-1]), [0, 0, 0])
897+
ap_rot, fh_rot, rl_rot = self.general_info['angulation'] * DEG2RAD
898+
Mx = euler2mat(x=ap_rot)
899+
My = euler2mat(y=fh_rot)
900+
Mz = euler2mat(z=rl_rot)
901+
# By trial and error, this unexpected order of rotations seem to give
902+
# the closest to the observed (converted NIfTI) affine.
903+
rot = from_matvec(dot_reduce(Mz, Mx, My))
900904
# compose the PSL affine
901905
psl_aff = dot_reduce(rot, permute_to_psl, zoomer, to_center)
902906
if origin == 'scanner':

nibabel/tests/test_parrec_data.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from .. import load as top_load
1111
from ..parrec import load
12+
from ..affines import voxel_sizes
1213

1314
from .nibabel_data import get_nibabel_data, needs_nibabel_data
1415

@@ -19,9 +20,10 @@
1920
from numpy.testing import assert_almost_equal
2021

2122
BALLS = pjoin(get_nibabel_data(), 'nitest-balls1')
23+
OBLIQUE = pjoin(get_nibabel_data(), 'parrec_oblique')
2224

2325
# Amount by which affine translation differs from NIFTI conversion
24-
AFF_OFF = [-0.93644031, -0.95572686, 0.03288748]
26+
AFF_OFF = [-0.93575081, -0.95657335, 0.03264122]
2527

2628

2729
@needs_nibabel_data('nitest-balls1')
@@ -45,8 +47,8 @@ def test_loading():
4547
aff_off = pimg.affine[:3, 3] - nimg.affine[:3, 3]
4648
assert_almost_equal(aff_off, AFF_OFF, 4)
4749
# The difference is max in the order of 0.5 voxel
48-
vox_sizes = np.sqrt((nimg.affine[:3, :3] ** 2).sum(axis=0))
49-
assert_true(np.all(np.abs(aff_off / vox_sizes) <= 0.5))
50+
vox_sizes = voxel_sizes(nimg.affine)
51+
assert_true(np.all(np.abs(aff_off / vox_sizes) <= 0.501))
5052
# The data is very close, unless it's the fieldmap
5153
if par_root != 'fieldmap':
5254
assert_true(np.allclose(pimg.dataobj, nimg.dataobj))
@@ -63,3 +65,25 @@ def test_fieldmap():
6365
load(fieldmap_par)
6466
top_load(fieldmap_nii)
6567
raise SkipTest('Fieldmap remains puzzling')
68+
69+
70+
@needs_nibabel_data('parrec_oblique')
71+
def test_oblique_loading():
72+
# Test loading of oblique parrec files
73+
for par in glob(pjoin(OBLIQUE, 'PARREC', '*.PAR')):
74+
par_root, ext = splitext(basename(par))
75+
# Check we can load the image
76+
pimg = load(par)
77+
assert_equal(pimg.shape, (560, 560, 1))
78+
# Compare against NIFTI if present
79+
nifti_fname = pjoin(OBLIQUE, 'NIFTI', par_root + '.nii')
80+
nimg = top_load(nifti_fname)
81+
assert_almost_equal(nimg.affine[:3, :3], pimg.affine[:3, :3], 3)
82+
# The translation part is always off
83+
# The ammount differs by rotation
84+
aff_off = pimg.affine[:3, 3] - nimg.affine[:3, 3]
85+
# The difference is max in the order of 0.5 voxel
86+
vox_sizes = voxel_sizes(nimg.affine)
87+
assert_true(np.all(np.abs(aff_off / vox_sizes) <= 0.5))
88+
# The data is very close
89+
assert_true(np.allclose(pimg.dataobj, nimg.dataobj))

nibabel/tests/test_scripts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ def test_parrec2nii_with_data():
263263
assert_almost_equal(aff_off, AFF_OFF, 3)
264264
# The difference is max in the order of 0.5 voxel
265265
vox_sizes = vox_size(philips_img.affine)
266-
assert_true(np.all(np.abs(aff_off / vox_sizes) <= 0.5))
266+
assert_true(np.all(np.abs(aff_off / vox_sizes) <= 0.501))
267267
# The data is very close, unless it's the fieldmap
268268
if par_root != 'fieldmap':
269269
conved_data_lps = flip_axis(conved_img.dataobj, 1)

0 commit comments

Comments
 (0)