Skip to content
Snippets Groups Projects
Commit 390edcd0 authored by Paul McCarthy's avatar Paul McCarthy :mountain_bicyclist:
Browse files

Merge branch 'enh/wrappers' into 'master'

Enh/wrappers

See merge request fsl/fslpy!59
parents 431c4d59 70107c8d
No related branches found
No related tags found
No related merge requests found
Showing
with 541 additions and 92 deletions
...@@ -2,6 +2,32 @@ This document contains the ``fslpy`` release history in reverse chronological ...@@ -2,6 +2,32 @@ This document contains the ``fslpy`` release history in reverse chronological
order. order.
1.10.0 (Wednesday July 18th 2018)
---------------------------------
Added
^^^^^
* A new script, :mod:`.extract_noise`, which can be used to extract ICA
component time courses from a MELODIC ICA analysis.
* New :func:`.path.allFiles` function which returns all files underneath a
directory.
* The :func:`.fileOrImage` and :func:`.fileOrArray` decorators now support
loading of files which are specified with an output basename.
* New :mod:`.fast` wrapper function for the FSL FAST tool.
Changed
^^^^^^^
* When using the :func:`.run.run` function, the command output/error streams
are now forwarded immediately.
* Removed dependency on ``pytest-runner``.
1.9.0 (Monday June 4th 2018) 1.9.0 (Monday June 4th 2018)
---------------------------- ----------------------------
......
``fsl.utils.fslsub``
====================
.. automodule:: fsl.utils.fslsub
:members:
:undoc-members:
:show-inheritance:
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
fsl.utils.assertions fsl.utils.assertions
fsl.utils.cache fsl.utils.cache
fsl.utils.ensure fsl.utils.ensure
fsl.utils.fslsub
fsl.utils.idle fsl.utils.idle
fsl.utils.imcp fsl.utils.imcp
fsl.utils.memoize fsl.utils.memoize
......
``fsl.wrappers.fast``
=====================
.. automodule:: fsl.wrappers.fast
:members:
:undoc-members:
:show-inheritance:
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
fsl.wrappers.bet fsl.wrappers.bet
fsl.wrappers.eddy fsl.wrappers.eddy
fsl.wrappers.fast
fsl.wrappers.flirt fsl.wrappers.flirt
fsl.wrappers.fnirt fsl.wrappers.fnirt
fsl.wrappers.fslmaths fsl.wrappers.fslmaths
......
...@@ -62,8 +62,10 @@ def loadLabelFile(filename, ...@@ -62,8 +62,10 @@ def loadLabelFile(filename,
separated by commas: separated by commas:
- The component index (starting from 1). - The component index (starting from 1).
- One or more labels for the component (multiple labels must be - One or more labels for the component (multiple labels must be
comma-separated). comma-separated).
- ``'True'`` if the component has been classified as *bad*, - ``'True'`` if the component has been classified as *bad*,
``'False'`` otherwise. This field is optional - if the last ``'False'`` otherwise. This field is optional - if the last
comma-separated token on a line is not equal (case-insensitive) comma-separated token on a line is not equal (case-insensitive)
...@@ -91,12 +93,15 @@ def loadLabelFile(filename, ...@@ -91,12 +93,15 @@ def loadLabelFile(filename,
file is returned. file is returned.
:returns: A tuple containing: :returns: A tuple containing:
- The path to the melodic directory as specified in the label
file - The path to the melodic directory as specified in the label
- A list of lists, one list per component, with each list file
containing the labels for the corresponding component.
- If ``returnIndices is True``, a list of the noisy component - A list of lists, one list per component, with each list
indices (starting from 1) that were specified in the file. containing the labels for the corresponding component.
- If ``returnIndices is True``, a list of the noisy component
indices (starting from 1) that were specified in the file.
""" """
signalLabels = None signalLabels = None
......
...@@ -90,8 +90,11 @@ def submit(command, ...@@ -90,8 +90,11 @@ def submit(command,
queuing options queuing options
:arg multi_threaded: Submit a multi-threaded task - Set to a tuple :arg multi_threaded: Submit a multi-threaded task - Set to a tuple
containing two elements: containing two elements:
- <pename>: a PE configures for the requested queues - <pename>: a PE configures for the requested queues
- <threads>: number of threads to run - <threads>: number of threads to run
:arg verbose: If True, use verbose mode :arg verbose: If True, use verbose mode
:return: tuple of submitted job ids :return: tuple of submitted job ids
......
...@@ -13,6 +13,7 @@ paths. ...@@ -13,6 +13,7 @@ paths.
deepest deepest
shallowest shallowest
allFiles
hasExt hasExt
addExt addExt
removeExt removeExt
...@@ -24,8 +25,9 @@ paths. ...@@ -24,8 +25,9 @@ paths.
""" """
import glob
import os.path as op import os.path as op
import os
import glob
class PathError(Exception): class PathError(Exception):
...@@ -78,6 +80,20 @@ def shallowest(path, suffixes): ...@@ -78,6 +80,20 @@ def shallowest(path, suffixes):
return None return None
def allFiles(root):
"""Return a list containing all files which exist underneath the specified
``root`` directory.
"""
files = []
for dirpath, _, filenames in os.walk(root):
filenames = [op.join(dirpath, f) for f in filenames]
files.extend(filenames)
return files
def hasExt(path, allowedExts): def hasExt(path, allowedExts):
"""Convenience function which returns ``True`` if the given ``path`` """Convenience function which returns ``True`` if the given ``path``
ends with any of the given ``allowedExts``, ``False`` otherwise. ends with any of the given ``allowedExts``, ``False`` otherwise.
......
...@@ -107,7 +107,7 @@ def _forwardStream(in_, *outs): ...@@ -107,7 +107,7 @@ def _forwardStream(in_, *outs):
omodes = [getattr(o, 'mode', 'w') for o in outs] omodes = [getattr(o, 'mode', 'w') for o in outs]
def realForward(): def realForward():
for line in in_: for line in iter(in_.readline, b''):
for i, o in enumerate(outs): for i, o in enumerate(outs):
if 'b' in omodes[i]: o.write(line) if 'b' in omodes[i]: o.write(line)
else: o.write(line.decode('utf-8')) else: o.write(line.decode('utf-8'))
......
...@@ -21,7 +21,7 @@ import contextlib ...@@ -21,7 +21,7 @@ import contextlib
@contextlib.contextmanager @contextlib.contextmanager
def tempdir(root=None, changeto=True): def tempdir(root=None, changeto=True, override=None):
"""Returns a context manager which creates and returns a temporary """Returns a context manager which creates and returns a temporary
directory, and then deletes it on exit. directory, and then deletes it on exit.
...@@ -32,17 +32,25 @@ def tempdir(root=None, changeto=True): ...@@ -32,17 +32,25 @@ def tempdir(root=None, changeto=True):
:arg changeto: If ``True`` (the default), current working directory is set :arg changeto: If ``True`` (the default), current working directory is set
to the new temporary directory before yielding, and restored to the new temporary directory before yielding, and restored
afterwards. afterwards.
:arg override: Don't create a temporary directory, but use this one
instead. This allows ``tempdir`` to be used as a context
manager when a temporary directory already exists.
""" """
testdir = tempfile.mkdtemp(dir=root) if override is None:
prevdir = os.getcwd() testdir = tempfile.mkdtemp(dir=root)
try: prevdir = os.getcwd()
else:
testdir = override
try:
if changeto: if changeto:
os.chdir(testdir) os.chdir(testdir)
yield testdir yield testdir
finally: finally:
if changeto: if override is None:
os.chdir(prevdir) if changeto:
shutil.rmtree(testdir) os.chdir(prevdir)
shutil.rmtree(testdir)
...@@ -81,6 +81,7 @@ from .bet import (bet, # noqa ...@@ -81,6 +81,7 @@ from .bet import (bet, # noqa
robustfov) robustfov)
from .eddy import (eddy_cuda, # noqa from .eddy import (eddy_cuda, # noqa
topup) topup)
from .fast import (fast,) # noqa
from .flirt import (flirt, # noqa from .flirt import (flirt, # noqa
invxfm, invxfm,
applyxfm, applyxfm,
......
...@@ -14,7 +14,7 @@ import fsl.utils.assertions as asrt ...@@ -14,7 +14,7 @@ import fsl.utils.assertions as asrt
from . import wrapperutils as wutils from . import wrapperutils as wutils
@wutils.fileOrImage('input', 'output') @wutils.fileOrImage('input', outprefix='output')
@wutils.fslwrapper @wutils.fslwrapper
def bet(input, output, **kwargs): def bet(input, output, **kwargs):
"""Wrapper for the ``bet`` command. """Wrapper for the ``bet`` command.
......
#!/usr/bin/env python
#
# fast.py - Wrapper for the FSL fast command.
#
# Author: Martin Craig <martin.craig@eng.ox.ac.uk>
# Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :func:`fast` function, a wrapper for the FSL
`FAST <https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FAST>`_ command.
"""
import six
import fsl.utils.assertions as asrt
from . import wrapperutils as wutils
@wutils.fileOrImage('imgs', 'A', 's', 'manualseg', outprefix='out')
@wutils.fileOrArray('a')
@wutils.fslwrapper
def fast(imgs, out='fast', **kwargs):
"""Wrapper for the ``fast`` command.
:arg imgs: Input image(s)
:arg out: Output basename
:arg n_classes: Number of tissue classes (corresponds to the ``--class``
command line option)
"""
if isinstance(imgs, six.string_types):
imgs = [imgs]
asrt.assertIsNifti(*imgs)
argmap = {
'n_classes' : 'class',
}
cmd = ['fast', '-v', '--out=%s' % out]
cmd += wutils.applyArgStyle('--=', argmap=argmap, **kwargs)
cmd += imgs
return cmd
...@@ -94,15 +94,29 @@ def concatxfm(inmat1, inmat2, outmat): ...@@ -94,15 +94,29 @@ def concatxfm(inmat1, inmat2, outmat):
return cmd return cmd
@wutils.fileOrImage('infile', 'out', 'reffile') @wutils.fileOrImage('infile', 'out', 'reffile', outprefix='out')
@wutils.fileOrArray('init') @wutils.fileOrArray('init', outprefix='out')
@wutils.fslwrapper @wutils.fslwrapper
def mcflirt(infile, **kwargs): def mcflirt(infile, **kwargs):
"""Wrapper for the ``mcflirt`` command.""" """Wrapper for the ``mcflirt`` command."""
asrt.assertIsNifti(infile) asrt.assertIsNifti(infile)
argmap = {
'twod' : '2d',
}
valmap = {
'2d' : wutils.SHOW_IF_TRUE,
'gdt' : wutils.SHOW_IF_TRUE,
'meanvol' : wutils.SHOW_IF_TRUE,
'stats' : wutils.SHOW_IF_TRUE,
'mats' : wutils.SHOW_IF_TRUE,
'plots' : wutils.SHOW_IF_TRUE,
'report' : wutils.SHOW_IF_TRUE,
}
cmd = ['mcflirt', '-in', infile] cmd = ['mcflirt', '-in', infile]
cmd += wutils.applyArgStyle('-', **kwargs) cmd += wutils.applyArgStyle('-', argmap=argmap, valmap=valmap, **kwargs)
return cmd return cmd
...@@ -85,24 +85,36 @@ and returned:: ...@@ -85,24 +85,36 @@ and returned::
""" """
import os.path as op import itertools as it
import os import os.path as op
import sys import os
import inspect import re
import tempfile import sys
import warnings import glob
import functools import shutil
import collections import random
import string
import fnmatch
import inspect
import logging
import tempfile
import warnings
import functools
import collections
import six import six
import nibabel as nib import nibabel as nib
import numpy as np import numpy as np
import fsl.utils.tempdir as tempdir
import fsl.utils.run as run import fsl.utils.run as run
import fsl.utils.path as fslpath
import fsl.utils.tempdir as tempdir
import fsl.data.image as fslimage import fsl.data.image as fslimage
log = logging.getLogger(__name__)
def _update_wrapper(wrapper, wrapped, *args, **kwargs): def _update_wrapper(wrapper, wrapped, *args, **kwargs):
"""Replacement for the built-in ``functools.update_wrapper``. This """Replacement for the built-in ``functools.update_wrapper``. This
implementation ensures that the wrapper function has an attribute implementation ensures that the wrapper function has an attribute
...@@ -475,7 +487,8 @@ class _FileOrThing(object): ...@@ -475,7 +487,8 @@ class _FileOrThing(object):
``_FileOrThing`` decorators can be used with any other decorators ``_FileOrThing`` decorators can be used with any other decorators
**as long as** they do not manipulate the return value. **as long as** they do not manipulate the return value, and as long as
the ``_FileOrThing`` decorators are adjacent to each other.
""" """
...@@ -497,25 +510,42 @@ class _FileOrThing(object): ...@@ -497,25 +510,42 @@ class _FileOrThing(object):
return self.__output return self.__output
def __init__(self, func, prepIn, prepOut, load, *things): def __init__(self,
func,
prepIn,
prepOut,
load,
removeExt,
*args,
**kwargs):
"""Initialise a ``_FileOrThing`` decorator. """Initialise a ``_FileOrThing`` decorator.
:arg func: The function to be decorated. :arg func: The function to be decorated.
:arg prepIn: Function which returns a file name to be used in
place of an input argument.
:arg prepOut: Function which generates a file name to use for
arguments that were set to :data:`LOAD`.
:arg prepIn: Function which returns a file name to be used in :arg load: Function which is called to load items for arguments
place of an input argument. that were set to :data:`LOAD`. Must accept a file path
as its sole argument.
:arg prepOut: Function which generates a file name to use for :arg removeExt: Function which can remove a file extension from a file
arguments that were set to :data:`LOAD`. path.
:arg load: Function which is called to load items for arguments :arg outprefix: Must be passed as a keyword argument. The name of a
that were set to :data:`LOAD`. Must accept a file path positional or keyword argument to the function, which
as its sole argument. specifies an output file name prefix. All other
arguments with names that begin with this prefix may
be interpreted as things to ``LOAD``.
All other positional arguments are interpreted as the names of the
arguments to the function which will be handled by this
``_FileOrThing`` decorator. If not provided, *all* arguments passed to
the function will be handled.
:arg things: Names of all arguments which will be handled by
this ``_FileOrThing`` decorator. If not provided,
*all* arguments passed to the function will be
handled.
The ``prepIn`` and ``prepOut`` functions must accept the following The ``prepIn`` and ``prepOut`` functions must accept the following
positional arguments: positional arguments:
...@@ -527,11 +557,13 @@ class _FileOrThing(object): ...@@ -527,11 +557,13 @@ class _FileOrThing(object):
- The argument value that was passed in - The argument value that was passed in
""" """
self.__func = func self.__func = func
self.__prepIn = prepIn self.__prepIn = prepIn
self.__prepOut = prepOut self.__prepOut = prepOut
self.__load = load self.__load = load
self.__things = things self.__removeExt = removeExt
self.__things = args
self.__outprefix = kwargs.get('outprefix', None)
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
...@@ -545,45 +577,70 @@ class _FileOrThing(object): ...@@ -545,45 +577,70 @@ class _FileOrThing(object):
func = self.__func func = self.__func
argnames = namedPositionals(func, args) argnames = namedPositionals(func, args)
# If this _FileOrThing is being called
# by another _FileOrThing don't create
# another working directory. We do this
# sneakily, by setting an attribute on
# the wrapped function which stores the
# current working directory.
wrapped = _unwrap(func)
fot_workdir = getattr(wrapped, '_fot_workdir', None)
parent = fot_workdir is None
# Create a tempdir to store any temporary # Create a tempdir to store any temporary
# input/output things, but don't change # input/output things, but don't change
# into it, as file paths passed to the # into it, as file paths passed to the
# function may be relative. # function may be relative.
with tempdir.tempdir(changeto=False) as td: with tempdir.tempdir(changeto=False, override=fot_workdir) as td:
log.debug('Redirecting LOADed outputs to %s', td)
# Replace any things with file names. # Replace any things with file names.
# Also get a list of LOAD outputs # Also get a list of LOAD outputs
args, kwargs, outfiles = self.__prepareArgs( args = self.__prepareArgs(parent, td, argnames, args, kwargs)
td, argnames, args, kwargs) args, kwargs, outprefix, outfiles, prefixes = args
# The prefix/patterns may be
# overridden by a parent FoT
outprefix = getattr(wrapped, '_fot_outprefix', outprefix)
prefixes = getattr(wrapped, '_fot_prefixes', prefixes)
# if there are any other FileOrThings
# in the decorator chain, get them to
# use our working directory, and
# prefixes, instead of creating their
# own.
if parent:
setattr(wrapped, '_fot_workdir', td)
setattr(wrapped, '_fot_outprefix', outprefix)
setattr(wrapped, '_fot_prefixes', prefixes)
# Call the function # Call the function
result = func(*args, **kwargs) try:
result = func(*args, **kwargs)
# make a _Reults object to store
# the output. If we are decorating
# another _FileOrThing, the
# results will get merged together
# into a single _Results dict.
if not isinstance(result, _FileOrThing._Results):
result = _FileOrThing._Results(result)
# Load the LOADed outputs
for oname, ofile in outfiles.items():
if not op.exists(ofile): oval = None
else: oval = self.__load(ofile)
result[oname] = oval finally:
# if we're the top-level FileOrThing
# decorator, remove the attributes we
# added above.
if parent:
delattr(wrapped, '_fot_workdir')
delattr(wrapped, '_fot_outprefix')
delattr(wrapped, '_fot_prefixes')
return result return self.__generateResult(
td, result, outprefix, outfiles, prefixes)
def __prepareArgs(self, workdir, argnames, args, kwargs): def __prepareArgs(self, parent, workdir, argnames, args, kwargs):
"""Prepares all input and output arguments to be passed to the """Prepares all input and output arguments to be passed to the
decorated function. Any arguments with a value of :data:`LOAD` are decorated function. Any arguments with a value of :data:`LOAD` are
passed to the ``prepOut`` function specified at :meth:`__init__`. passed to the ``prepOut`` function specified at :meth:`__init__`.
All other arguments are passed through the ``prepIn`` function. All other arguments are passed through the ``prepIn`` function.
:arg parent: ``True`` if this ``_FileOrThing`` is the first in a
chain of ``_FileOrThing`` decorators.
:arg workdir: Directory in which all temporary files should be stored. :arg workdir: Directory in which all temporary files should be stored.
:arg args: Positional arguments to be passed to the decorated :arg args: Positional arguments to be passed to the decorated
...@@ -597,46 +654,224 @@ class _FileOrThing(object): ...@@ -597,46 +654,224 @@ class _FileOrThing(object):
- An updated copy of ``kwargs``. - An updated copy of ``kwargs``.
- The output file prefix that was actually passed in
(it is subsequently modified so that prefixed outputs
are redirected to a temporary location). All prefixed
outputs that are not ``LOAD``ed should be moved into
this directory. ``None`` if there is no output
prefix.
- A dictionary of ``{ name : filename }`` mappings, - A dictionary of ``{ name : filename }`` mappings,
for all arguments with a value of ``LOAD``. for all arguments with a value of ``LOAD``.
- A dictionary ``{ filepat : replstr }`` paths, for
all output-prefix arguments with a value of ``LOAD``.
""" """
outfiles = dict() # These containers keep track
# of output files which are to
# be loaded into memory
outfiles = dict()
prefixedFiles = dict()
allargs = {k : v for k, v in zip(argnames, args)} allargs = {k : v for k, v in zip(argnames, args)}
allargs.update(kwargs) allargs.update(kwargs)
# Has an output prefix been specified?
prefix = allargs.get(self.__outprefix, None)
realPrefix = None
# Prefixed outputs are only
# managed by the parent
# _FileOrthing in a chain of
# FoT decorators.
if not parent:
prefix = None
# If so, replace it with a new output
# prefix which will redirect all output
# to the temp dir.
#
# Importantly, here we assume that the
# underlying function (and hence the
# underlying command-line tool) will
# accept an output prefix which contains
# a directory path.
if prefix is not None:
# If prefix is set to LOAD,
# all generated output files
# should be loaded - we use a
# randomly generated prefix,
# and add it to prefixedFiles,
# so that every file which
# starts with it will be
# loaded.
if prefix is LOAD:
prefix = random.sample(string.ascii_letters, 10)
prefix = ''.join(prefix)
prefixedFiles[prefix] = self.__outprefix
realPrefix = prefix
fakePrefix = op.join(workdir, prefix)
allargs[self.__outprefix] = fakePrefix
log.debug('Replacing output prefix: %s -> %s',
realPrefix, fakePrefix)
# If the prefix specifies a
# directory, make sure it
# exists (remember that we're
# in a temporary directory)
pdir = op.dirname(fakePrefix)
if pdir != '' and not op.exists(pdir):
os.makedirs(pdir)
if len(self.__things) > 0: things = self.__things if len(self.__things) > 0: things = self.__things
else: things = allargs.keys() else: things = allargs.keys()
for name in things: for name, val in list(allargs.items()):
# don't process the
# outprefix argument
if name == self.__outprefix:
continue
val = allargs.get(name, None) # is this argument referring
# to a prefixed output?
isprefixed = (prefix is not None and
name.startswith(prefix))
if val is None: if not (isprefixed or name in things):
continue continue
if val is LOAD: # Prefixed output files may only
# be given a value of LOAD
if isprefixed and val is not LOAD:
raise ValueError('Cannot specify name of prefixed file - the '
'name is defined by the output prefix: '
'{}'.format(name))
outfile = self.__prepOut(workdir, name, val) if val is LOAD:
if outfile is not None: # this argument refers to an output
allargs[ name] = outfile # that is generated from the output
# prefix argument, and doesn't map
# directly to an argument of the
# function. So we don't pass it
# through.
if isprefixed:
prefixedFiles[name] = name
allargs.pop(name)
# regular output-file argument
else:
outfile = self.__prepOut(workdir, name, val)
outfiles[name] = outfile outfiles[name] = outfile
else: allargs[ name] = outfile
infile = self.__prepIn(workdir, name, val) # Assumed to be an input file
else:
# sequences may be
# accepted for inputs
if isinstance(val, (list, tuple)):
infile = list(val)
for i, v in enumerate(val):
v = self.__prepIn(workdir, name, v)
if v is not None:
infile[i] = v
else:
infile = self.__prepIn(workdir, name, val)
if infile is not None: if infile is not None:
allargs[name] = infile allargs[name] = infile
if realPrefix is not None and len(prefixedFiles) == 0:
allargs[self.__outprefix] = realPrefix
args = [allargs.pop(k) for k in argnames] args = [allargs.pop(k) for k in argnames]
kwargs = allargs kwargs = allargs
return args, kwargs, outfiles return args, kwargs, realPrefix, outfiles, prefixedFiles
def __generateResult(
self, workdir, result, outprefix, outfiles, prefixes):
"""Loads function outputs and returns a :class:`_Results` object.
Called by :meth:`__call__` after the decorated function has been
called. Figures out what files should be loaded, and loads them into
a ``_Results`` object.
:arg workdir: Directory which contains the function outputs.
:arg result: Function return value.
:arg outprefix: Original output prefix that was passed into the
function (or ``None`` if one wasn't passed)
:arg outfiles: Dictionary containing output files to be loaded (see
:meth:`__prepareArgs`).
:arg prefixes: Dictionary containing output-prefix patterns to be
loaded (see :meth:`__prepareArgs`).
:returns: A ``_Results`` object containing all loaded outputs.
"""
# make a _Results object to store
# the output. If we are decorating
# another _FileOrThing, the
# results will get merged together
# into a single _Results dict.
if not isinstance(result, _FileOrThing._Results):
result = _FileOrThing._Results(result)
# Load the LOADed outputs
for oname, ofile in outfiles.items():
log.debug('Loading output %s: %s', oname, ofile)
if op.exists(ofile): oval = self.__load(ofile)
else: oval = None
result[oname] = oval
def fileOrImage(*imgargs): # No output prefix - we're done
if outprefix is None or len(prefixes) == 0:
return result
# Load or move output-prefixed files.
# Find all files with a name that
# matches the prefix that was passed
# in (recursing into matching sub-
# directories too).
allPrefixed = glob.glob(op.join(workdir, '{}*'.format(outprefix)))
allPrefixed = [fslpath.allFiles(f) if op.isdir(f) else [f]
for f in allPrefixed]
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
log.debug('Loading prefixed output %s [%s]: %s',
prefPat, prefName, prefixed)
# if the load function returns
# None, this file is probably
# not of the correct type.
fval = self.__load(fullpath)
if fval is not None:
prefixed = self.__removeExt(prefixed)
prefixed = re.sub('^' + prefPat, prefName, prefixed)
result[prefixed] = fval
break
return result
def fileOrImage(*args, **kwargs):
"""Decorator which can be used to ensure that any NIfTI images are saved """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`` to file, and output images can be loaded and returned as ``nibabel``
image objects or :class:`.Image` objects. image objects or :class:`.Image` objects.
...@@ -674,6 +909,10 @@ def fileOrImage(*imgargs): ...@@ -674,6 +909,10 @@ def fileOrImage(*imgargs):
return op.join(workdir, '{}.nii.gz'.format(name)) return op.join(workdir, '{}.nii.gz'.format(name))
def load(path): def load(path):
if not fslimage.looksLikeImage(path):
return None
# create an independent in-memory # create an independent in-memory
# copy of the image file # copy of the image file
img = nib.load(path) img = nib.load(path)
...@@ -693,7 +932,13 @@ def fileOrImage(*imgargs): ...@@ -693,7 +932,13 @@ def fileOrImage(*imgargs):
raise RuntimeError('Cannot handle type: {}'.format(intypes)) raise RuntimeError('Cannot handle type: {}'.format(intypes))
def decorator(func): def decorator(func):
fot = _FileOrThing(func, prepIn, prepOut, load, *imgargs) fot = _FileOrThing(func,
prepIn,
prepOut,
load,
fslimage.removeExt,
*args,
**kwargs)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
result = fot(*args, **kwargs) result = fot(*args, **kwargs)
...@@ -705,7 +950,7 @@ def fileOrImage(*imgargs): ...@@ -705,7 +950,7 @@ def fileOrImage(*imgargs):
return decorator return decorator
def fileOrArray(*arrargs): def fileOrArray(*args, **kwargs):
"""Decorator which can be used to ensure that any Numpy arrays are saved """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. to text files, and output files can be loaded and returned as Numpy arrays.
""" """
...@@ -724,10 +969,18 @@ def fileOrArray(*arrargs): ...@@ -724,10 +969,18 @@ def fileOrArray(*arrargs):
def prepOut(workdir, name, val): def prepOut(workdir, name, val):
return op.join(workdir, '{}.txt'.format(name)) return op.join(workdir, '{}.txt'.format(name))
load = np.loadtxt def load(path):
try: return np.loadtxt(path)
except Exception: return None
def decorator(func): def decorator(func):
fot = _FileOrThing(func, prepIn, prepOut, load, *arrargs) fot = _FileOrThing(func,
prepIn,
prepOut,
load,
fslpath.removeExt,
*args,
**kwargs)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
return fot(*args, **kwargs) return fot(*args, **kwargs)
......
...@@ -4,4 +4,3 @@ mock==2.* ...@@ -4,4 +4,3 @@ mock==2.*
coverage==4.* coverage==4.*
pytest==3.* pytest==3.*
pytest-cov==2.* pytest-cov==2.*
pytest-runner>=2.*,<=4.*
...@@ -11,8 +11,9 @@ import os ...@@ -11,8 +11,9 @@ import os
import sys import sys
import glob import glob
import shutil import shutil
import tempfile import fnmatch
import logging import logging
import tempfile
import contextlib import contextlib
import itertools as it import itertools as it
import os.path as op import os.path as op
...@@ -187,16 +188,26 @@ def make_dummy_image_file(path): ...@@ -187,16 +188,26 @@ def make_dummy_image_file(path):
make_dummy_file(path) make_dummy_file(path)
def cleardir(dir): def cleardir(dir, pat=None):
"""Deletes everything in the given directory, but not the directory """Deletes everything in the given directory, but not the directory
itself. itself.
""" """
for f in os.listdir(dir): for f in os.listdir(dir):
if pat is not None and not fnmatch.fnmatch(f, pat):
continue
f = op.join(dir, f) f = op.join(dir, f)
if op.isfile(f): os.remove(f) if op.isfile(f): os.remove(f)
elif op.isdir(f): shutil.rmtree(f) elif op.isdir(f): shutil.rmtree(f)
def checkdir(dir, *expfiles):
for f in expfiles:
assert op.exists(op.join(dir, f))
def random_voxels(shape, nvoxels=1): def random_voxels(shape, nvoxels=1):
randVoxels = np.vstack( randVoxels = np.vstack(
[np.random.randint(0, s, nvoxels) for s in shape[:3]]).T [np.random.randint(0, s, nvoxels) for s in shape[:3]]).T
......
...@@ -110,6 +110,23 @@ def test_shallowest(): ...@@ -110,6 +110,23 @@ def test_shallowest():
assert fslpath.shallowest(path, suffixes) == output assert fslpath.shallowest(path, suffixes) == output
def test_allFiles():
create = [
'a/1',
'a/2',
'a/b/1',
'a/b/2',
'a/b/c/1',
'a/b/d/1',
]
with testdir(create) as td:
assert (sorted(fslpath.allFiles('.')) ==
sorted([op.join('.', c) for c in create]))
assert (sorted(fslpath.allFiles(td)) ==
sorted([op.join(td, c) for c in create]))
def test_hasExt(): def test_hasExt():
tests = [ tests = [
......
...@@ -51,3 +51,15 @@ def test_tempdir_changeto(): ...@@ -51,3 +51,15 @@ def test_tempdir_changeto():
assert op.realpath(os.getcwd()) == cwd assert op.realpath(os.getcwd()) == cwd
assert op.realpath(os.getcwd()) == cwd assert op.realpath(os.getcwd()) == cwd
def test_tempdir_override():
with tempdir.tempdir() as parent:
# tempdir should not create/change to
# a new temp directory, but should
# stay in the override directory
with tempdir.tempdir(override=parent):
assert op.realpath(os.getcwd()) == op.realpath(parent)
# override should not be deleted
assert op.exists(parent)
...@@ -15,14 +15,23 @@ import fsl.utils.run as run ...@@ -15,14 +15,23 @@ import fsl.utils.run as run
from . import mockFSLDIR from . import mockFSLDIR
def checkResult(cmd, base, args): def checkResult(cmd, base, args, stripdir=None):
"""We can't control the order in which command line args are generated, """We can't control the order in which command line args are generated,
so we need to test all possible orderings. so we need to test all possible orderings.
:arg cmd: Generated command :arg cmd: Generated command
:arg base: Beginning of expected command :arg base: Beginning of expected command
:arg args: Sequence of expected arguments :arg args: Sequence of expected arguments
:arg stripdir: Sequence of indices indicating arguments
for whihc any leading directory should be ignored.
""" """
if stripdir is not None:
cmd = list(cmd.split())
for si in stripdir:
cmd[si] = op.basename(cmd[si])
cmd = ' '.join(cmd)
permutations = it.permutations(args, len(args)) permutations = it.permutations(args, len(args))
possible = [' '.join([base] + list(p)) for p in permutations] possible = [' '.join([base] + list(p)) for p in permutations]
...@@ -34,7 +43,7 @@ def test_bet(): ...@@ -34,7 +43,7 @@ def test_bet():
bet = op.join(fsldir, 'bin', 'bet') bet = op.join(fsldir, 'bin', 'bet')
result = fw.bet('input', 'output', mask=True, c=(10, 20, 30)) result = fw.bet('input', 'output', mask=True, c=(10, 20, 30))
expected = (bet + ' input output', ('-m', '-c 10 20 30')) expected = (bet + ' input output', ('-m', '-c 10 20 30'))
assert checkResult(result.output[0], *expected) assert checkResult(result.output[0], *expected, stripdir=[2])
def test_robustfov(): def test_robustfov():
...@@ -263,3 +272,18 @@ def test_fslmaths(): ...@@ -263,3 +272,18 @@ def test_fslmaths():
assert result.output[0] == expected assert result.output[0] == expected
# TODO test LOAD output # TODO test LOAD output
def test_fast():
with asrt.disabled(), run.dryrun(), mockFSLDIR() as fsldir:
cmd = op.join(fsldir, 'bin', 'fast')
result = fw.fast('input', 'myseg', n_classes=3)
expected = [cmd, '-v', '--out=myseg', '--class=3', 'input']
assert result.output[0] == ' '.join(expected)
result = fw.fast(('in1', 'in2', 'in3'), 'myseg', n_classes=3)
expected = [cmd, '-v', '--out=myseg', '--class=3', 'in1', 'in2', 'in3']
assert result.output[0] == ' '.join(expected)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment