From 9a99c71d1deb8caeded28a6bffccccacf5955a24 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Sun, 12 Mar 2017 17:39:18 +0000 Subject: [PATCH] test_image module rewritten so it generates its own data, instead of relying on an existing data set. --- tests/__init__.py | 22 +- tests/test_image.py | 608 ++++++++++++++++++++++++++++------------ tests/test_immv_imcp.py | 21 +- 3 files changed, 458 insertions(+), 193 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 439289331..09ffa0e26 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -60,24 +60,14 @@ def cleardir(dir): elif op.isdir(f): shutil.rmtree(f) -def make_random_image(filename, dims=(10, 10, 10), affine=None): - """Creates a NIFTI image with random data, returns the hash of said data. +def make_random_image(filename, dims=(10, 10, 10)): + """Creates a NIFTI1 image with random data, saves and + returns it. """ - if affine is None: - affine = np.eye(4) - - data = np.random.random(dims) - img = nib.Nifti1Image(data, affine) + data = np.array(np.random.random(dims) * 100, dtype=np.float32) + img = nib.Nifti1Image(data, np.eye(4)) nib.save(img, filename) - return hash(data.tobytes()) - -def check_image_hash(filename, datahash): - """Checks that the given NIFTI image matches the given hash. - """ - - img = nib.load(filename) - assert hash(img.get_data().tobytes()) == datahash - + return img diff --git a/tests/test_image.py b/tests/test_image.py index dfab39763..831ca2772 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -1,14 +1,19 @@ #!/usr/bin/env python # -# test_image.py - +# test_image.py - Unit tests for the fsl.data.image module. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""Unit tests for the fsl.data.image module. """ -import os.path as op + +import os.path as op +import itertools as it +import tempfile +import shutil +import glob import pytest -import glob import numpy as np import numpy.linalg as npla @@ -20,17 +25,73 @@ import fsl.data.constants as constants import fsl.data.image as fslimage import fsl.utils.path as fslpath +from . import make_random_image +from . import make_dummy_file + + +def make_image(filename, + imgtype=1, + dims=(10, 10, 10), + pixdims=(1, 1, 1), + dtype=np.float32): + """Convenience function which makes an image containing random data. + Saves and returns the nibabel object. + + imgtype == 0: ANALYZE + imgtype == 1: NIFTI1 + imgtype == 2: NIFTI2 + """ + + if imgtype == 0: filename = '{}.img'.format(filename) + else: filename = '{}.nii'.format(filename) + + if imgtype == 0: hdr = nib.AnalyzeHeader() + elif imgtype == 1: hdr = nib.Nifti1Header() + elif imgtype == 2: hdr = nib.Nifti2Header() + + pixdims = pixdims[:len(dims)] + + hdr.set_data_dtype(dtype) + hdr.set_data_shape(dims) + hdr.set_zooms([abs(p) for p in pixdims]) + + xform = np.eye(4) + for i, p in enumerate(pixdims): + xform[i, i] = p + + data = np.array(np.random.random(dims) * 100, dtype=dtype) + + if imgtype == 0: img = nib.AnalyzeImage(data, xform, hdr) + elif imgtype == 1: img = nib.Nifti1Image( data, xform, hdr) + elif imgtype == 2: img = nib.Nifti2Image( data, xform, hdr) + + nib.save(img, filename) + + return img + + # Need to test: -# - Create image from file name (create a temp .nii.gz) # - Create image from existing nibabel image # - Create image from numpy array # - calcRange # - loadData -def test_load(testdir): - assert testdir is not None +def test_load(): + """Create an Image from a file name. """ + + # notnifti.nii.gz is just a plain text + # file, the rest are NIFTI images + toCreate = ['compressed.nii.gz', + 'uncompressed.nii', + 'img_hdr_pair.img', + 'compressed_img_hdr_pair.img.gz', + 'ambiguous.nii', + 'ambiguous.img', + 'ambiguous.img.gz', + 'notnifti.nii.gz'] + shouldPass = ['compressed', 'compressed.nii.gz', @@ -52,48 +113,96 @@ def test_load(testdir): ('ambiguous', fslpath.PathError), ('notnifti', ImageFileError), ('notnifti.nii.gz', ImageFileError)] - - # Not raising an error means the test passes - for fname in shouldPass: - fslimage.Image(op.join(testdir, 'nifti_formats', fname)) - # These should raise an error - for fname, exc in shouldRaise: - with pytest.raises(exc): - fslimage.Image(op.join(testdir, 'nifti_formats', fname)) - + testdir = tempfile.mkdtemp() + for f in toCreate: + + if f.startswith('notnifti'): + make_dummy_file(op.join(testdir, f)) + else: + make_random_image(op.join(testdir, f)) + + # Not raising an error means the test passes + try: + for fname in shouldPass: + fslimage.Image(op.join(testdir, fname)) + + # These should raise an error + for fname, exc in shouldRaise: + with pytest.raises(exc): + fslimage.Image(op.join(testdir, fname)) + finally: + shutil.rmtree(testdir) + + +def test_Image_atts_analyze(): _test_Image_atts(0) +def test_Image_atts_nifti1(): _test_Image_atts(1) +def test_Image_atts_nifti2(): _test_Image_atts(2) +def _test_Image_atts(imgtype): + """Test that basic Nifti/Image attributes are correct. """ + + testdir = tempfile.mkdtemp() + allowedExts = fslimage.ALLOWED_EXTENSIONS + fileGroups = fslimage.FILE_GROUPS + + # (file, dims, pixdims, dtype) + dtypes = [np.uint8, np.int16, np.int32, np.float32, np.double] + dims = [(10, 1, 1), + (1, 10, 1), + (1, 1, 10), + (10, 10, 1), + (10, 1, 10), + (1, 10, 10), + (10, 10, 10), + (1, 1, 1, 5), + (10, 10, 1, 5), + (10, 1, 10, 5), + (1, 10, 10, 5), + (10, 10, 10, 5)] + pixdims = [(0.5, 0.5, 0.5, 2), + (1.0, 1.0, 1.0, 2), + (2.0, 2.0, 2.0, 2), + (1.0, 5.0, 1.0, 3)] + + tests = list(it.product(dims, pixdims, dtypes)) + paths = ['test{:03d}'.format(i) for i in range(len(tests))] + + for path, (dims, pixdims, dtype) in zip(paths, tests): + + ndims = len(dims) + pixdims = pixdims[:ndims] -def test_Image_atts(testdir): + path = op.abspath(op.join(testdir, path)) + make_image(path, imgtype, dims, pixdims, dtype) - allowedExts = fslimage.ALLOWED_EXTENSIONS + try: - # (file, dims, pixdims) - tests = [ - ('MNI152_T1_0.5mm', (364, 436, 364), (0.5, 0.5, 0.5), np.uint8), - ('MNI152_T1_1mm', (182, 218, 182), (1.0, 1.0, 1.0), np.int16), - ('MNI152_T1_2mm', (91, 109, 91), (2.0, 2.0, 2.0), np.int16), - ('MNI152_T1_2mm_4D', (91, 109, 91, 5), (2.0, 2.0, 2.0, 1.0), np.int16), - (op.join('nifti2D', 'MNI152_T1_2mm_sliceXY'), (91, 109, 1), (2.0, 2.0, 2.0), np.int16), - (op.join('nifti2D', 'MNI152_T1_2mm_sliceXZ'), (91, 1, 91), (2.0, 2.0, 2.0), np.int16), - (op.join('nifti2D', 'MNI152_T1_2mm_sliceYZ'), (1, 109, 91), (2.0, 2.0, 2.0), np.int16)] + for path, (dims, pixdims, dtype) in zip(paths, tests): - for path, dims, pixdims, dtype in tests: + ndims = len(dims) + pixdims = pixdims[:ndims] - path = op.abspath(op.join(testdir, path)) - i = fslimage.Image(path) + path = op.abspath(op.join(testdir, path)) + i = fslimage.Image(path) - assert tuple(i.shape) == tuple(dims) - assert tuple(i.pixdim) == tuple(pixdims) - assert tuple(i.nibImage.shape) == tuple(dims) - assert tuple(i.nibImage.header.get_zooms()) == tuple(pixdims) + assert tuple(i.shape) == tuple(dims) + assert tuple(i.pixdim) == tuple(pixdims) + assert tuple(i.nibImage.shape) == tuple(dims) + assert tuple(i.nibImage.header.get_zooms()) == tuple(pixdims) - assert i.dtype == dtype - assert i.name == op.basename(path) - assert i.dataSource == fslpath.addExt(path, allowedExts, mustExist=True) + assert i.dtype == dtype + assert i.name == op.basename(path) + assert i.dataSource == fslpath.addExt(path, + allowedExts=allowedExts, + mustExist=True, + fileGroups=fileGroups) + finally: + shutil.rmtree(testdir) def test_looksLikeImage(): + """Test the looksLikeImage function. """ # (file, expected) tests = [ @@ -111,9 +220,22 @@ def test_looksLikeImage(): assert fslimage.looksLikeImage(path) == expected -def test_addExt(testdir): +def test_addExt(): + """Test the addExt function. """ default = fslimage.defaultExt() + testdir = tempfile.mkdtemp() + + toCreate = [ + 'compressed.nii.gz', + 'uncompressed.nii', + 'img_hdr_pair.img', + 'compressed_img_hdr_pair.img.gz', + 'ambiguous.nii', + 'ambiguous.nii.gz', + 'ambiguous.img', + 'ambiguous.img.gz' + ] # (file, mustExist, expected) tests = [ @@ -141,85 +263,170 @@ def test_addExt(testdir): ('ambiguous.img.gz', True, 'ambiguous.img.gz'), ('ambiguous.hdr.gz', True, 'ambiguous.hdr.gz')] - for path, mustExist, expected in tests: - if mustExist: - path = op.join(testdir, 'nifti_formats', path) - expected = op.join(testdir, 'nifti_formats', expected) - - assert fslimage.addExt(path, mustExist) == expected - - with pytest.raises(fslimage.PathError): - path = op.join(testdir, 'nifti_formats', 'ambiguous') - fslimage.addExt(path, mustExist=True) - - -def test_Image_orientation(testdir): - - neuro = op.join(testdir, 'dtifit', 'neuro', 'dti_FA') - radio = op.join(testdir, 'dtifit', 'radio', 'dti_FA') - - neuro = fslimage.Image(neuro) - radio = fslimage.Image(radio) - - assert neuro.isNeurological() - assert not radio.isNeurological() + for path in toCreate: + path = op.abspath(op.join(testdir, path)) + make_random_image(path) + + try: + for path, mustExist, expected in tests: + + path = op.abspath(op.join(testdir, path)) + expected = op.abspath(op.join(testdir, expected)) + + assert fslimage.addExt(path, mustExist) == expected + + # Make sure that an ambiguous path fails + with pytest.raises(fslimage.PathError): + path = op.join(testdir, 'ambiguous') + fslimage.addExt(path, mustExist=True) + finally: + shutil.rmtree(testdir) + +def test_Image_orientation_analyze_neuro(): _test_Image_orientation(0, 'neuro') +def test_Image_orientation_analyze_radio(): _test_Image_orientation(0, 'radio') +def test_Image_orientation_nifti1_neuro(): _test_Image_orientation(1, 'neuro') +def test_Image_orientation_nifti1_radio(): _test_Image_orientation(1, 'radio') +def test_Image_orientation_nifti2_neuro(): _test_Image_orientation(2, 'neuro') +def test_Image_orientation_nifti2_radio(): _test_Image_orientation(2, 'radio') +def _test_Image_orientation(imgtype, voxorient): + """Test the Nifti.isNeurological and Nifti.getOrientation methods. """ + + testdir = tempfile.mkdtemp() + imagefile = op.join(testdir, 'image') + + # an image with RAS voxel storage order + # (affine has a positive determinant) + # is said to be "neurological", whereas + # an image with LAS voxel storage order + # (negative determinant - x axis must + # be flipped to bring it into RAS nifti + # world coordinates)) is said to be + # "radiological". The make_image function + # forms the affine from these pixdims. + if voxorient == 'neuro': pixdims = ( 1, 1, 1) + elif voxorient == 'radio': pixdims = (-1, 1, 1) + + make_image(imagefile, imgtype, (10, 10, 10), pixdims, np.float32) + + image = fslimage.Image(imagefile) + + # analyze images are always assumed to be + # stored in radiological (LAS) orientation + if imgtype == 0: + expectNeuroTest = False + expectvox0Orientation = constants.ORIENT_R2L + expectvox1Orientation = constants.ORIENT_P2A + expectvox2Orientation = constants.ORIENT_I2S + + elif voxorient == 'neuro': + expectNeuroTest = True + expectvox0Orientation = constants.ORIENT_L2R + expectvox1Orientation = constants.ORIENT_P2A + expectvox2Orientation = constants.ORIENT_I2S + else: + expectNeuroTest = False + expectvox0Orientation = constants.ORIENT_R2L + expectvox1Orientation = constants.ORIENT_P2A + expectvox2Orientation = constants.ORIENT_I2S + + try: + + assert image.isNeurological() == expectNeuroTest + + # All images should have the + # same orientation in the + # world coordinate system + assert image.getOrientation(0, np.eye(4)) == constants.ORIENT_L2R + assert image.getOrientation(1, np.eye(4)) == constants.ORIENT_P2A + assert image.getOrientation(2, np.eye(4)) == constants.ORIENT_I2S + + # But the voxel orientation + # is dependent on the affine + affine = image.voxToWorldMat + assert image.getOrientation(0, affine) == expectvox0Orientation + assert image.getOrientation(1, affine) == expectvox1Orientation + assert image.getOrientation(2, affine) == expectvox2Orientation + + finally: + shutil.rmtree(testdir) + + +def test_Image_sqforms_nifti1_normal(): _test_Image_sqforms(1, 1, 1) +def test_Image_sqforms_nifti1_nosform(): _test_Image_sqforms(1, 0, 1) +def test_Image_sqforms_nifti1_noqform(): _test_Image_sqforms(1, 1, 0) +def test_Image_sqforms_nifti1_nosqform(): _test_Image_sqforms(1, 1, 0) +def test_Image_sqforms_nifti2_normal(): _test_Image_sqforms(2, 1, 1) +def test_Image_sqforms_nifti2_nosform(): _test_Image_sqforms(2, 0, 1) +def test_Image_sqforms_nifti2_noqform(): _test_Image_sqforms(2, 1, 0) +def test_Image_sqforms_nifti2_nosqform(): _test_Image_sqforms(2, 1, 0) +def _test_Image_sqforms(imgtype, sformcode, qformcode): + """Test the Nifti.getXFormCode method, and the voxToWorldMat/worldToVoxMat + attributes for NIFTI images with the given sform/qform code combination. + """ + + testdir = tempfile.mkdtemp() + + imagefile = op.abspath(op.join(testdir, 'image.nii.gz')) + + # For an image with no s/q form, we expect the + # fallback affine - a simple scaling matrix. + # We add some offsets to the actual affine so + # we can distinguish it from the fallback affine. + scaleMat = np.diag([2, 2, 2, 1]) + invScaleMat = np.diag([0.5, 0.5, 0.5, 1]) + affine = np.array(scaleMat) + affine[:3, 3] = [25, 20, 20] + invAffine = npla.inv(affine) + + image = make_image(imagefile, imgtype, (10, 10, 10), (2, 2, 2), np.float32) + + image.set_sform(affine, sformcode) + image.set_qform(affine, qformcode) + image.update_header() + + nib.save(image, imagefile) + + # No s or qform - we expect the fallback affine + if sformcode == 0 and qformcode == 0: + expAffine = scaleMat + invExpAffine = invScaleMat + expCode = constants.NIFTI_XFORM_UNKNOWN + + # No sform, but valid qform - expect the affine + elif sformcode == 0 and qformcode > 0: + expAffine = affine + invExpAffine = invAffine + expCode = qformcode + + # Valid sform (qform irrelevant) - expect the affine + elif sformcode > 0: + expAffine = affine + invExpAffine = invAffine + expCode = sformcode + + image = fslimage.Image(imagefile) + + try: + assert np.all(np.isclose(image.voxToWorldMat, expAffine)) + assert np.all(np.isclose(image.worldToVoxMat, invExpAffine)) - # Both images should have the - # same orientation in the - # world coordinate system - assert neuro.getOrientation(0, np.eye(4)) == constants.ORIENT_L2R - assert neuro.getOrientation(1, np.eye(4)) == constants.ORIENT_P2A - assert neuro.getOrientation(2, np.eye(4)) == constants.ORIENT_I2S - assert radio.getOrientation(0, np.eye(4)) == constants.ORIENT_L2R - assert radio.getOrientation(1, np.eye(4)) == constants.ORIENT_P2A - assert radio.getOrientation(2, np.eye(4)) == constants.ORIENT_I2S + assert image.getXFormCode() == expCode + assert image.getXFormCode('sform') == sformcode + assert image.getXFormCode('qform') == qformcode + finally: + shutil.rmtree(testdir) - # The radio image should be - # l/r flipped in the voxel - # coordinate system - assert neuro.getOrientation(0, neuro.voxToWorldMat) == constants.ORIENT_L2R - assert neuro.getOrientation(1, neuro.voxToWorldMat) == constants.ORIENT_P2A - assert neuro.getOrientation(2, neuro.voxToWorldMat) == constants.ORIENT_I2S - - assert radio.getOrientation(0, radio.voxToWorldMat) == constants.ORIENT_R2L - assert radio.getOrientation(1, radio.voxToWorldMat) == constants.ORIENT_P2A - assert radio.getOrientation(2, radio.voxToWorldMat) == constants.ORIENT_I2S +def test_Image_changeXform_analyze(): _test_Image_changeXform(0) +def test_Image_changeXform_nifti1(): _test_Image_changeXform(1) +def test_Image_changeXform_nifti2(): _test_Image_changeXform(2) +def _test_Image_changeXform(imgtype): + """Test changing the Nifti.voxToWorldMat attribute. """ -def test_Image_sqforms(testdir): + testdir = tempfile.mkdtemp() + imagefile = op.join(testdir, 'image') - benchmark = fslimage.Image(op.join(testdir, 'MNI152_T1_2mm.nii.gz')) - nosform = fslimage.Image(op.join(testdir, 'MNI152_T1_2mm_nosform.nii.gz')) - noqform = fslimage.Image(op.join(testdir, 'MNI152_T1_2mm_noqform.nii.gz')) - nosqform = fslimage.Image(op.join(testdir, 'MNI152_T1_2mm_nosqform.nii.gz')) - - scalemat = np.diag([2, 2, 2, 1]) - invScalemat = np.diag([0.5, 0.5, 0.5, 1]) - - assert np.all(np.isclose(nosform.voxToWorldMat, benchmark.voxToWorldMat)) - assert np.all(np.isclose(nosform.worldToVoxMat, benchmark.worldToVoxMat)) - assert np.all(np.isclose(noqform.voxToWorldMat, benchmark.voxToWorldMat)) - assert np.all(np.isclose(noqform.worldToVoxMat, benchmark.worldToVoxMat)) - assert np.all(np.isclose(nosqform.voxToWorldMat, scalemat)) - assert np.all(np.isclose(nosqform.worldToVoxMat, invScalemat)) - - assert benchmark.getXFormCode() == constants.NIFTI_XFORM_MNI_152 - assert benchmark.getXFormCode('sform') == constants.NIFTI_XFORM_MNI_152 - assert benchmark.getXFormCode('qform') == constants.NIFTI_XFORM_MNI_152 - assert nosform .getXFormCode() == constants.NIFTI_XFORM_MNI_152 - assert nosform .getXFormCode('sform') == constants.NIFTI_XFORM_UNKNOWN - assert nosform .getXFormCode('qform') == constants.NIFTI_XFORM_MNI_152 - assert noqform .getXFormCode() == constants.NIFTI_XFORM_MNI_152 - assert noqform .getXFormCode('sform') == constants.NIFTI_XFORM_MNI_152 - assert noqform .getXFormCode('qform') == constants.NIFTI_XFORM_UNKNOWN - assert nosqform .getXFormCode() == constants.NIFTI_XFORM_UNKNOWN - assert nosqform .getXFormCode('sform') == constants.NIFTI_XFORM_UNKNOWN - assert nosqform .getXFormCode('qform') == constants.NIFTI_XFORM_UNKNOWN - - -def test_Image_changeXform(testdir): - - img = fslimage.Image(op.join(testdir, 'MNI152_T1_2mm.nii.gz')) + make_image(imagefile, imgtype) notified = {} @@ -227,30 +434,59 @@ def test_Image_changeXform(testdir): notified['xform'] = True def onSave(*a): - notified['save'] = True + notified['save'] = True + + img = fslimage.Image(imagefile) img.register('name1', onXform, 'transform') img.register('name2', onSave, 'saveState') - newXform = np.array([[5, 0, 0, 10], [0, 2, 0, 23], [0, 0, 14, 5], [0, 0, 0, 1]]) + newXform = np.array([[5, 0, 0, 10], + [0, 2, 0, 23], + [0, 0, 14, 5], + [0, 0, 0, 1]]) - assert img.saveState + try: - img.voxToWorldMat = newXform + # Image state should initially be saved + assert img.saveState - invx = npla.inv(newXform) + if imgtype == 0: + # ANALYZE affine is not editable + with pytest.raises(Exception): + img.voxToWorldMat = newXform + return - assert notified.get('xform', False) - assert notified.get('save', False) - assert not img.saveState - - assert np.all(np.isclose(img.voxToWorldMat, newXform)) - assert np.all(np.isclose(img.worldToVoxMat, invx)) + img.voxToWorldMat = newXform + + invx = npla.inv(newXform) + + # Did we get notified? + assert notified.get('xform', False) + assert notified.get('save', False) + assert not img.saveState + # Did the affine get updated? + assert np.all(np.isclose(img.voxToWorldMat, newXform)) + assert np.all(np.isclose(img.worldToVoxMat, invx)) + finally: + shutil.rmtree(testdir) -def test_Image_changeData(testdir): - img = fslimage.Image(op.join(testdir, 'dtypes', 'MNI152_T1_1mm_float.nii.gz')) +def test_Image_changeData_analyze(seed): _test_Image_changeData(0) +def test_Image_changeData_nifti1(seed): _test_Image_changeData(1) +def test_Image_changeData_nifti2(seed): _test_Image_changeData(2) +def _test_Image_changeData(imgtype): + """Test that changing image data triggers notification, and also causes + the dataRange attribute to be updated. + """ + + testdir = tempfile.mkdtemp() + imagefile = op.join(testdir, 'image') + + make_image(imagefile, imgtype) + + img = fslimage.Image(imagefile) notified = {} @@ -258,7 +494,6 @@ def test_Image_changeData(testdir): return (np.random.randint(0, img.shape[0]), np.random.randint(0, img.shape[1]), np.random.randint(0, img.shape[2])) - def onData(*a): notified['data'] = True @@ -273,71 +508,100 @@ def test_Image_changeData(testdir): img.register('name2', onSaveState, 'saveState') img.register('name3', onDataRange, 'dataRange') + # Calculate the actual data range data = img.nibImage.get_data() dmin = data.min() dmax = data.max() drange = dmax - dmin - assert img.saveState - assert np.all(np.isclose(img.dataRange, (dmin, dmax))) + try: - randval = dmin + np.random.random() * drange - rx, ry, rz = randvox() + assert img.saveState + assert np.all(np.isclose(img.dataRange, (dmin, dmax))) - img[rx, ry, rz] = randval + randval = dmin + np.random.random() * drange + rx, ry, rz = randvox() - assert np.isclose(img[rx, ry, rz], randval) - assert notified.get('data', False) - assert notified.get('save', False) - assert not img.saveState + img[rx, ry, rz] = randval - notified.pop('data') + assert np.isclose(img[rx, ry, rz], randval) + assert notified.get('data', False) + assert notified.get('save', False) + assert not img.saveState - newdmin = dmin - 100 - newdmax = dmax + 100 + notified.pop('data') - rx, ry, rz = randvox() - img[rx, ry, rz] = newdmin + newdmin = dmin - 100 + newdmax = dmax + 100 - assert notified.get('data', False) - assert notified.get('dataRange', False) - assert np.isclose(img[rx, ry, rz], newdmin) - assert np.all(np.isclose(img.dataRange, (newdmin, dmax))) + rx, ry, rz = randvox() + img[rx, ry, rz] = newdmin - notified.pop('data') - notified.pop('dataRange') + assert notified.get('data', False) + assert notified.get('dataRange', False) + assert np.isclose(img[rx, ry, rz], newdmin) + assert np.all(np.isclose(img.dataRange, (newdmin, dmax))) - rx, ry, rz = randvox() - img[rx, ry, rz] = newdmax + notified.pop('data') + notified.pop('dataRange') - assert notified.get('data', False) - assert notified.get('dataRange', False) - assert np.isclose(img[rx, ry, rz], newdmax) - assert np.all(np.isclose(img.dataRange, (newdmin, newdmax))) - + rx, ry, rz = randvox() + img[rx, ry, rz] = newdmax -def test_2D_images(testdir): + assert notified.get('data', False) + assert notified.get('dataRange', False) + assert np.isclose(img[rx, ry, rz], newdmax) + assert np.all(np.isclose(img.dataRange, (newdmin, newdmax))) - tests = [('MNI152_T1_2mm_sliceXY.nii.gz', (91, 109, 1), (2.0, 2.0, 2.0)), - ('MNI152_T1_2mm_sliceXZ.nii.gz', (91, 1, 91), (2.0, 2.0, 2.0)), - ('MNI152_T1_2mm_sliceYZ.nii.gz', (1, 109, 91), (2.0, 2.0, 2.0)), - ('MNI152_T1_2mm_sliceXY_4D.nii.gz', (91, 109, 1, 5), (2.0, 2.0, 2.0, 1.0)), + finally: + shutil.rmtree(testdir) - # When you create an XY slice with - # fslroi, it sets nifti/dim0 to 2. - # This should still be read in as - # a 3D image. - ('MNI152_T1_2mm_sliceXY_bad_dim0.nii.gz', (91, 109, 1), (2.0, 2.0, 2.0))] - for fname, shape, pixdim in tests: +def test_Image_2D_analyze(): _test_Image_2D(0) +def test_Image_2D_nifti1(): _test_Image_2D(1) +def test_Image_2D_nifti2(): _test_Image_2D(2) +def _test_Image_2D(imgtype): - fname = op.join(testdir, 'nifti2D', fname) - image = fslimage.Image(fname) - - assert len(shape) == len(image .shape) - assert len(shape) == len(image[:].shape) - assert len(pixdim) == len(image .pixdim) + testdir = tempfile.mkdtemp() + + # The first shape tests when the + # nifti dim0 field is set to 2, + # which happens when you create + # an XY slice with fslroi. This + # should still be read in as a + # 3D image. + testdims = [(10, 20), + (10, 20, 1), + (10, 1, 20), + (1, 10, 20), + (10, 20, 1, 5), + (10, 1, 20, 5), + (1, 10, 20, 5)] + + try: + + for shape in testdims: + + pixdim = [2] * len(shape) + + imagefile = op.join(testdir, 'image') + + make_image(imagefile, imgtype, shape, pixdim) + + image = fslimage.Image(imagefile) + + # 2D should appear as 3D + if len(shape) == 2: + shape = list(shape) + [1] + pixdim = list(pixdim) + [1] + + assert len(shape) == len(image .shape) + assert len(shape) == len(image[:].shape) + assert len(pixdim) == len(image .pixdim) + + assert tuple(map(float, shape)) == tuple(map(float, image .shape)) + assert tuple(map(float, shape)) == tuple(map(float, image[:].shape)) + assert tuple(map(float, pixdim)) == tuple(map(float, image .pixdim)) - assert tuple(map(float, shape)) == tuple(map(float, image .shape)) - assert tuple(map(float, shape)) == tuple(map(float, image[:].shape)) - assert tuple(map(float, pixdim)) == tuple(map(float, image .pixdim)) + finally: + shutil.rmtree(testdir) diff --git a/tests/test_immv_imcp.py b/tests/test_immv_imcp.py index b75315541..6b6f030fb 100644 --- a/tests/test_immv_imcp.py +++ b/tests/test_immv_imcp.py @@ -31,11 +31,22 @@ import fsl.data.image as fslimage from . import make_random_image from . import make_dummy_file -from . import check_image_hash from . import looks_like_image from . import cleardir +def makeImage(filename): + return hash(make_random_image(filename).get_data().tobytes()) + + +def checkImageHash(filename, datahash): + """Checks that the given NIFTI image matches the given hash. + """ + + img = nib.load(filename) + assert hash(img.get_data().tobytes()) == datahash + + def checkFilesToExpect(files, outdir, outputType, datahashes): exts = { @@ -91,7 +102,7 @@ def checkFilesToExpect(files, outdir, outputType, datahashes): else: h = datahashes[op.basename(f)] - check_image_hash(f, h) + checkImageHash(f, h) def test_imcp_script_shouldPass(move=False): @@ -308,7 +319,7 @@ def test_imcp_script_shouldPass(move=False): print('files_to_expect: ', files_to_expect) for i, fname in enumerate(files_to_create.split()): - imageHashes.append(make_random_image(op.join(indir, fname))) + imageHashes.append(makeImage(op.join(indir, fname))) imcp_args = imcp_args.split() @@ -413,7 +424,7 @@ def test_imcp_script_shouldFail(move=False): imcp_args = imcp_args .split() for fname in files_to_create: - make_random_image(op.join(indir, fname)) + makeImage(op.join(indir, fname)) imcp_args[:-1] = [op.join(indir, a) for a in imcp_args[:-1]] imcp_args[ -1] = op.join(outdir, imcp_args[-1]) @@ -609,7 +620,7 @@ def test_imcp_shouldPass(move=False): hashes = {} for fn in files_to_create: if looks_like_image(fn): - hashes[fn] = make_random_image(op.join(indir, fn)) + hashes[fn] = makeImage(op.join(indir, fn)) else: hashes[fn] = make_dummy_file(op.join(indir, fn)) -- GitLab