diff --git a/CHANGELOG.rst b/CHANGELOG.rst index af5f1d8c66cf9bb018b8d81cfe867c04ea64a711..0347a5df351c952d247feb8efaa3416d70d62c36 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,27 @@ This document contains the ``fslpy`` release history in reverse chronological order. +2.1.0 (Under development) +------------------------- + + +Added +^^^^^ + + +* New tensor conversion routines in the :mod:`.dtifit` module (Michiel + Cottaar). + + +Fixed +^^^^^ + + +* The :class:`.FeatDesign` class now handles "compressed" voxelwise EV files, + such as those generated by `PNM + <https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PNM>`_. + + 2.0.1 (Monday April 1st 2019) ----------------------------- diff --git a/fsl/data/dtifit.py b/fsl/data/dtifit.py index a58dfe0df6179d20ea8ede8f63acffa5fba00b3c..360eeb9003d6e7d5eb799fa4b7d278e38371399b 100644 --- a/fsl/data/dtifit.py +++ b/fsl/data/dtifit.py @@ -3,6 +3,7 @@ # dtifit.py - The DTIFitTensor class, and some related utility functions. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> +# Author: Michiel Cottaar <michiel.cottaar@ndcn.ox.ac.uk> # """This module provides the :class:`.DTIFitTensor` class, which encapsulates the diffusion tensor data generated by the FSL ``dtifit`` tool. diff --git a/fsl/data/featdesign.py b/fsl/data/featdesign.py index 633b712b4b04f9de986ae096612987e6814a00a8..ce5b41f592609b5dbd6bc8b987e04016567a761c 100644 --- a/fsl/data/featdesign.py +++ b/fsl/data/featdesign.py @@ -214,7 +214,7 @@ class FEATFSFDesign(object): 'for ev {}'.format(ev.index)) continue - design[:, ev.index] = ev.image[x, y, z, :] + design[:, ev.index] = ev.getData(x, y, z) return design @@ -228,6 +228,17 @@ class VoxelwiseEVMixin(object): ``filename`` Path to the image file containing the data for this EV ``image`` Reference to the :class:`.Image` object ============ ====================================================== + + Some FSL tools (e.g. `PNM + <https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PNM>`_) generate *compressed* + voxelwise EVs, where there is only one voxel per slice. For example, + if the input data has shape ``(x, y, z, t)``, and slices are acquired + along the Z plane, voxelwise EV files generated by PNM will have shape + ``(1, 1, z, t)``. + + Therefore, using the :meth:`getData` method is preferable to accessing + the :meth:`image` directly, as ``getData`` will check for compressed + images, and adjust the voxel coordinates accordingly. """ def __init__(self, filename): @@ -272,6 +283,29 @@ class VoxelwiseEVMixin(object): return self.__image + def getData(self, x, y, z): + """Returns the data at the specified voxel, taking into account + compressed voxelwise EVs. + """ + image = self.image + + if image is None: + return None + + dx, dy, dz = image.shape[:3] + + # "Compressed" images have one voxel + # per slice, i.e. they have shape + # [1, 1, nslices, ntimepoints] (if + # Z is the slice plane). + if sum((dx == 1, dy == 1, dz == 1)) == 2: + if dx == 1: x = 0 + if dy == 1: y = 0 + if dz == 1: z = 0 + + return image[x, y, z, :] + + class EV(object): """Class representing an explanatory variable in a FEAT design matrix. diff --git a/tests/test_featdesign.py b/tests/test_featdesign.py index dd821127e925bf37a15f17fab5f40d40b5268a26..5c5d1a7e3433cb2c62bcb1c13b0e4bf061aa6028 100644 --- a/tests/test_featdesign.py +++ b/tests/test_featdesign.py @@ -412,3 +412,19 @@ def test_VoxelwiseEVs(): exp = img.dataobj[x, y, z, :] assert np.all(ev1.image[x, y, z, :] == exp) assert np.all(ev2.image[x, y, z, :] == exp) + + +def test_compressed_voxelwise_ev(): + + testcases = [((1, 1, 10, 10), (0, 0, 5)), + ((1, 10, 1, 10), (0, 5, 0)), + ((10, 1, 1, 10), (5, 0, 0))] + + with tempdir(): + + for shape, vox in testcases: + img = tests.make_random_image('vev.nii.gz', shape) + vev = featdesign.VoxelwiseEV(0, 0, 'ev1', 'vev.nii.gz') + x, y, z = vox + + assert np.all(vev.getData(5, 5, 5) == img.dataobj[x, y, z, :])