diff --git a/fsl/utils/run.py b/fsl/utils/run.py index a5b9df816f9605edf62b8daa62d082142a898057..ba3ce9a647a7c026a501ffc845eaa0b69a65c29b 100644 --- a/fsl/utils/run.py +++ b/fsl/utils/run.py @@ -166,8 +166,8 @@ def run(*args, **kwargs): - stderr: Optional file-like object to which the command's standard error stream can be forwarded. - - cmd: If ``True``, the command itself is logged to the - standard output stream(s). + - cmd: Optional file-like object to which the command itself + is logged. :returns: If ``submit`` is provided, the return value of :func:`.fslsub` is returned. Otherwise returns a single @@ -194,7 +194,7 @@ def run(*args, **kwargs): tee = log .get('tee', False) logStdout = log .get('stdout', None) logStderr = log .get('stderr', None) - logCmd = log .get('cmd', False) + logCmd = log .get('cmd', None) args = _prepareArgs(args) if not bool(submit): @@ -272,8 +272,8 @@ def _realrun(tee, logStdout, logStderr, logCmd, *args): :arg logStderr: Optional file-like object to which the command's standard error stream can be forwarded. - :arg logCmd: If ``True``, the command itself is logged to the standard - output stream(s). + :arg logCmd: Optional file-like object to which the command itself is + logged. :arg args: Command to run @@ -308,15 +308,13 @@ def _realrun(tee, logStdout, logStderr, logCmd, *args): if logStdout is not None: outstreams.append(logStdout) if logStderr is not None: errstreams.append(logStderr) - # log the command to - # stdout if requested - if logCmd: + # log the command if requested + if logCmd is not None: cmd = ' '.join(args) + '\n' - for o in outstreams: - if 'b' in getattr(o, 'mode', 'w'): - o.write(cmd.encode('utf-8')) - else: - o.write(cmd) + if 'b' in getattr(logCmd, 'mode', 'w'): + logCmd.write(cmd.encode('utf-8')) + else: + logCmd.write(cmd) stdoutt = _forwardStream(proc.stdout, *outstreams) stderrt = _forwardStream(proc.stderr, *errstreams) @@ -340,23 +338,35 @@ def _realrun(tee, logStdout, logStderr, logCmd, *args): def runfsl(*args, **kwargs): - """Call a FSL command and return its output. This function simply prepends - ``$FSLDIR/bin/`` to the command before passing it to :func:`run`. + """Call a FSL command and return its output. + + This function searches for the command in the following + locations (ordered by priority): + + 1. ``FSL_PREFIX`` + 2. ``$FSLDEVDIR/bin`` + 3. ``$FSLDIR/bin`` + + If found, the full path to the command is then passed to :func:`run`. """ - - prefix = None + prefixes = [] if FSL_PREFIX is not None: - prefix = FSL_PREFIX - elif fslplatform.fsldevdir is not None: - prefix = op.join(fslplatform.fsldevdir, 'bin') - elif fslplatform.fsldir is not None: - prefix = op.join(fslplatform.fsldir, 'bin') - else: + prefixes.append(FSL_PREFIX) + if fslplatform.fsldevdir is not None: + prefixes.append(op.join(fslplatform.fsldevdir, 'bin')) + if fslplatform.fsldir is not None: + prefixes.append(op.join(fslplatform.fsldir, 'bin')) + + if not prefixes: raise FSLNotPresent('$FSLDIR is not set - FSL cannot be found!') - args = _prepareArgs(args) - args[0] = op.join(prefix, args[0]) + args = _prepareArgs(args) + for prefix in prefixes: + cmdpath = op.join(prefix, args[0]) + if op.isfile(cmdpath): + args[0] = cmdpath + break return run(*args, **kwargs) diff --git a/fsl/wrappers/__init__.py b/fsl/wrappers/__init__.py index 3a0297a3ef7af4574087ff3e4f3ace977d1b8d3f..b37413ed3678a5fbd84f9056bdae2d52f76a64dd 100644 --- a/fsl/wrappers/__init__.py +++ b/fsl/wrappers/__init__.py @@ -80,8 +80,10 @@ from .wrapperutils import (LOAD,) # noqa from .bet import (bet, # noqa robustfov) from .eddy import (eddy_cuda, # noqa - topup) + topup, + applytopup) from .fast import (fast,) # noqa +from .fsl_anat import (fsl_anat,) # noqa from .flirt import (flirt, # noqa invxfm, applyxfm, diff --git a/fsl/wrappers/bet.py b/fsl/wrappers/bet.py index 09282f117b26374ab99a40420976fa0c1771dc8d..e20ee3fb13cb0070aca3b742b1946d34511106db 100644 --- a/fsl/wrappers/bet.py +++ b/fsl/wrappers/bet.py @@ -35,6 +35,7 @@ def bet(input, output, **kwargs): 'robust' : 'R', 'fracintensity' : 'f', 'seg' : 'n', + 'centre' : 'c', } valmap = { @@ -55,6 +56,14 @@ def bet(input, output, **kwargs): } cmd = ['bet', input, output] + + # The 'centre' argument requires three co-ordinates and can't be passed + # as a single value, so needs to be handled separately. Assume it is + # passed as a Python sequence + centre = kwargs.pop("c", None) + if centre is not None: + cmd += ['c', ] + list(centre) + cmd += wutils.applyArgStyle('-', argmap=argmap, valmap=valmap, **kwargs) return cmd diff --git a/fsl/wrappers/eddy.py b/fsl/wrappers/eddy.py index ae5c7bb4f0075c65fa363970237b26baf7797bd1..3419c99d133045a4b34762e85de4d1e5f24528d1 100644 --- a/fsl/wrappers/eddy.py +++ b/fsl/wrappers/eddy.py @@ -61,8 +61,8 @@ def eddy_cuda(imain, mask, index, acqp, bvecs, bvals, out, **kwargs): return cmd -@wutils.fileOrImage('imain', 'fout', 'iout') -@wutils.fileOrArray('datain') +@wutils.fileOrImage('imain', 'fout', 'iout', outprefix='out') +@wutils.fileOrArray('datain', outprefix='out') @wutils.fslwrapper def topup(imain, datain, **kwargs): """Wrapper for the ``topup`` command.""" @@ -78,3 +78,25 @@ def topup(imain, datain, **kwargs): cmd += wutils.applyArgStyle('--=', valmap=valmap, **kwargs) return cmd + +@wutils.fileOrImage('imain', 'out') +@wutils.fileOrArray('datain') +@wutils.fslwrapper +def applytopup(imain, datain, index, **kwargs): + """Wrapper for the ``applytopup`` command.""" + + valmap = { + 'verbose' : wutils.SHOW_IF_TRUE + } + + asrt.assertFileExists(datain) + asrt.assertIsNifti(imain) + + cmd = [ + 'applytopup', '--imain={}'.format(imain), + '--inindex={}'.format(index), + '--datain={}'.format(datain), + ] + cmd += wutils.applyArgStyle('--=', valmap=valmap, **kwargs) + + return cmd diff --git a/fsl/wrappers/fast.py b/fsl/wrappers/fast.py index a944ea77ad31f2c8487c2e8e4aec2e3b8aea1b77..b1cd712ac99e9e45d2e97f74f5dae5b38c6e80c0 100644 --- a/fsl/wrappers/fast.py +++ b/fsl/wrappers/fast.py @@ -33,12 +33,19 @@ def fast(imgs, out='fast', **kwargs): asrt.assertIsNifti(*imgs) + valmap = { + 'nobias' : wutils.SHOW_IF_TRUE, + 'verbose' : wutils.SHOW_IF_TRUE, + 'Prior' : wutils.SHOW_IF_TRUE, + 'segments' : wutils.SHOW_IF_TRUE, + } + argmap = { 'n_classes' : 'class', } cmd = ['fast', '-v', '--out=%s' % out] - cmd += wutils.applyArgStyle('--=', argmap=argmap, **kwargs) + cmd += wutils.applyArgStyle('--=', valmap=valmap, argmap=argmap, singlechar_args=True, **kwargs) cmd += imgs return cmd diff --git a/fsl/wrappers/flirt.py b/fsl/wrappers/flirt.py index cac769bf26f428d4bed62d830ec661cf43b8dd74..8b5d1d526dc6a893441204d788d58b7302d59260 100644 --- a/fsl/wrappers/flirt.py +++ b/fsl/wrappers/flirt.py @@ -59,14 +59,15 @@ def flirt(src, ref, **kwargs): return cmd -def applyxfm(src, ref, mat, out, interp='spline'): +def applyxfm(src, ref, mat, out, interp='spline', **kwargs): """Convenience function which runs ``flirt -applyxfm ...``.""" return flirt(src, ref, out=out, applyxfm=True, init=mat, - interp=interp) + interp=interp, + **kwargs) @wutils.fileOrArray() diff --git a/fsl/wrappers/fnirt.py b/fsl/wrappers/fnirt.py index bd457337357694e3bb869ef8a0c516cebd1c7cb1..f4ca120fceebed4cdd97310087a1f816e544a979 100644 --- a/fsl/wrappers/fnirt.py +++ b/fsl/wrappers/fnirt.py @@ -27,12 +27,12 @@ from . import wrapperutils as wutils 'refout', 'refmask', 'inmask') @wutils.fileOrArray('aff') @wutils.fslwrapper -def fnirt(src, ref, **kwargs): +def fnirt(src, **kwargs): """Wrapper for the ``fnirt`` command.""" - asrt.assertIsNifti(src, ref) + asrt.assertIsNifti(src) - cmd = ['fnirt', '--in={}'.format(src), '--ref={}'.format(ref)] + cmd = ['fnirt', '--in={}'.format(src)] cmd += wutils.applyArgStyle('--=', **kwargs) return cmd @@ -41,7 +41,7 @@ def fnirt(src, ref, **kwargs): @wutils.fileOrImage('src', 'ref', 'out', 'warp', 'mask') @wutils.fileOrArray('premat', 'postmat') @wutils.fslwrapper -def applywarp(src, ref, out, warp, **kwargs): +def applywarp(src, ref, out, **kwargs): """Wrapper for the ``applywarp`` command. """ valmap = { @@ -55,8 +55,7 @@ def applywarp(src, ref, out, warp, **kwargs): cmd = ['applywarp', '--in={}'.format(src), '--ref={}'.format(ref), - '--out={}'.format(out), - '--warp={}'.format(warp)] + '--out={}'.format(out)] cmd += wutils.applyArgStyle('--=', valmap=valmap, **kwargs) @@ -76,7 +75,7 @@ def invwarp(warp, ref, out, **kwargs): 'verbose' : wutils.SHOW_IF_TRUE, } - asrt.assertIsNifti(warp, ref, out) + asrt.assertIsNifti(warp, ref) cmd = ['invwarp', '--warp={}'.format(warp), @@ -88,7 +87,7 @@ def invwarp(warp, ref, out, **kwargs): return cmd -@wutils.fileOrImage('out', 'ref', 'warp1', 'warp2', 'shiftmap') +@wutils.fileOrImage('out', 'ref', 'warp1', 'warp2', 'shiftmap', 'jacobian') @wutils.fileOrArray('premat', 'midmat', 'postmat') @wutils.fslwrapper def convertwarp(out, ref, **kwargs): @@ -99,7 +98,6 @@ def convertwarp(out, ref, **kwargs): 'rel' : wutils.SHOW_IF_TRUE, 'absout' : wutils.SHOW_IF_TRUE, 'relout' : wutils.SHOW_IF_TRUE, - 'jacobian' : wutils.SHOW_IF_TRUE, 'jstats' : wutils.SHOW_IF_TRUE, 'constrainj' : wutils.SHOW_IF_TRUE, 'verbose' : wutils.SHOW_IF_TRUE, @@ -107,5 +105,4 @@ def convertwarp(out, ref, **kwargs): cmd = ['convertwarp', '--ref={}'.format(ref), '--out={}'.format(out)] cmd += wutils.applyArgStyle('--=', valmap=valmap, **kwargs) - return cmd diff --git a/fsl/wrappers/fsl_anat.py b/fsl/wrappers/fsl_anat.py new file mode 100644 index 0000000000000000000000000000000000000000..a1ea260c137049a0dd26b1a84dbbd3184dc1e78c --- /dev/null +++ b/fsl/wrappers/fsl_anat.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# +# fsl_anat.py - Wrapper for the FSL_ANAT command. +# +# Author: Martin Craig <martin.craig@eng.ox.ac.uk> +# +"""This module provides the :func:`fsl_anat` function, a wrapper for the FSL +`FSL_ANAT <https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/fsl_anat>`_ command. +""" + +import six + +import fsl.utils.assertions as asrt +from . import wrapperutils as wutils + +@wutils.fileOrImage('img', outprefix='out') +@wutils.fslwrapper +def fsl_anat(img, out='fsl_anat', **kwargs): + """Wrapper for the ``fsl_anat`` command. + + :arg img: Input structural image + :arg out: Output directory name + """ + asrt.assertIsNifti(img) + + valmap = { + 'strongbias' : wutils.SHOW_IF_TRUE, + 'weakbias' : wutils.SHOW_IF_TRUE, + 'noreorient' : wutils.SHOW_IF_TRUE, + 'nocrop' : wutils.SHOW_IF_TRUE, + 'nobias' : wutils.SHOW_IF_TRUE, + 'noreg' : wutils.SHOW_IF_TRUE, + 'nononlinreg' : wutils.SHOW_IF_TRUE, + 'noseg' : wutils.SHOW_IF_TRUE, + 'nosubcortseg' : wutils.SHOW_IF_TRUE, + 'nosearch' : wutils.SHOW_IF_TRUE, + 'nocleanup' : wutils.SHOW_IF_TRUE, + } + + argmap = { + } + + img_type = kwargs.pop("img_type", "T1") + cmd = ['fsl_anat', '-i', img, '-o', out, '-t', img_type] + smoothing = kwargs.pop("bias_smoothing", None) + if smoothing is not None: + cmd += ['-s', str(smoothing)] + + cmd += wutils.applyArgStyle('--=', valmap=valmap, argmap=argmap, singlechar_args=True, **kwargs) + + return cmd diff --git a/fsl/wrappers/wrapperutils.py b/fsl/wrappers/wrapperutils.py index 6728a23272696f295227291d723101cd366e7be5..cf452d9eee82a56d5a498beba9a159dc652a11b9 100644 --- a/fsl/wrappers/wrapperutils.py +++ b/fsl/wrappers/wrapperutils.py @@ -152,11 +152,13 @@ def cmdwrapper(func): :func:`fsl.utils.run.run` function in a standardised manner. """ def wrapper(*args, **kwargs): + stdout = kwargs.pop('stdout', True) + stderr = kwargs.pop('stderr', False) + exitcode = kwargs.pop('exitcode', False) submit = kwargs.pop('submit', None) - stderr = kwargs.pop('stderr', True) log = kwargs.pop('log', {'tee' : True}) cmd = func(*args, **kwargs) - return run.run(cmd, stderr=stderr, log=log, submit=submit) + return run.run(cmd, stderr=stderr, log=log, submit=submit, stdout=stdout, exitcode=exitcode) return _update_wrapper(wrapper, func) @@ -166,11 +168,13 @@ def fslwrapper(func): :func:`fsl.utils.run.runfsl` function in a standardised manner. """ def wrapper(*args, **kwargs): + stdout = kwargs.pop('stdout', True) + stderr = kwargs.pop('stderr', False) + exitcode = kwargs.pop('exitcode', False) submit = kwargs.pop('submit', None) - stderr = kwargs.pop('stderr', True) log = kwargs.pop('log', {'tee' : True}) cmd = func(*args, **kwargs) - return run.runfsl(cmd, stderr=stderr, log=log, submit=submit) + return run.runfsl(cmd, stderr=stderr, log=log, submit=submit, stdout=stdout, exitcode=exitcode) return _update_wrapper(wrapper, func) @@ -192,7 +196,7 @@ generated command line arguments. """ -def applyArgStyle(style, valsep=None, argmap=None, valmap=None, **kwargs): +def applyArgStyle(style, valsep=None, argmap=None, valmap=None, singlechar_args=False, **kwargs): """Turns the given ``kwargs`` into command line options. This function is intended to be used to automatically generate command line options from arguments passed into a Python function. @@ -251,6 +255,9 @@ def applyArgStyle(style, valsep=None, argmap=None, valmap=None, **kwargs): The argument for any options not specified in the ``valmap`` will be converted into strings. + :arg singlechar_args: If True, single character arguments always take a single + hyphen prefix (e.g. -h) regardless of the style + :arg kwargs: Arguments to be converted into command-line options. :returns: A list containing the generated command-line options. @@ -276,7 +283,7 @@ def applyArgStyle(style, valsep=None, argmap=None, valmap=None, **kwargs): if valmap is None: valmap = {} def fmtarg(arg): - if style in ('-', '-='): arg = '-{}'.format(arg) + if style in ('-', '-=') or (singlechar_args and len(arg) == 1): arg = '-{}'.format(arg) elif style in ('--', '--='): arg = '--{}'.format(arg) return arg @@ -303,11 +310,12 @@ def applyArgStyle(style, valsep=None, argmap=None, valmap=None, **kwargs): for k, v in kwargs.items(): + if v is None: continue + k = argmap.get(k, k) mapv = valmap.get(k, fmtval(v)) k = fmtarg(k) - if mapv in (SHOW_IF_TRUE, HIDE_IF_TRUE): if (mapv is SHOW_IF_TRUE and v) or \ (mapv is HIDE_IF_TRUE and not v): @@ -849,7 +857,6 @@ class _FileOrThing(object): for prefixed in it.chain(*allPrefixed): fullpath = prefixed prefixed = op.relpath(prefixed, workdir) - for prefPat, prefName in prefixes.items(): if not fnmatch.fnmatch(prefixed, '{}*'.format(prefPat)): continue @@ -862,10 +869,17 @@ class _FileOrThing(object): # not of the correct type. fval = self.__load(fullpath) if fval is not None: - prefixed = self.__removeExt(prefixed) + noext = self.__removeExt(prefixed) prefPat = prefPat.replace('\\', '\\\\') - prefixed = re.sub('^' + prefPat, prefName, prefixed) - result[prefixed] = fval + noext = re.sub('^' + prefPat, prefName, noext) + # If there is already an item in result with the + # name (stripped of prefix), then instead store + # the result with the full prefixed name + if noext not in result: + result[noext] = fval + else: + withext = re.sub('^' + prefPat, prefName, prefixed) + result[withext] = fval break return result @@ -887,8 +901,11 @@ def fileOrImage(*args, **kwargs): infile = None - if isinstance(val, (fslimage.Image, nib.nifti1.Nifti1Image)): - intypes.append(type(val)) + if isinstance(val, fslimage.Image): + intypes.append(fslimage.Image) + + elif isinstance(val, nib.nifti1.Nifti1Image): + intypes.append(nib.nifti1.Nifti1Image) if isinstance(val, fslimage.Image): val = val.nibImage