From 5870a4f6407a3b6083ec0eaedbbf8a23598045a5 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Sun, 16 Apr 2017 13:27:03 +0100 Subject: [PATCH] featimage module unit tests. Needs more. --- tests/__init__.py | 99 +++++++++++++++++++++++--- tests/test_featdesign.py | 30 +++----- tests/test_featimage.py | 150 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+), 27 deletions(-) create mode 100644 tests/test_featimage.py diff --git a/tests/__init__.py b/tests/__init__.py index 3b89f3a30..c35bd760b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -7,12 +7,14 @@ """Unit tests for ``fslpy``. """ -import os -import shutil -import tempfile -import os.path as op -import numpy as np -import nibabel as nib +import os +import glob +import shutil +import tempfile +import itertools as it +import os.path as op +import numpy as np +import nibabel as nib def testdir(contents=None): @@ -102,14 +104,95 @@ def cleardir(dir): elif op.isdir(f): shutil.rmtree(f) -def make_random_image(filename, dims=(10, 10, 10)): +def make_random_image(filename, dims=(10, 10, 10), xform=None): """Creates a NIFTI1 image with random data, saves and returns it. """ + if xform is None: + xform = np.eye(4) + data = np.array(np.random.random(dims) * 100, dtype=np.float32) - img = nib.Nifti1Image(data, np.eye(4)) + img = nib.Nifti1Image(data, xform) nib.save(img, filename) return img + +def make_mock_feat_analysis(featdir, + testdir, + shape4D, + xform=None, + indata=True, + voxEVs=True, + pes=True, + copes=True, + zstats=True, + residuals=True, + clustMasks=True): + + if xform is None: + xform = np.eye(4) + + timepoints = shape4D[ 3] + shape = shape4D[:3] + + src = featdir + dest = op.join(testdir, op.basename(featdir)) + featdir = dest + + shutil.copytree(src, dest) + + if indata: + filtfunc = op.join(featdir, 'filtered_func_data.nii.gz') + make_random_image(filtfunc, shape4D, xform) + + # and some dummy voxelwise EV files + if voxEVs: + voxFiles = list(it.chain( + glob.glob(op.join(featdir, 'designVoxelwiseEV*nii.gz')), + glob.glob(op.join(featdir, 'InputConfoundEV*nii.gz')))) + + for i, vf in enumerate(voxFiles): + + # Each voxel contains range(i, i + timepoints), + # offset by the flattened voxel index + data = np.meshgrid(*[range(s) for s in shape], indexing='ij') + data = np.ravel_multi_index(data, shape) + data = data.reshape(list(shape) + [1]).repeat(timepoints, axis=3) + data[..., :] += range(i, i + timepoints) + + nib.save(nib.nifti1.Nifti1Image(data, xform), vf) + + otherFiles = [] + otherShapes = [] + + if pes: + files = glob.glob(op.join(featdir, 'stats', 'pe*nii.gz')) + otherFiles .extend(files) + otherShapes.extend([shape] * len(files)) + + if copes: + files = glob.glob(op.join(featdir, 'stats', 'cope*nii.gz')) + otherFiles .extend(files) + otherShapes.extend([shape] * len(files)) + + if zstats: + files = glob.glob(op.join(featdir, 'stats', 'zstat*nii.gz')) + otherFiles .extend(files) + otherShapes.extend([shape] * len(files)) + + if residuals: + files = glob.glob(op.join(featdir, 'stats', 'res4d.nii.gz')) + otherFiles .extend(files) + otherShapes.extend([shape4D]) + + if clustMasks: + files = glob.glob(op.join(featdir, 'cluster_mask*nii.gz')) + otherFiles .extend(files) + otherShapes.extend([shape] * len(files)) + + for f, s in zip(otherFiles, otherShapes): + make_random_image(f, s, xform) + + return featdir diff --git a/tests/test_featdesign.py b/tests/test_featdesign.py index 5b392bd2e..412503e55 100644 --- a/tests/test_featdesign.py +++ b/tests/test_featdesign.py @@ -136,25 +136,17 @@ def test_FEATFSFDesign_firstLevelVoxelwiseEV(seed): # and generate some dummy data for # the voxelwise EVs. featdir = op.join(testdir, '1stlevel_2.feat') - shape = (64, 64, 5) - timepoints = 45 - - shutil.copytree(template, featdir) - - voxFiles = list(it.chain( - glob.glob(op.join(featdir, 'designVoxelwiseEV*nii.gz')), - glob.glob(op.join(featdir, 'InputConfoundEV*nii.gz')))) - - for i, vf in enumerate(voxFiles): - - # Each voxel contains range(i, i + timepoints), - # offset by the flattened voxel index - data = np.meshgrid(*[range(s) for s in shape], indexing='ij') - data = np.ravel_multi_index(data, shape) - data = data.reshape(list(shape) + [1]).repeat(timepoints, axis=3) - data[..., :] += range(i, i + timepoints) - - fslimage.Image(data).save(vf) + shape4D = (64, 64, 5, 45) + shape = shape4D[:3] + + featdir = tests.make_mock_feat_analysis( + template, testdir, shape4D, + indata=False, + pes=False, + copes=False, + zstats=False, + residuals=False, + clusterMasks=False) # Now load the design, and make sure that # the voxel EVs are filled correctly diff --git a/tests/test_featimage.py b/tests/test_featimage.py new file mode 100644 index 000000000..edcc5a187 --- /dev/null +++ b/tests/test_featimage.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# +# test_featimage.py - +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# + + +import os.path as op +import os +import itertools as it +import glob +import shutil + +import numpy as np + +import pytest + +import tests +import fsl.data.featimage as featimage +import fsl.data.featdesign as featdesign +import fsl.data.featanalysis as featanalysis + + +datadir = op.join(op.dirname(__file__), 'testdata', 'test_feat') +featdirs = ['1stlevel_1.feat', '1stlevel_2.feat', '1stlevel_2.feat', + '2ndlevel_1.gfeat/cope1.feat', '2ndlevel_1.gfeat/cope2.feat', + '2ndlevel_2.gfeat/cope1.feat', '2ndlevel_2.gfeat/cope2.feat'] +shapes = [(64, 64, 5, 45), + (64, 64, 5, 45), + (64, 64, 5, 45), + (91, 109, 91, 3), + (91, 109, 91, 3), + (91, 109, 91, 3), + (91, 109, 91, 3)] +xforms = [[[-4, 0, 0, 0], + [ 0, 4, 0, 0], + [ 0, 0, 6, 0], + [ 0, 0, 0, 1]]] * 3 + \ + [[[-2, 0, 0, 90], + [ 0, 2, 0, -126], + [ 0, 0, 2, -72], + [ 0, 0, 0, 1]]] * 4 +xforms = [np.array(xf) for xf in xforms] + +TEST_ANALYSES = list(zip( + featdirs, + shapes, + xforms)) + + +def test_FEATImage_attributes(): + + for i, featdir in enumerate(featdirs): + with tests.testdir() as testdir: + + featdir = tests.make_mock_feat_analysis( + op.join(datadir, featdir), + testdir, + shapes[i], + xforms[i], + pes=False, + copes=False, + zstats=False, + residuals=False, + clustMasks=False) + + # Now create a FEATImage. We validate its + # attributes against the values returned by + # the functions in featdesign/featanalysis. + fi = featimage.FEATImage(featdir) + settings = featanalysis.loadSettings(featdir) + design = featdesign.FEATFSFDesign(featdir, settings) + desmat = design.getDesign() + evnames = [ev.title for ev in design.getEVs()] + contrastnames, contrasts = featanalysis.loadContrasts(featdir) + + assert fi.getFEATDir() == featdir + assert fi.getAnalysisName() == op.splitext(op.basename(featdir))[0] + assert fi.isFirstLevelAnalysis() == featanalysis.isFirstLevelAnalysis(settings) + assert fi.getTopLevelAnalysisDir() == featanalysis.getTopLevelAnalysisDir(featdir) + assert fi.getReportFile() == featanalysis.getReportFile(featdir) + assert fi.hasStats() == featanalysis.hasStats(featdir) + assert fi.numPoints() == desmat.shape[0] + assert fi.numEVs() == desmat.shape[1] + assert fi.evNames() == evnames + assert fi.numContrasts() == len(contrasts) + assert fi.contrastNames() == contrastnames + assert fi.contrasts() == contrasts + assert np.all(np.isclose(fi.getDesign(), desmat)) + + assert fi.thresholds() == featanalysis.getThresholds(settings) + + for ci in range(len(contrasts)): + result = fi.clusterResults(ci) + expect = featanalysis.loadClusterResults(featdir, settings, ci) + assert len(result) == len(expect) + assert all([rc.nvoxels == ec.nvoxels for rc, ec in zip(result, expect)]) + + +def test_FEATImage_imageAccessors(): + + for i, featdir in enumerate(featdirs): + with tests.testdir() as testdir: + featdir = tests.make_mock_feat_analysis( + op.join(datadir, featdir), + testdir, + shapes[i], + xforms[i]) + + shape4D = shapes[ i] + shape = shape4D[:3] + + fi = featimage.FEATImage(featdir) + nevs = fi.numEVs() + ncons = fi.numContrasts() + + # Testing the FEATImage intenral cache + for i in range(2): + assert fi.getResiduals().shape == shape4D + + for ev in range(nevs): + assert fi.getPE(ev).shape == shape + for con in range(ncons): + assert fi.getCOPE( con).shape == shape + assert fi.getZStats( con).shape == shape + assert fi.getClusterMask(con).shape == shape + + + +def test_FEATImage_fit_firstLevel(): + featdir = '' + fi = featimage.FEATImage(featdir) + fi.fit(0, (0, 0, 0)) + + +def test_FEATImage_fit_higherLevel(): + featdir = '' + fi = featimage.FEATImage(featdir) + fi.fit(0, (0, 0, 0)) + + +def test_FEATImage_partialFit(): + featdir = '' + fi = featimage.FEATImage(featdir) + fi.fit(0, (0, 0, 0)) + + +def test_FEATImage_nostats(): + pass -- GitLab