diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3ede469ebaa70809c73155372de877f20e591e0d..d7cfb940dab078529c3259612ce9fbd5a8b9e6b2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,11 +2,25 @@ This document contains the ``fslpy`` release history in reverse chronological order. +3.18.0 (Under development) +-------------------------- + + +Added +^^^^^ + +* New wrapper function for the FLIRT `midtrans` command (!443). +* The :class:`.Image` class now accepts a ``version`` parameter, as an + easy way of specifying the NIfTI file format version (!443). + + 3.17.0 (Friday 9th February 2024) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +--------------------------------- + Added +^^^^^ * New wrapper function for the FLIRT `makerot` command (!441). * New wrapper functions for the `imcp`, `immv`, `imrm`, `imln`, `imglob` and diff --git a/fsl/data/image.py b/fsl/data/image.py index 8157a422ad133b286246f11ed36506849e63dc54..2d69817707a45cf0965d3441e91eb24338dba52b 100644 --- a/fsl/data/image.py +++ b/fsl/data/image.py @@ -1160,6 +1160,7 @@ class Image(Nifti): dataSource : PathLike = None, loadMeta : bool = False, dataMgr : DataManager = None, + version : int = None, **kwargs): """Create an ``Image`` object with the given image data or file name. @@ -1205,6 +1206,12 @@ class Image(Nifti): :arg dataMgr: Object implementing the :class:`DataManager` interface, for managing access to the image data. + + :arg version: NIfTI version - either 1 or 2. Only used when creating + an image from a numpy array, and when a ``header`` is + not provided. Defaults to the value dictated by the + ``FSLOUTPUTTYPE`` environment variable. + All other arguments are passed through to the ``nibabel.load`` function (if it is called). """ @@ -1219,6 +1226,10 @@ class Image(Nifti): deprecated.warn('Image(calcRange)', vin='3.9.0', rin='4.0.0', msg='The calcRange option has no effect') + if version not in (None, 1, 2): + raise ValueError('Invalid value for version - only NIfTI ' + 'versions 1 and 2 are supported') + nibImage = None saved = False @@ -1261,12 +1272,15 @@ class Image(Nifti): if header is not None: xform = header.get_best_affine() else: xform = np.identity(4) - # default to NIFTI1 if FSLOUTPUTTYPE - # is not set, just to be safe. + # NIfTI1 or NIfTI2 - if version was provided, + # use that, otherwise use the FSLOUTPUTTYPE + # environment variable if header is None: outputType = defaultOutputType() - if 'NIFTI2' in outputType.name: ctr = nib.Nifti2Image - else: ctr = nib.Nifti1Image + if version == 2: ctr = nib.Nifti2Image + elif version == 1: ctr = nib.Nifti1Image + elif 'NIFTI2' in outputType.name: ctr = nib.Nifti2Image + else: ctr = nib.Nifti1Image # make sure that the data type is correct, # in case this header was passed in from diff --git a/fsl/tests/test_image.py b/fsl/tests/test_image.py index 14a85f59630b092421c874824998e0f00db041ea..3efe06d3b9ef5603fec154fd9f645d3db17c7082 100644 --- a/fsl/tests/test_image.py +++ b/fsl/tests/test_image.py @@ -225,6 +225,27 @@ def test_create(): assert np.all(np.isclose(img.pixdim, (2, 3, 4))) +def test_create_niftiversion(): + + data = np.random.random((10, 10, 10)) + + with mock.patch.dict(os.environ, FSLOUTPUTTYPE='NIFTI_GZ'): + img = fslimage.Image(data) + assert img.niftiVersion == 1 + img = fslimage.Image(data, version=1) + assert img.niftiVersion == 1 + img = fslimage.Image(data, version=2) + assert img.niftiVersion == 2 + + with mock.patch.dict(os.environ, FSLOUTPUTTYPE='NIFTI2_GZ'): + img = fslimage.Image(data) + assert img.niftiVersion == 2 + img = fslimage.Image(data, version=1) + assert img.niftiVersion == 1 + img = fslimage.Image(data, version=2) + assert img.niftiVersion == 2 + + def test_name_dataSource(): with tempdir(): diff --git a/fsl/tests/test_wrappers/test_wrappers.py b/fsl/tests/test_wrappers/test_wrappers.py index 090cba02608a45dd654ca9f06491fcfea2260df9..02177828c14db48301ebf08e8c56ef0eb8a32ce7 100755 --- a/fsl/tests/test_wrappers/test_wrappers.py +++ b/fsl/tests/test_wrappers/test_wrappers.py @@ -660,3 +660,9 @@ def test_makerot(): assert fw.makerot(90).stdout[0] == f'{exe} -t 90' assert fw.makerot(90, cov='cov', centre=[10, 10, 10]).stdout[0] == \ f'{exe} -t 90 --cov=cov --centre=10,10,10' + + +def test_midtrans(): + with testenv('midtrans') as exe: + assert fw.midtrans('out', 'xfm1', 'xfm2', 'xfm3').stdout[0] == \ + f'{exe} -o out xfm1 xfm2 xfm3' diff --git a/fsl/wrappers/__init__.py b/fsl/wrappers/__init__.py index fd95d7690aa9e215772c52b0e92817b7441372fe..6cba0e0262a13771824aa24872755672067c1d73 100755 --- a/fsl/wrappers/__init__.py +++ b/fsl/wrappers/__init__.py @@ -129,7 +129,8 @@ from fsl.wrappers.flirt import (flirt, fixscaleskew, mcflirt, standard_space_roi, - makerot) + makerot, + midtrans) from fsl.wrappers.fnirt import (fnirt, applywarp, invwarp, diff --git a/fsl/wrappers/flirt.py b/fsl/wrappers/flirt.py index 4fa13c6117adec0dca8a6fb3572d97de96cddfdb..67a485a981ffdb3e9ebcda7f1d0f9bfa4def5001 100644 --- a/fsl/wrappers/flirt.py +++ b/fsl/wrappers/flirt.py @@ -232,3 +232,21 @@ def makerot(theta, **kwargs): cmd += wutils.applyArgStyle('--=', valmap=valmap, **kwargs) return cmd + + +@wutils.fileOrArray('output', 'transforms', outprefix='separate') +@wutils.fileOrImage('template') +@wutils.fslwrapper +def midtrans(output, *transforms, **kwargs): + """Wrapper for the ``midtrans`` command. """ + + valmap = { + 'verbose' : wutils.SHOW_IF_TRUE, + 'debug' : wutils.SHOW_IF_TRUE + } + + cmd = ['midtrans'] + cmd += wutils.applyArgStyle('--=', valmap=valmap, **kwargs) + cmd += ['-o', output] + list(transforms) + + return cmd