Skip to content

Commit 920719a

Browse files
authored
Merge pull request #9 from oesteban/tst/add-base-battery
ENH: Setting up a battery of tests
2 parents ca37d30 + bb08caa commit 920719a

File tree

8 files changed

+178
-52
lines changed

8 files changed

+178
-52
lines changed

nitransforms/conftest.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
import nibabel as nb
66
import pytest
77
import tempfile
8+
import pkg_resources
9+
10+
SOMEONES_ANATOMY = pkg_resources.resource_filename(
11+
'nitransforms', 'tests/data/someones_anatomy.nii.gz')
12+
_data = None
813

914

1015
@pytest.fixture(autouse=True)
@@ -25,3 +30,41 @@ def doctest_autoimport(doctest_namespace):
2530
doctest_namespace['testfile'] = nifti_fname
2631
yield
2732
tmpdir.cleanup()
33+
34+
35+
@pytest.fixture
36+
def data_path():
37+
"""Return the test data folder."""
38+
return os.path.join(os.path.dirname(__file__), 'tests/data')
39+
40+
41+
@pytest.fixture
42+
def get_data():
43+
"""Generate data in the requested orientation."""
44+
global _data
45+
46+
if _data is not None:
47+
return _data
48+
49+
img = nb.load(SOMEONES_ANATOMY)
50+
imgaff = img.affine
51+
52+
_data = {'RAS': img}
53+
newaff = imgaff.copy()
54+
newaff[0, 0] *= -1.0
55+
newaff[0, 3] = imgaff.dot(np.hstack((np.array(img.shape[:3]) - 1, 1.0)))[0]
56+
_data['LAS'] = nb.Nifti1Image(np.flip(img.get_fdata(), 0), newaff, img.header)
57+
newaff = imgaff.copy()
58+
newaff[0, 0] *= -1.0
59+
newaff[1, 1] *= -1.0
60+
newaff[:2, 3] = imgaff.dot(np.hstack((np.array(img.shape[:3]) - 1, 1.0)))[:2]
61+
_data['LPS'] = nb.Nifti1Image(np.flip(np.flip(img.get_fdata(), 0), 1), newaff, img.header)
62+
A = nb.volumeutils.shape_zoom_affine(img.shape, img.header.get_zooms(), x_flip=False)
63+
R = nb.affines.from_matvec(nb.eulerangles.euler2mat(x=0.09, y=0.001, z=0.001))
64+
newaff = R.dot(A)
65+
oblique_img = nb.Nifti1Image(img.get_fdata(), newaff, img.header)
66+
oblique_img.header.set_qform(newaff, 1)
67+
oblique_img.header.set_sform(newaff, 1)
68+
_data['oblique'] = oblique_img
69+
70+
return _data

nitransforms/tests/data/affine-LAS-itk.tfm

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
affine-RAS.itk.tfm

nitransforms/tests/test_transform.py

Lines changed: 113 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,73 +2,135 @@
22
# vi: set ft=python sts=4 ts=4 sw=4 et:
33
"""Tests of the transform module."""
44
import os
5-
import numpy as np
6-
from numpy.testing import assert_array_equal, assert_almost_equal, \
7-
assert_array_almost_equal
85
import pytest
6+
import numpy as np
7+
from subprocess import check_call
98

10-
from nibabel.loadsave import load as loadimg
11-
from nibabel.nifti1 import Nifti1Image
9+
import nibabel as nb
1210
from nibabel.eulerangles import euler2mat
1311
from nibabel.affines import from_matvec
14-
from ..patched import shape_zoom_affine
15-
from .. import linear as nbl
16-
from nibabel.testing import (assert_equal, assert_not_equal, assert_true,
17-
assert_false, assert_raises,
18-
suppress_warnings, assert_dt_equal)
1912
from nibabel.tmpdirs import InTemporaryDirectory
13+
from .. import linear as nbl
14+
from .utils import assert_affines_by_filename
2015

21-
data_path = os.path.join(os.path.dirname(__file__), 'data')
22-
SOMEONES_ANATOMY = os.path.join(data_path, 'someones_anatomy.nii.gz')
16+
TESTS_BORDER_TOLERANCE = 0.05
17+
APPLY_LINEAR_CMD = {
18+
'fsl': """\
19+
flirt -setbackground 0 -interp nearestneighbour -in {moving} -ref {reference} \
20+
-applyxfm -init {transform} -out resampled.nii.gz\
21+
""".format,
22+
'itk': """\
23+
antsApplyTransforms -d 3 -r {reference} -i {moving} \
24+
-o resampled.nii.gz -n NearestNeighbor -t {transform} --float\
25+
""".format,
26+
'afni': """\
27+
3dAllineate -base {reference} -input {moving} \
28+
-prefix resampled.nii.gz -1Dmatrix_apply {transform} -final NN\
29+
""".format,
30+
}
2331

