From f011b82f325c8f09676379b5595a43ffd0dc138a Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauldmccarthy@gmail.com>
Date: Thu, 1 Mar 2018 17:38:58 +0000
Subject: [PATCH] further experimentation

---
 fsl/wrappers/__init__.py     |  50 ++--
 fsl/wrappers/bet.py          |   5 +-
 fsl/wrappers/flirt.py        |  90 +++----
 fsl/wrappers/wrapperutils.py | 466 +++++++++++++++++++++++++++--------
 4 files changed, 435 insertions(+), 176 deletions(-)

diff --git a/fsl/wrappers/__init__.py b/fsl/wrappers/__init__.py
index 3bf0f2a63..8d50b77ad 100644
--- a/fsl/wrappers/__init__.py
+++ b/fsl/wrappers/__init__.py
@@ -8,25 +8,31 @@
 them to be called from Python.
 """
 
-
-from .bet      import (bet,)            # noqa
-from .eddy     import (eddy_cuda,       # noqa
-                       topup)
-from .flirt    import (flirt,           # noqa
-                       invxfm,
-                       applyxfm,
-                       concatxfm,
-                       mcflirt)
-from .fnirt    import (fnirt,           # noqa
-                       applywarp,
-                       invwarp,
-                       convertwarp)
-from .fslmaths import (fslmaths,)       # noqa
-from .fugue    import (fugue,           # noqa
-                       sigloss)
-from .melodic  import (melodic,         # noqa
-                       fsl_regfilt)
-from .misc     import (fslreorient2std, # noqa
-                       fslroi,
-                       slicer,
-                       cluster)
+from .wrapperutils import (applyArgStyle,   # noqa
+                           required,
+                           fileOrImage,
+                           fileOrArray,
+                           RETURN,
+                           SHOW_IF_TRUE,
+                           HIDE_IF_TRUE)
+from .bet          import (bet,)            # noqa
+from .eddy         import (eddy_cuda,       # noqa
+                           topup)
+from .flirt        import (flirt,           # noqa
+                           invxfm,
+                           applyxfm,
+                           concatxfm,
+                           mcflirt)
+from .fnirt        import (fnirt,           # noqa
+                           applywarp,
+                           invwarp,
+                           convertwarp)
+from .fslmaths     import (fslmaths,)       # noqa
+from .fugue        import (fugue,           # noqa
+                           sigloss)
+from .melodic      import (melodic,         # noqa
+                           fsl_regfilt)
+from .misc         import (fslreorient2std, # noqa
+                           fslroi,
+                           slicer,
+                           cluster)
diff --git a/fsl/wrappers/bet.py b/fsl/wrappers/bet.py
index 58c4aab3b..c9de633f8 100644
--- a/fsl/wrappers/bet.py
+++ b/fsl/wrappers/bet.py
@@ -11,6 +11,10 @@ import fsl.utils.run        as run
 from . import wrapperutils  as wutils
 
 
+def pybet():   # ??
+    pass
+
+
 @wutils.fileOrImage('input', 'output')
 def bet(input, output, **kwargs):
     """Delete non-brain tissue from an image of the whole head.
@@ -20,7 +24,6 @@ def bet(input, output, **kwargs):
     :arg mask:
     :arg robust:
     :arg fracintensity:
