diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cdfbb2f92eee621164e0b7dbe392f7813e107d03..150331aa3f44643b9b7b622419e9a3a32e40aa92 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,19 @@ This document contains the ``fslpy`` release history in reverse chronological order. +3.0.1 (Wednesday 15th April 2020) +--------------------------------- + + +Changed +^^^^^^^ + + +* The :func:`.isMelodicDir` function now accepts directories that do not end + with ``.ica``, as long as all required files are present. + + + 3.0.0 (Sunday 29th March 2020) ------------------------------ diff --git a/fsl/data/image.py b/fsl/data/image.py index 1dc0aaf93bd1def53bb793d27c0033269eba82e2..eb9c468cc50a9831833b50bbe3cd758576297e18 100644 --- a/fsl/data/image.py +++ b/fsl/data/image.py @@ -622,8 +622,9 @@ class Nifti(notifier.Notifier, meta.Meta): if from_ not in ('voxel', 'fsl', 'world') or \ to not in ('voxel', 'fsl', 'world'): - raise ValueError('Invalid source/reference spaces: ' - '{} -> {}'.format(from_, to)) + raise ValueError('Invalid source/reference spaces: "{}" -> "{}".' + 'Recognised spaces are "voxel", "fsl", and ' + '"world"'.format(from_, to)) return np.copy(self.__affines[from_, to]) diff --git a/fsl/data/melodicanalysis.py b/fsl/data/melodicanalysis.py index f3779d5ccecd139cc80730ea1ae66dc07ff9044a..ba0048234d8e72abe5210bb2fc708d2fd07eb16e 100644 --- a/fsl/data/melodicanalysis.py +++ b/fsl/data/melodicanalysis.py @@ -33,7 +33,6 @@ import logging import os.path as op import numpy as np -import fsl.utils.path as fslpath import fsl.data.image as fslimage import fsl.data.featanalysis as featanalysis @@ -63,10 +62,9 @@ def isMelodicImage(path): def isMelodicDir(path): - """Returns ``True`` if the given path looks like it is contained within - a MELODIC directory, ``False`` otherwise. A melodic directory: + """Returns ``True`` if the given path looks like it is a MELODIC directory, + ``False`` otherwise. A MELODIC directory: - - Must be named ``*.ica``. - Must contain a file called ``melodic_IC.nii.gz`` or ``melodic_oIC.nii.gz``. - Must contain a file called ``melodic_mix``. @@ -75,12 +73,7 @@ def isMelodicDir(path): path = op.abspath(path) - if op.isdir(path): dirname = path - else: dirname = op.dirname(path) - - sufs = ['.ica'] - - if not any([dirname.endswith(suf) for suf in sufs]): + if not op.isdir(path): return False # Must contain an image file called @@ -88,7 +81,7 @@ def isMelodicDir(path): prefixes = ['melodic_IC', 'melodic_oIC'] for p in prefixes: try: - fslimage.addExt(op.join(dirname, p)) + fslimage.addExt(op.join(path, p)) break except fslimage.PathError: pass @@ -97,8 +90,8 @@ def isMelodicDir(path): # Must contain files called # melodic_mix and melodic_FTmix - if not op.exists(op.join(dirname, 'melodic_mix')): return False - if not op.exists(op.join(dirname, 'melodic_FTmix')): return False + if not op.exists(op.join(path, 'melodic_mix')): return False + if not op.exists(op.join(path, 'melodic_FTmix')): return False return True @@ -108,10 +101,13 @@ def getAnalysisDir(path): to that MELODIC directory is returned. Otherwise, ``None`` is returned. """ - meldir = fslpath.deepest(path, ['.ica']) + if not op.isdir(path): + path = op.dirname(path) - if meldir is not None and isMelodicDir(meldir): - return meldir + while path not in (op.sep, ''): + if isMelodicDir(path): + return path + path = op.dirname(path) return None diff --git a/fsl/wrappers/flirt.py b/fsl/wrappers/flirt.py index df7eb2722b19954303ef36132f7d7f36365ac673..eaee2d0ac10daa5e43743c071d23ca316f5c254f 100644 --- a/fsl/wrappers/flirt.py +++ b/fsl/wrappers/flirt.py @@ -98,19 +98,26 @@ def invxfm(inmat, omat): return ['convert_xfm', '-omat', omat, '-inverse', inmat] -@wutils.fileOrArray('inmat1', 'inmat2', 'outmat') +@wutils.fileOrArray('atob', 'atoc', 'btoc') @wutils.fslwrapper -def concatxfm(inmat1, inmat2, outmat): - """Use ``convert_xfm`` to concatenate two affines.""" +def concatxfm(atob, btoc, atoc): + """Use ``convert_xfm`` to concatenate two affines. Note that the + order of the input matrices is the opposite of the order expected + by ``convert_xfm``. + + :arg atob: Input matrix, transforming from "A" to "B". + :arg btoc: Input matrix, transforming from "B" to "C". + :arg atoc: Output matrix, transforming from "A" to "C". + """ - asrt.assertFileExists(inmat1, inmat2) + asrt.assertFileExists(atob, btoc) cmd = ['convert_xfm', '-omat', - outmat, + atoc, '-concat', - inmat2, - inmat1] + btoc, + atob] return cmd diff --git a/tests/test_assertions.py b/tests/test_assertions.py index 61b483f4bc2826b5dafefa5809dd04da1897b6d6..5d35f7507507503f353763619fc157f4132740f2 100644 --- a/tests/test_assertions.py +++ b/tests/test_assertions.py @@ -160,14 +160,14 @@ def test_assertIsMelodicDir(): ('analysis.ica', [ 'melodic_mix', 'melodic_FTmix'], False), ('analysis.ica', ['melodic_IC.nii.gz', 'melodic_FTmix'], False), ('analysis.ica', ['melodic_IC.nii.gz', 'melodic_mix'], False), - ('analysis', ['melodic_IC.nii.gz', 'melodic_mix', 'melodic_FTmix'], False), - ('analysis', ['melodic_oIC.nii.gz', 'melodic_mix', 'melodic_FTmix'], False), + ('analysis', ['melodic_IC.nii.gz', 'melodic_mix', 'melodic_FTmix'], True), + ('analysis', [ 'melodic_mix', 'melodic_FTmix'], False), ] for dirname, paths, expected in tests: with testdir(paths, dirname): if expected: - assertions.assertIsMelodicDir(dirname) + assertions.assertIsMelodicDir('.') else: with pytest.raises(AssertionError): assertions.assertIsMelodicDir(dirname) diff --git a/tests/test_atlases.py b/tests/test_atlases.py index 1c7abc0dfe6d2d8720c86edf605f0bd782af5c6d..ebfe1ba8579f0f550a9d6f1984c88bd349d87662 100644 --- a/tests/test_atlases.py +++ b/tests/test_atlases.py @@ -13,7 +13,8 @@ import os import os.path as op import numpy as np -import mock + +from unittest import mock import pytest import tests diff --git a/tests/test_fslsub.py b/tests/test_fslsub.py index 40d7668285c813b332c8da91574cc34ffa671681..d7c6460cafe5428d36833a953befe27207a4c626 100644 --- a/tests/test_fslsub.py +++ b/tests/test_fslsub.py @@ -22,7 +22,7 @@ from . import mockFSLDIR mock_fsl_sub = """ -#!{} +#!/usr/bin/env python3 import random import os @@ -62,8 +62,7 @@ with open('{{}}.o{{}}'.format(cmd, jobid), 'w') as stdout, \ print(str(jobid)) sys.exit(0) -""".format(sys.executable, op.dirname(fsl.__file__)).strip() - +""".format(op.dirname(fsl.__file__)).strip() @contextlib.contextmanager def fslsub_mockFSLDIR(): diff --git a/tests/test_melodicanalysis.py b/tests/test_melodicanalysis.py index 907f3c3ba0c892d17206acb156aab830acefb283..9a8c251ac34dae1e8664614a5e9e5551db630c78 100644 --- a/tests/test_melodicanalysis.py +++ b/tests/test_melodicanalysis.py @@ -55,10 +55,10 @@ def test_isMelodicDir(): meldir = op.join(testdir, 'analysis.ica') assert mela.isMelodicDir(meldir) - # Directory must end in .ica + # non-.ica prefix is ok with tests.testdir([p.replace('.ica', '.blob') for p in paths]) as testdir: meldir = op.join(testdir, 'analysis.blob') - assert not mela.isMelodicDir(meldir) + assert mela.isMelodicDir(meldir) # Directory must exist! assert not mela.isMelodicDir('non-existent.ica') diff --git a/tests/test_wrappers/test_fslstats.py b/tests/test_wrappers/test_fslstats.py index 8d20b4accaf7416a42d02c136d43129028f06fde..a38bf86d5b8eb42f746fec669315e57bba2488b6 100644 --- a/tests/test_wrappers/test_fslstats.py +++ b/tests/test_wrappers/test_fslstats.py @@ -18,9 +18,9 @@ from .. import mockFSLDIR as mockFSLDIR_base, make_random_image mock_fslstats = """ -#!{} +#!/usr/bin/env python3 -shape = {{outshape}} +shape = {outshape} import sys import numpy as np @@ -31,7 +31,7 @@ if len(shape) == 1: data = data.reshape(1, -1) np.savetxt(sys.stdout, data, fmt='%i') -""".format(sys.executable).strip() +""".strip() @contextlib.contextmanager