From 26b41155133e7852454c1089f30130685d7790db Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauldmccarthy@gmail.com> Date: Sat, 7 Jul 2018 12:54:01 +0100 Subject: [PATCH] ENH: _FileOrThing supports output prefix with a directory --- fsl/wrappers/wrapperutils.py | 177 +++++++++++++++++++++-------------- 1 file changed, 105 insertions(+), 72 deletions(-) diff --git a/fsl/wrappers/wrapperutils.py b/fsl/wrappers/wrapperutils.py index fa6b66086..e895eb137 100644 --- a/fsl/wrappers/wrapperutils.py +++ b/fsl/wrappers/wrapperutils.py @@ -85,20 +85,22 @@ and returned:: """ -import os.path as op -import os -import sys -import glob -import shutil -import random -import string -import fnmatch -import inspect -import logging -import tempfile -import warnings -import functools -import collections +import itertools as it +import os.path as op +import os +import re +import sys +import glob +import shutil +import random +import string +import fnmatch +import inspect +import logging +import tempfile +import warnings +import functools +import collections import six import nibabel as nib @@ -585,66 +587,13 @@ class _FileOrThing(object): # Replace any things with file names. # Also get a list of LOAD outputs args = self.__prepareArgs(td, argnames, args, kwargs) - args, kwargs, basePrefix, outfiles, prefixes = args + args, kwargs, outprefix, outfiles, prefixes = args # Call the function result = func(*args, **kwargs) - # 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 - - # Load or move output-prefixed files - if basePrefix is not None: - - prefixDir = op.abspath(op.dirname(basePrefix)) - basePrefix = op.basename(basePrefix) - allPrefixed = glob.glob(op.join(td, '{}*'.format(basePrefix))) - - for filename in allPrefixed: - - basename = op.basename(filename) - for prefPat, prefName in prefixes.items(): - if fnmatch.fnmatch(basename, '{}*'.format(prefPat)): - - log.debug('Loading prefixed output %s [%s]: %s', - prefPat, prefName, filename) - - # if the load function returns - # None, this file is probably - # not of the correct type. - fval = self.__load(filename) - if fval is not None: - basename = self.__removeExt(basename) - basename = basename.replace(prefPat, prefName) - result[basename] = fval - break - - # if file did not match any pattern, - # move it into the real prefix, where - # the function would have saved it - # if we had not modified the prefix - # (see __prepareArgs) - else: - log.debug('Moving prefixed output %s into %s', - filename, prefixDir) - shutil.move(filename, prefixDir) - - return result + return self.__generateResult( + td, result, outprefix, outfiles, prefixes) def __prepareArgs(self, workdir, argnames, args, kwargs): @@ -718,8 +667,14 @@ class _FileOrThing(object): prefixedFiles[prefix] = self.__outprefix realPrefix = prefix - prefix = op.basename(prefix) - allargs[self.__outprefix] = op.join(workdir, prefix) + fakePrefix = op.join(workdir, prefix) + allargs[self.__outprefix] = fakePrefix + + + pdir = op.dirname(fakePrefix) + + if pdir != '' and not op.exists(pdir): + os.makedirs(pdir) if len(self.__things) > 0: things = self.__things else: things = allargs.keys() @@ -771,12 +726,90 @@ class _FileOrThing(object): if infile is not None: allargs[name] = infile + if realPrefix is not None and len(prefixedFiles) == 0: + allargs[self.__outprefix] = realPrefix + args = [allargs.pop(k) for k in argnames] kwargs = allargs 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 + + # 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 to file, and output images can be loaded and returned as ``nibabel`` -- GitLab