2432

25-
@pytest.mark.parametrize('image_orientation', ['RAS', 'LAS', 'LPS', 'oblique'])
26-
def test_affines_save(image_orientation):
27-
"""Check implementation of exporting affines to formats."""
28-
# Generate test transform
29-
img = loadimg(SOMEONES_ANATOMY)
30-
imgaff = img.affine
31-
32-
if image_orientation == 'LAS':
33-
newaff = imgaff.copy()
34-
newaff[0, 0] *= -1.0
35-
newaff[0, 3] = imgaff.dot(np.hstack((np.array(img.shape[:3]) - 1, 1.0)))[0]
36-
img = Nifti1Image(np.flip(img.get_fdata(), 0), newaff, img.header)
37-
elif image_orientation == 'LPS':
38-
newaff = imgaff.copy()
39-
newaff[0, 0] *= -1.0
40-
newaff[1, 1] *= -1.0
41-
newaff[:2, 3] = imgaff.dot(np.hstack((np.array(img.shape[:3]) - 1, 1.0)))[:2]
42-
img = Nifti1Image(np.flip(np.flip(img.get_fdata(), 0), 1), newaff, img.header)
43-
elif image_orientation == 'oblique':
44-
A = shape_zoom_affine(img.shape, img.header.get_zooms(), x_flip=False)
45-
R = from_matvec(euler2mat(x=0.09, y=0.001, z=0.001))
46-
newaff = R.dot(A)
47-
img = Nifti1Image(img.get_fdata(), newaff, img.header)
48-
img.header.set_qform(newaff, 1)
49-
img.header.set_sform(newaff, 1)
33+
@pytest.mark.parametrize('image_orientation', [
34+
'RAS', 'LAS', 'LPS', # 'oblique',
35+
])
36+
@pytest.mark.parametrize('sw_tool', ['itk', 'fsl', 'afni'])
37+
def test_linear_load(tmpdir, data_path, get_data, image_orientation, sw_tool):
38+
"""Check implementation of loading affines from formats."""
39+
tmpdir.chdir()
5040

41+
img = get_data[image_orientation]
42+
img.to_filename('img.nii.gz')
43+
44+
# Generate test transform
5145
T = from_matvec(euler2mat(x=0.9, y=0.001, z=0.001), [4.0, 2.0, -1.0])
46+
xfm = nbl.Affine(T)
47+
xfm.reference = img
48+
49+
ext = ''
50+
if sw_tool == 'itk':
51+
ext = '.tfm'
52+
53+
fname = 'affine-%s.%s%s' % (image_orientation, sw_tool, ext)
54+
xfm_fname = os.path.join(data_path, fname)
55+
56+
if sw_tool == 'fsl':
57+
with pytest.raises("ValueError"):
58+
loaded = nbl.load(xfm_fname, fmt=fname.split('.')[-1])
59+
with pytest.raises("ValueError"):
60+
loaded = nbl.load(xfm_fname, fmt=fname.split('.')[-1],
61+
reference='img.nii.gz')
62+
with pytest.raises("ValueError"):
63+
loaded = nbl.load(xfm_fname, fmt=fname.split('.')[-1],
64+
moving='img.nii.gz')
65+
66+
loaded = nbl.load(xfm_fname, fmt=fname.split('.')[-1],
67+
moving='img.nii.gz', reference='img.nii.gz')
68+
if sw_tool == 'afni':
69+
with pytest.raises("ValueError"):
70+
loaded = nbl.load(xfm_fname, fmt=fname.split('.')[-1])
71+
72+
loaded = nbl.load(xfm_fname, fmt=fname.split('.')[-1],
73+
reference='img.nii.gz')
5274

75+
assert loaded == xfm
76+
77+
78+
@pytest.mark.parametrize('image_orientation', [
79+
'RAS', 'LAS', 'LPS', # 'oblique',
80+
])
81+
@pytest.mark.parametrize('sw_tool', ['itk', 'fsl', 'afni'])
82+
def test_linear_save(data_path, get_data, image_orientation, sw_tool):
83+
"""Check implementation of exporting affines to formats."""
84+
img = get_data[image_orientation]
85+
# Generate test transform
86+
T = from_matvec(euler2mat(x=0.9, y=0.001, z=0.001), [4.0, 2.0, -1.0])
5387
xfm = nbl.Affine(T)
5488
xfm.reference = img
5589