-    :arg seg:
 
     Refer to the ``bet`` command-line help for details on all arguments.
     """
diff --git a/fsl/wrappers/flirt.py b/fsl/wrappers/flirt.py
index 9247ce455..4c777a61f 100644
--- a/fsl/wrappers/flirt.py
+++ b/fsl/wrappers/flirt.py
@@ -20,60 +20,34 @@
 import fsl.utils.run        as run
 import fsl.utils.assertions as asrt
 import fsl.data.image       as fslimage
+from . import wrapperutils  as wutils
 
 
-def flirt(src, ref, out=None, omat=None, dof=None, cost=None, wmseg=None,
-          init=None, schedule=None, echospacing=None, pedir=None,
-          fieldmap=None, fieldmapmask=None, bbrslope=None, bbrtype=None,
-          interp=None, refweight=None, applyisoxfm=None, usesqform=False,
-          nosearch=False, verbose=0):
+@wutils.required('src', 'ref')
+@wutils.fileOrImage('src', 'ref', 'out', 'wmseg', 'fieldmap', 'fieldmapmask')
+@wutils.fileOrArray('init', 'omat', 'wmcoords', 'wmnorms')
+def flirt(src, ref, **kwargs):
     """FLIRT (FMRIB's Linear Image Registration Tool)."""
+
     asrt.assertIsNifti(src, ref)
-    asrt.assertFileExists(src, ref)
 
-    cmd = "flirt -in {0} -ref {1}".format(src, ref)
-
-    if out is not None:
-        asrt.assertIsNifti(out)
-        cmd += " -out {0}".format(out)
-    if omat is not None:
-        cmd += " -omat {0}".format(omat)
-    if dof is not None:
-        cmd += " -dof {0}".format(dof)
-    if cost is not None:
-        cmd += " -cost {0}".format(cost)
-    if wmseg is not None:
-        asrt.assertIsNifti(wmseg)
-        cmd += " -wmseg {0}".format(wmseg)
-    if init is not None:
-        cmd += " -init {0}".format(init)
-    if schedule is not None:
-        cmd += " -schedule {0}".format(schedule)
-    if echospacing is not None:
-        cmd += " -echospacing {0}".format(echospacing)
-    if pedir is not None:
-        cmd += " -pedir {0}".format(pedir)
-    if fieldmap is not None:
-        cmd += " -fieldmap {0}".format(fieldmap)
-    if fieldmapmask is not None:
-        cmd += " -fieldmapmask {0}".format(fieldmapmask)
-    if bbrslope is not None:
-        cmd += " -bbrslope {0}".format(bbrslope)
-    if bbrtype is not None:
-        cmd += " -bbrtype {0}".format(bbrtype)
-    if interp is not None:
-        cmd += " -interp {0}".format(interp)
-    if refweight is not None:
-        asrt.assertIsNifti(refweight)
-        cmd += " -refweight {0}".format(refweight)
-    if applyisoxfm is not None:
-        cmd += " -applyisoxfm {0}".format(applyisoxfm)
-    if verbose is not None:
-        cmd += " -verbose {0}".format(verbose)
-    if usesqform:
-        cmd += " -usesqform"
-    if nosearch:
-        cmd += " -nosearch"
+    valmap = {
+        'usesqform'    : wutils.SHOW_IF_TRUE,
+        'displayinit'  : wutils.SHOW_IF_TRUE,
+        'noresample'   : wutils.SHOW_IF_TRUE,
+        'forcescaling' : wutils.SHOW_IF_TRUE,
+        'applyxfm'     : wutils.SHOW_IF_TRUE,
+        'nosearch'     : wutils.SHOW_IF_TRUE,
+        'noclamp'      : wutils.SHOW_IF_TRUE,
+        'noresampblur' : wutils.SHOW_IF_TRUE,
+        '2D'           : wutils.SHOW_IF_TRUE,
+        'v'            : wutils.SHOW_IF_TRUE,
+        'version'      : wutils.SHOW_IF_TRUE,
+        'help'         : wutils.SHOW_IF_TRUE,
+    }
+
+    cmd  = ['flirt', '-in', src, '-ref', ref]
+    cmd += wutils.applyArgStyle('-', valmap=valmap, **kwargs)
 
     return run.runfsl(cmd)
 
@@ -94,13 +68,25 @@ def applyxfm(src, ref, mat, out, interp='spline'):
     cmd = "flirt -init {0} -in {1} -ref {2} -applyxfm -out {3} -interp {4}"
     return run.runfsl(cmd.format(mat, src, ref, out, interp))
 
-
+@wutils.required(   'inmat1', 'inmat2', 'outmat')
+@wutils.fileOrArray('inmat1', 'inmat2', 'outmat')
 def concatxfm(inmat1, inmat2, outmat):
     """Tool to concatenate two FSL transformation matrices."""
+
+    print('inmat1', inmat1)
+    print('inmat2', inmat2)
+    print('outmat', outmat)
+
     asrt.assertFileExists(inmat1, inmat2)
 
-    cmd = "convert_xfm -omat {0} -concat {1} {2}"
-    return run.runfsl(cmd.format(outmat, inmat2, inmat1))
+    cmd = ['convert_xfm',
+           '-omat',
+           outmat,
+           '-concat',
+           inmat2,
+           inmat1]
+
+    return run.runfsl(cmd)
 
 
 def mcflirt(infile, outfile, reffile=None, spline_final=True, plots=True,
diff --git a/fsl/wrappers/wrapperutils.py b/fsl/wrappers/wrapperutils.py
index 629845bf2..b60c57912 100644
--- a/fsl/wrappers/wrapperutils.py
+++ b/fsl/wrappers/wrapperutils.py
@@ -1,13 +1,26 @@
 #!/usr/bin/env python
 #
-# wrapperutils.py -
+# wrapperutils.py - Functions and decorators used by the FSL wrapper
+# functions.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
+"""This module contains functions and decorators used by the FSL wrapper
+functions.
+
+.. autosummary::
+   :nosignatures:
+
+   applyArgStyle
+   required
+   fileOrImage
+   fileOrArray
+"""
 
 
 import os.path as op
 import            os
+import            sys
 import            inspect
 import            tempfile
 import            warnings
@@ -16,50 +29,133 @@ import            collections
 
 import            six
 import nibabel as nib
+import numpy   as np
 
 import fsl.utils.tempdir as tempdir
 import fsl.data.image    as fslimage
 
 
-class _BooleanFlag(object):
-    def __init__(self, show):
-        self.show = show
-    def __eq__(self, other):
-        return type(other) == type(self) and self.show == other.show
+def _update_wrapper(wrapper, wrapped, *args, **kwargs):
+    """Replacement for the built-in ``functools.update_wrapper``. This
+    implementation ensures that the wrapper function has an attribute
+    called ``__wrapped__``, which refers to the ``wrapped`` function.
+
+    This behaviour is only required in Python versions < 3.4.
+    """
+
+    wrapper = functools.update_wrapper(wrapper, wrapped, *args, **kwargs)
+
+    # Python >= 3.4 does things right
+    if sys.version_info[0] * 10 + sys.version_info[1] < 3.4:
+        wrapper.__wrapped__ = wrapped
+    return wrapper
 
 
-SHOW_IF_TRUE = _BooleanFlag(True)
-HIDE_IF_TRUE = _BooleanFlag(False)
+def _unwrap(func):
+    """Replacement for the built-in ``inspect.unwrap`` function, which
+    is not present in Python versions prior to 3.4.
+    """
+
+    # Python >= 3.4 has an inspect.unwrap function
+    if sys.version_info[0] * 10 + sys.version_info[1] < 3.4:
+        return inspect.unwrap(func)
+
+    # Otherwise we follow the __wrapped__ chain ourselves
+    if hasattr(func, '__wrapped__'):
+        return _unwrap(func.__wrapped__)
+
+    return func
+
+
+
+SHOW_IF_TRUE = object()
+"""Constant to be used in the ``valmap`` passed to the :func:`applyArgStyle`
+function.
+
+When a ``SHOW_IF_TRUE`` argument is ``True``, it is added to the generated
+command line arguments.
+"""
+
+
+HIDE_IF_TRUE = object()
+"""Constant to be used in the ``valmap`` passed to the :func:`applyArgStyle`
+function.
+
+When a ``HIDE_IF_TRUE`` argument is ``True``, it is suppressed from the
+generated command line arguments.
+"""
 
 
 def applyArgStyle(style, argmap=None, valmap=None, **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.
+
+    :arg style:  Controls how the ``kwargs`` are converted into command-line
+                 options - must be one of the following:
+                  - `'-'`: ``-name val``
+                  - `'--'`: ``--name val``
+                  - `'-='`: ``-name=val``
+                  - `'--='`: ``--name=val``
+
+    :arg argmap: Dictionary of ``{kwarg-name : cli-name}`` mappings. This can
+                 be used if you want to use different argument names in your
+                 Python function for the command-line options.
+
+    :arg valmap: Dictionary of ``{cli-name : value}`` mappings. This can be
+                 used to define specific semantics for some command-line
+                 options. Acceptable values for ``value`` are as follows
+
+                  - :data:`SHOW_IF_TRUE` - if the argument is present, and
+                    ``True`` in ``kwargs``, the command line option
+                    will be added (without any arguments).
+
+                  - :data:`HIDE_IF_TRUE` - if the argument is present, and
+                    ``False`` in ``kwargs``, the command line option
+                    will be added (without any arguments).
+
+                  - Any other constant value. If the argument is present
+                    in ``kwargs``, its command-line option will be
+                    added, with the constant value as its argument.
+
+                 The argument for any options not specified in the ``valmap``
+                 will be converted into strings.
 
-    def fmtarg(arg, style):
+    :arg kwargs: Arguments to be converted into command-line options.
+
+    :returns:    A list containing the generated command-line options.
+    """
+
+    if style not in ('-', '--', '-=', '--='):
+        raise ValueError('Invalid style: {}'.format(style))
+
+    if argmap is None: argmap = {}
+    if valmap is None: valmap = {}
+
+    def fmtarg(arg):
         if   style in ('-',  '-='):  arg =  '-{}'.format(arg)
         elif style in ('--', '--='): arg = '--{}'.format(arg)
         return arg
 
-    def fmtval(val, style=None):
+    def fmtval(val):
         if     isinstance(val, collections.Sequence) and \
            not isinstance(val, six.string_types):
             return ' '.join([str(v) for v in val])
         else:
             return str(val)
 
-    if style not in ('-', '--', '-=', '--='):
-        raise ValueError('Invalid style: {}'.format(style))
-
     args = []
 
     for k, v in kwargs.items():
 
         k    = argmap.get(k, k)
-        mapv = valmap.get(k, fmtval(v, style))
-        k    = fmtarg(k, style)
+        mapv = valmap.get(k, fmtval(v))
+        k    = fmtarg(k)
+
+        if (mapv is SHOW_IF_TRUE and     v) or \
+           (mapv is HIDE_IF_TRUE and not v):
+            args.append(k)
 
-        if mapv in (SHOW_IF_TRUE, HIDE_IF_TRUE):
-            if v == mapv.show:
-                args.append(k)
         elif '=' in style:
             args.append('{}={}'.format(k, mapv))
         else:
@@ -72,110 +168,216 @@ def required(*reqargs):
     """Decorator which makes sure that all specified keyword arguments are
     present before calling the decorated function.
     """
+
     def decorator(func):
-        def wrapper(**kwargs):
+        def wrapper(*args, **kwargs):
+            kwargs = kwargs.copy()
+            kwargs.update(argsToKwargs(func, args))
             for reqarg in reqargs:
                 assert reqarg in kwargs
             return func(**kwargs)
-        return wrapper
+        return _update_wrapper(wrapper, func)
+
     return decorator
 
 
 def argsToKwargs(func, args):
     """Given a function, and a sequence of positional arguments destined
     for that function, converts the positional arguments into a dict
-    of keyword arguments. Used by the :class:`_FileOrImage` class.
+    of keyword arguments. Used by the :class:`_FileOrThing` class.
     """
-    # getfullargspec is the only way to get the names
-    # of positional arguments in Python 2.x. It is
-    # deprecated in python 3.5, but not in python 3.6.
-    with warnings.catch_warnings():
-        warnings.filterwarnings('ignore', category=DeprecationWarning)
-        spec = inspect.getfullargspec(func)
+
+    func = _unwrap(func)
+
+    # getargspec is the only way to get the names
+    # of positional arguments in Python 2.x.
+    if sys.version_info[0] < 3:
+        argnames = inspect.getargspec(func).args
+
+    # getargspec is deprecated in python 3.x
+    else:
+
+        # getfullargspec is deprecated in
+        # python 3.5, but not in python 3.6.
+        with warnings.catch_warnings():
+            warnings.filterwarnings('ignore', category=DeprecationWarning)
+            argnames = inspect.getfullargspec(func).args
 
     kwargs = collections.OrderedDict()
-    for name, val in zip(spec.args, args):
+    for name, val in zip(argnames, args):
         kwargs[name] = val
 
     return kwargs
 
 
 RETURN = object()
-"""
+"""Constant used by the :class:`_FileOrThing` class to indicate that an output
+file should be loaded into memory and returned as a Python object.
 """
 
 
-class _FileOrImage(object):
-    """
+class _FileOrThing(object):
+    """Decorator which ensures that certain arguments which are passed into the
+    decorated function are always passed as file names. Both positional and
+    keyword arguments can be specified.
+
+
+    The ``_FileOrThing`` class is not intended to be used directly - see the
+    :func:`fileOrImage` and :func:`fileOrArray` decorator functions for more
+    details.
+
+    These decorators are intended for functions which wrap a command-line tool,
+    i.e. where some inputs/outputs need to be specified as file names.
+
+
+    **Inputs**
+
+
+    Any arguments which are not of type ``Thing`` are passed through to the
+    decorated function unmodified.  Arguments which are of type ``Thing`` are
+    saved to a temporary file, and the name of that file is passed to the
+    function.
+
 
-    Inputs:
-      - In-memory nibabel images loaded from a file. The image is replaced with
-        its file name.
+    **Outputs**
 
-      - In-memory nibabel images. The image is saved to a temporary file, and
-        replaced with the temporary file's name. The file is deleted after the
-        function has returned.
 
-    Outputs:
-      - File name:  The file name is passed straight through to the function.
-      - ``RETURN``: A temporary file name is passed to the function. After the
-        function has completed, the image is loaded into memory and the
-        temporary file is deleted. The image is returned from the function
-        call.
+    If an argument is given the special :data:`RETURN` value, it is assumed
+    to be an output argument. In this case, it is replaced with a temporary
+    file name then, after the function has completed, that file is loaded
+    into memory, and the value returned (along with the function's output,
+    and any other arguments with a value of ``RETURN``).
+
+
+    **Return value**
+
+
+    Functions decorated with a ``_FileOrThing`` decorator will always return a
+    tuple, where the first element is the function's actual return value. The
+    remainder of the tuple will contain any arguments that were given the
+    special ``RETURN`` value. ``None`` is returned for any ``RETURN``
+    arguments corresponded to output files that were not generated by the
+    function.
+
+
+    **Example**
+
+
+    As an example of using the ``fileOrArray`` decorator on a function
+    which concatenates two files containing affine transformations, and
+    saves the output to a file::
+
+        # if atob, btoc, or output are passed
+        # in as arrays, they are converted to
+        # file names.
+        @fileOrArray('atob', 'btoc', 'output')
+        def concat(atob, btoc, output=None):
+
+            # inputs are guaranteed to be files
+            atob = np.loadtxt(atob)
+            btoc = np.loadtxt(atoc)
+
+            atoc = np.dot(btoc, atob)
+
+            if output is not None:
+                np.savetxt(output, atoc)
+
+
+    Because we have decorated the ``concat`` function with :func:`fileToArray`,
+    it can be called with either file names, or Numpy arrays::
+
+        # All arguments are passed through
+        # unmodified - the output will be
+        # saved to a file called atoc.mat
+        concat('atob.txt', 'btoc.txt', 'atoc.mat')
+
+        # The output is returned as a numpy
+        # array (in a tuple with the concat
+        # function's return value)
+        atoc = concat('atob.txt', 'btoc.txt', RETURN)[1]
+
+        # The inputs are saved to temporary
+        # files, and those file names are
+        # passed to the concat function.
+        atoc = concat(np.diag([2, 2, 2, 0]), np.diag([3, 3, 3, 3]), RETURN)[1]
     """
 
 
-    def __init__(self, *imgargs):
-        """
+    def __init__(self, prepareThing, loadThing, *things):
+        """Initialise a ``_FileOrThing`` decorator.
+
+        :arg prepareThing: Function which
+        :arg loadThing:    Function which is called for arguments that
+                           were set to :data:`RETURN`.
+
+        :arg things:
         """
-        self.__imgargs = imgargs
+        self.__prepareThing = prepareThing
+        self.__loadThing    = loadThing
+        self.__things       = things
 
 
     def __call__(self, func):
-        """
-        """
-        return functools.partial(self.__wrapper, func)
+        """Creates and returns the real decorator function. """
+
+        self.__func          = func
+        self.__isFileOrThing = False
+
+        if hasattr(func, '__self__'):
+            self.__isFileOrThing = isinstance(func.__self__, _FileOrThing)
+
+        wrapper = functools.partial(self.__wrapper, func)
+
+        return _update_wrapper(wrapper, func)
 
 
     def __wrapper(self, func, *args, **kwargs):
-        """
+        """Function which wraps ``func``, ensuring that any arguments of
+        type ``Thing`` are saved to temporary files, and any arguments
+        with the value :data:`RETURN` are loaded and returned.
         """
 
+        func          = self.__func
+        isFileOrThing = self.__isFileOrThing
+
+        kwargs = kwargs.copy()
         kwargs.update(argsToKwargs(func, args))
 
         # Create a tempdir to store any temporary
-        # input/output images, but don't change
+        # input/output things, but don't change
         # into it, as file paths passed to the
         # function may be relative.
         with tempdir.tempdir(changeto=False) as td:
 
-            kwargs, infiles, outfiles = self.__prepareArgs(td, kwargs)
+            kwargs, infiles, outfiles = self.__prepareThings(td, kwargs)
 
             # Call the function
-            result  = func(**kwargs)
+            result = func(**kwargs)
 
-            # Load the output images that
+            # Load the output things that
             # were specified as RETURN
-            outimgs = []
+            outthings = []
             for of in outfiles:
 
                 # output file didn't get created
                 if not op.exists(of):
-                    oi = None
+                    ot = None
 
-                # load the file, and create
-                # an in-memory copy (the file
-                # is going to get deleted)
+                # load the thing
                 else:
-                    oi = nib.load(of)
-                    oi = nib.nifti1.Nifti1Image(oi.get_data(), None, oi.header)
+                    ot = self.__loadThing(of)
 
-                outimgs.append(oi)
+                outthings.append(ot)
 
-            return tuple([result] + outimgs)
+            if isFileOrThing:
+                things = result[1:]
+                result = result[0]
+                return tuple([result] + things + outthings)
+            else:
+                return tuple([result] + outthings)
 
 
-    def __prepareArgs(self, workdir, kwargs):
+    def __prepareThings(self, workdir, kwargs):
         """
         """
 
@@ -183,48 +385,110 @@ class _FileOrImage(object):
         infiles  = []
         outfiles = []
 
-        for imgarg in self.__imgargs:
+        for tname in self.__things:
 
-            img = kwargs.get(imgarg, None)
+            tval = kwargs.get(tname, None)
 
-            # Not specified, nothing to do
-            if img is None:
+            if tval is None:
                 continue
 
-            # This is an input image which has
-            # been specified as an in-memory
-            # nibabel image. if the image has
-            # a backing file, replace the image
-            # object with the file name.
-            # Otherwise, save the image out to
-            # a temporary file, and replace the
-            # image with the file name.
-            if isinstance(img, nib.nifti1.Nifti1Image):
-                imgfile = img.get_filename()
-
-                # in-memory image - we have
-                # to save it out to a file
-                if imgfile is None:
-
-                    hd, imgfile = tempfile.mkstemp(fslimage.defaultExt())
-
-                    os.close(hd)
-                    img.to_filename(imgfile)
-                    infiles.append(imgfile)
-
-                # replace the image with its
-                # file name
-                kwargs[img] = imgfile
-
-            # This is an output image, and the
-            # caller has requested that it be
-            # returned from the function call
-            # as an in-memory image.
-            if img == RETURN:
-                kwargs[imgarg] = '{}.nii.gz'.format(imgarg)
-                outfiles.append(imgarg)
+            tval, infile, outfile = self.__prepareThing(workdir, tname, tval)
+
+            if infile  is not None: infiles .append(infile)
+            if outfile is not None: outfiles.append(outfile)
+
+            kwargs[tname] = tval
 
         return kwargs, infiles, outfiles
 
 
-fileOrImage = _FileOrImage
+def fileOrImage(*imgargs):
+    """Decorator which can be used to ensure that any NIfTI images are saved
+    to file, and output images can be loaded and returned as ``nibabel``
+    image objects.
+    """
+
+    def prepareArg(workdir, name, val):
+
+        newval  = val
+        infile  = None
+        outfile = None
+
+        # This is an input image which has
+        # been specified as an in-memory
+        # nibabel image. if the image has
+        # a backing file, replace the image
+        # object with the file name.
+        # Otherwise, save the image out to
+        # a temporary file, and replace the
+        # image with the file name.
+        if isinstance(val, nib.nifti1.Nifti1Image):
+            imgfile = val.get_filename()
+
+            # in-memory image - we have
+            # to save it out to a file
+            if imgfile is None:
+
+                hd, imgfile = tempfile.mkstemp(fslimage.defaultExt())
+
+                os.close(hd)
+                val.to_filename(imgfile)
+                infile = imgfile
+
+            # replace the image with its
+            # file name
+            newval = imgfile
+
+        # This is an output image, and the
+        # caller has requested that it be
+        # returned from the function call
+        # as an in-memory image.
+        elif val == RETURN:
+            newval  = op.join(workdir, '{}.nii.gz'.format(name))
+            outfile = newval
+
+        return newval, infile, outfile
+
+    def loadImage(path):
+        # create an independent in-memory
+        # copy of the image file
+        img = nib.load(path)
+        return nib.nifti1.Nifti1Image(img.get_data(), None, img.header)
+
+    return _FileOrThing(prepareArg, loadImage, *imgargs)
+
+
+def fileOrArray(*arrargs):
+    """Decorator which can be used to ensure that any Numpy arrays are saved
+    to text files, and output files can be loaded and returned as Numpy arrays.
+    """
+
+    def prepareArg(workdir, name, val):
+
+        newval  = val
+        infile  = None
+        outfile = None
+
+        # Input has been provided as a numpy
+        # array - save it to a file, and
+        # replace the argument with the file
+        # name
+        if isinstance(val, np.ndarray):
+
+            hd, arrfile = tempfile.mkstemp('.txt')
+
+            os.close(hd)
+
+            np.savetxt(arrfile, val, fmt='%0.18f')
+            newval = arrfile
+
+        # This is an output, and the caller has
+        # requested that it be returned from the
+        # function call as an in-memory array.
+        elif val == RETURN:
+            newval  = op.join(workdir, '{}.txt'.format(name))
+            outfile = newval
+
+        return newval, infile, outfile
+
+    return _FileOrThing(prepareArg, np.loadtxt, *arrargs)
-- 
GitLab