Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Setting up a battery of tests #9

Merged
merged 4 commits into from
Oct 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions nitransforms/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
import nibabel as nb
import pytest
import tempfile
import pkg_resources

SOMEONES_ANATOMY = pkg_resources.resource_filename(
'nitransforms', 'tests/data/someones_anatomy.nii.gz')
_data = None


@pytest.fixture(autouse=True)
Expand All @@ -25,3 +30,41 @@ def doctest_autoimport(doctest_namespace):
doctest_namespace['testfile'] = nifti_fname
yield
tmpdir.cleanup()


@pytest.fixture
def data_path():
"""Return the test data folder."""
return os.path.join(os.path.dirname(__file__), 'tests/data')


@pytest.fixture
def get_data():
"""Generate data in the requested orientation."""
global _data

if _data is not None:
return _data

img = nb.load(SOMEONES_ANATOMY)
imgaff = img.affine

_data = {'RAS': img}
newaff = imgaff.copy()
newaff[0, 0] *= -1.0
newaff[0, 3] = imgaff.dot(np.hstack((np.array(img.shape[:3]) - 1, 1.0)))[0]
_data['LAS'] = nb.Nifti1Image(np.flip(img.get_fdata(), 0), newaff, img.header)
newaff = imgaff.copy()
newaff[0, 0] *= -1.0
newaff[1, 1] *= -1.0
newaff[:2, 3] = imgaff.dot(np.hstack((np.array(img.shape[:3]) - 1, 1.0)))[:2]
_data['LPS'] = nb.Nifti1Image(np.flip(np.flip(img.get_fdata(), 0), 1), newaff, img.header)
A = nb.volumeutils.shape_zoom_affine(img.shape, img.header.get_zooms(), x_flip=False)
R = nb.affines.from_matvec(nb.eulerangles.euler2mat(x=0.09, y=0.001, z=0.001))
newaff = R.dot(A)
oblique_img = nb.Nifti1Image(img.get_fdata(), newaff, img.header)
oblique_img.header.set_qform(newaff, 1)
oblique_img.header.set_sform(newaff, 1)
_data['oblique'] = oblique_img

return _data
1 change: 0 additions & 1 deletion nitransforms/tests/data/affine-LAS-itk.tfm

This file was deleted.

1 change: 1 addition & 0 deletions nitransforms/tests/data/affine-LAS.itk.tfm
164 changes: 113 additions & 51 deletions nitransforms/tests/test_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,73 +2,135 @@
# vi: set ft=python sts=4 ts=4 sw=4 et:
"""Tests of the transform module."""
import os
import numpy as np
from numpy.testing import assert_array_equal, assert_almost_equal, \
assert_array_almost_equal
import pytest
import numpy as np
from subprocess import check_call

from nibabel.loadsave import load as loadimg
from nibabel.nifti1 import Nifti1Image
import nibabel as nb
from nibabel.eulerangles import euler2mat
from nibabel.affines import from_matvec
from ..patched import shape_zoom_affine
from .. import linear as nbl
from nibabel.testing import (assert_equal, assert_not_equal, assert_true,
assert_false, assert_raises,
suppress_warnings, assert_dt_equal)
from nibabel.tmpdirs import InTemporaryDirectory
from .. import linear as nbl
from .utils import assert_affines_by_filename

data_path = os.path.join(os.path.dirname(__file__), 'data')
SOMEONES_ANATOMY = os.path.join(data_path, 'someones_anatomy.nii.gz')
TESTS_BORDER_TOLERANCE = 0.05
APPLY_LINEAR_CMD = {
'fsl': """\
flirt -setbackground 0 -interp nearestneighbour -in {moving} -ref {reference} \
-applyxfm -init {transform} -out resampled.nii.gz\
""".format,
'itk': """\
antsApplyTransforms -d 3 -r {reference} -i {moving} \
-o resampled.nii.gz -n NearestNeighbor -t {transform} --float\
""".format,
'afni': """\
3dAllineate -base {reference} -input {moving} \
-prefix resampled.nii.gz -1Dmatrix_apply {transform} -final NN\
""".format,
}


@pytest.mark.parametrize('image_orientation', ['RAS', 'LAS', 'LPS', 'oblique'])
def test_affines_save(image_orientation):
"""Check implementation of exporting affines to formats."""
# Generate test transform
img = loadimg(SOMEONES_ANATOMY)
imgaff = img.affine

if image_orientation == 'LAS':
newaff = imgaff.copy()
newaff[0, 0] *= -1.0
newaff[0, 3] = imgaff.dot(np.hstack((np.array(img.shape[:3]) - 1, 1.0)))[0]
img = Nifti1Image(np.flip(img.get_fdata(), 0), newaff, img.header)
elif image_orientation == 'LPS':
newaff = imgaff.copy()
newaff[0, 0] *= -1.0
newaff[1, 1] *= -1.0
newaff[:2, 3] = imgaff.dot(np.hstack((np.array(img.shape[:3]) - 1, 1.0)))[:2]
img = Nifti1Image(np.flip(np.flip(img.get_fdata(), 0), 1), newaff, img.header)
elif image_orientation == 'oblique':
A = shape_zoom_affine(img.shape, img.header.get_zooms(), x_flip=False)
R = from_matvec(euler2mat(x=0.09, y=0.001, z=0.001))
newaff = R.dot(A)
img = Nifti1Image(img.get_fdata(), newaff, img.header)
img.header.set_qform(newaff, 1)
img.header.set_sform(newaff, 1)
@pytest.mark.parametrize('image_orientation', [
'RAS', 'LAS', 'LPS', # 'oblique',
])
@pytest.mark.parametrize('sw_tool', ['itk', 'fsl', 'afni'])
def test_linear_load(tmpdir, data_path, get_data, image_orientation, sw_tool):
"""Check implementation of loading affines from formats."""
tmpdir.chdir()