56-
itk = nbl.load(os.path.join(data_path, 'affine-%s-itk.tfm' % image_orientation),
57-
fmt='itk')
58-
fsl = np.loadtxt(os.path.join(data_path, 'affine-%s.fsl' % image_orientation))
59-
afni = np.loadtxt(os.path.join(data_path, 'affine-%s.afni' % image_orientation))
90+
ext = ''
91+
if sw_tool == 'itk':
92+
ext = '.tfm'
6093

6194
with InTemporaryDirectory():
62-
xfm.to_filename('M.tfm', fmt='itk')
63-
xfm.to_filename('M.fsl', fmt='fsl')
64-
xfm.to_filename('M.afni', fmt='afni')
95+
xfm_fname1 = 'M.%s%s' % (sw_tool, ext)
96+
xfm.to_filename(xfm_fname1, fmt=sw_tool)
97+
98+
xfm_fname2 = os.path.join(
99+
data_path, 'affine-%s.%s%s' % (image_orientation, sw_tool, ext))
100+
assert_affines_by_filename(xfm_fname1, xfm_fname2)
101+
102+
103+
@pytest.mark.parametrize('image_orientation', [
104+
'RAS', 'LAS', 'LPS', # 'oblique',
105+
])
106+
@pytest.mark.parametrize('sw_tool', ['itk', 'fsl', 'afni'])
107+
def test_apply_linear_transform(tmpdir, data_path, get_data, image_orientation, sw_tool):
108+
"""Check implementation of exporting affines to formats."""
109+
tmpdir.chdir()
110+
111+
img = get_data[image_orientation]
112+
# Generate test transform
113+
T = from_matvec(euler2mat(x=0.9, y=0.001, z=0.001), [4.0, 2.0, -1.0])
114+
xfm = nbl.Affine(T)
115+
xfm.reference = img
116+
117+
ext = ''
118+
if sw_tool == 'itk':
119+
ext = '.tfm'
65120

66-
nb_itk = nbl.load('M.tfm', fmt='itk')
67-
nb_fsl = np.loadtxt('M.fsl')
68-
nb_afni = np.loadtxt('M.afni')
121+
img.to_filename('img.nii.gz')
122+
xfm_fname = 'M.%s%s' % (sw_tool, ext)
123+
xfm.to_filename(xfm_fname, fmt=sw_tool)
69124

70-
assert_equal(itk, nb_itk)
71-
assert_almost_equal(fsl, nb_fsl)
72-
assert_almost_equal(afni, nb_afni)
125+
cmd = APPLY_LINEAR_CMD[sw_tool](
126+
transform=os.path.abspath(xfm_fname),
127+
reference=os.path.abspath('img.nii.gz'),
128+
moving=os.path.abspath('img.nii.gz'))
129+
exit_code = check_call([cmd], shell=True)
130+
assert exit_code == 0
131+
sw_moved = nb.load('resampled.nii.gz')
73132

74-
# Create version not aligned to canonical
133+
nt_moved = xfm.resample(img, order=0)
134+
diff = sw_moved.get_fdata() - nt_moved.get_fdata()
135+
# A certain tolerance is necessary because of resampling at borders
136+
assert (np.abs(diff) > 1e-3).sum() / diff.size < TESTS_BORDER_TOLERANCE

nitransforms/tests/utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Utilities for testing."""
2+
from pathlib import Path
3+
import numpy as np
4+
5+
from .. import linear as nbl
6+
7+
8+
def assert_affines_by_filename(affine1, affine2):
9+
"""Check affines by filename."""
10+
affine1 = Path(affine1)
11+
affine2 = Path(affine2)
12+
assert affine1.suffix == affine2.suffix, 'affines of different type'
13+
14+
if affine1.suffix.endswith('.tfm'): # An ITK transform
15+
xfm1 = nbl.load(str(affine1), fmt='itk')
16+
xfm2 = nbl.load(str(affine2), fmt='itk')
17+
assert xfm1 == xfm2
18+
else:
19+
xfm1 = np.loadtxt(str(affine1))
20+
xfm2 = np.loadtxt(str(affine2))
21+
np.testing.assert_almost_equal(xfm1, xfm2)

0 commit comments

Comments
 (0)