img = get_data[image_orientation]
img.to_filename('img.nii.gz')

# Generate test transform
T = from_matvec(euler2mat(x=0.9, y=0.001, z=0.001), [4.0, 2.0, -1.0])
xfm = nbl.Affine(T)
xfm.reference = img

ext = ''
if sw_tool == 'itk':
ext = '.tfm'

fname = 'affine-%s.%s%s' % (image_orientation, sw_tool, ext)
xfm_fname = os.path.join(data_path, fname)

if sw_tool == 'fsl':
with pytest.raises("ValueError"):
loaded = nbl.load(xfm_fname, fmt=fname.split('.')[-1])
with pytest.raises("ValueError"):
loaded = nbl.load(xfm_fname, fmt=fname.split('.')[-1],
reference='img.nii.gz')
with pytest.raises("ValueError"):
loaded = nbl.load(xfm_fname, fmt=fname.split('.')[-1],
moving='img.nii.gz')

loaded = nbl.load(xfm_fname, fmt=fname.split('.')[-1],
moving='img.nii.gz', reference='img.nii.gz')
if sw_tool == 'afni':
with pytest.raises("ValueError"):
loaded = nbl.load(xfm_fname, fmt=fname.split('.')[-1])

loaded = nbl.load(xfm_fname, fmt=fname.split('.')[-1],
reference='img.nii.gz')

assert loaded == xfm


@pytest.mark.parametrize('image_orientation', [
'RAS', 'LAS', 'LPS', # 'oblique',
])
@pytest.mark.parametrize('sw_tool', ['itk', 'fsl', 'afni'])
def test_linear_save(data_path, get_data, image_orientation, sw_tool):
"""Check implementation of exporting affines to formats."""
img = get_data[image_orientation]
# Generate test transform
T = from_matvec(euler2mat(x=0.9, y=0.001, z=0.001), [4.0, 2.0, -1.0])
xfm = nbl.Affine(T)
xfm.reference = img

itk = nbl.load(os.path.join(data_path, 'affine-%s-itk.tfm' % image_orientation),
fmt='itk')
fsl = np.loadtxt(os.path.join(data_path, 'affine-%s.fsl' % image_orientation))
afni = np.loadtxt(os.path.join(data_path, 'affine-%s.afni' % image_orientation))
ext = ''
if sw_tool == 'itk':
ext = '.tfm'

with InTemporaryDirectory():
xfm.to_filename('M.tfm', fmt='itk')
xfm.to_filename('M.fsl', fmt='fsl')
xfm.to_filename('M.afni', fmt='afni')
xfm_fname1 = 'M.%s%s' % (sw_tool, ext)
xfm.to_filename(xfm_fname1, fmt=sw_tool)

xfm_fname2 = os.path.join(
data_path, 'affine-%s.%s%s' % (image_orientation, sw_tool, ext))
assert_affines_by_filename(xfm_fname1, xfm_fname2)


@pytest.mark.parametrize('image_orientation', [
'RAS', 'LAS', 'LPS', # 'oblique',
])
@pytest.mark.parametrize('sw_tool', ['itk', 'fsl', 'afni'])
def test_apply_linear_transform(tmpdir, data_path, get_data, image_orientation, sw_tool):
"""Check implementation of exporting affines to formats."""
tmpdir.chdir()

img = get_data[image_orientation]
# Generate test transform
T = from_matvec(euler2mat(x=0.9, y=0.001, z=0.001), [4.0, 2.0, -1.0])
xfm = nbl.Affine(T)
xfm.reference = img

ext = ''
if sw_tool == 'itk':
ext = '.tfm'

nb_itk = nbl.load('M.tfm', fmt='itk')
nb_fsl = np.loadtxt('M.fsl')
nb_afni = np.loadtxt('M.afni')
img.to_filename('img.nii.gz')
xfm_fname = 'M.%s%s' % (sw_tool, ext)
xfm.to_filename(xfm_fname, fmt=sw_tool)

assert_equal(itk, nb_itk)
assert_almost_equal(fsl, nb_fsl)
assert_almost_equal(afni, nb_afni)
cmd = APPLY_LINEAR_CMD[sw_tool](
transform=os.path.abspath(xfm_fname),
reference=os.path.abspath('img.nii.gz'),
moving=os.path.abspath('img.nii.gz'))
exit_code = check_call([cmd], shell=True)
assert exit_code == 0
sw_moved = nb.load('resampled.nii.gz')

# Create version not aligned to canonical
nt_moved = xfm.resample(img, order=0)
diff = sw_moved.get_fdata() - nt_moved.get_fdata()
# A certain tolerance is necessary because of resampling at borders
assert (np.abs(diff) > 1e-3).sum() / diff.size < TESTS_BORDER_TOLERANCE
21 changes: 21 additions & 0 deletions nitransforms/tests/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Utilities for testing."""
from pathlib import Path
import numpy as np

from .. import linear as nbl


def assert_affines_by_filename(affine1, affine2):
"""Check affines by filename."""
affine1 = Path(affine1)
affine2 = Path(affine2)
assert affine1.suffix == affine2.suffix, 'affines of different type'

if affine1.suffix.endswith('.tfm'): # An ITK transform
xfm1 = nbl.load(str(affine1), fmt='itk')
xfm2 = nbl.load(str(affine2), fmt='itk')
assert xfm1 == xfm2
else:
xfm1 = np.loadtxt(str(affine1))
xfm2 = np.loadtxt(str(affine2))
np.testing.assert_almost_equal(xfm1, xfm2)