Commit aeffabbd authored by Paul McCarthy's avatar Paul McCarthy
Browse files

addExt resolves ambiguities a bit better. And also raises custom error type.

parent abd05f90
......@@ -90,7 +90,7 @@ def isFEATDir(path):
try:
fslimage.addExt(op.join(path, 'filtered_func_data'), mustExist=True)
except ValueError:
except fslimage.PathError:
return False
if not op.exists(op.join(dirname, 'design.fsf')): return False
......@@ -451,7 +451,7 @@ def getDataFile(featdir):
"""Returns the name of the file in the FEAT directory which contains
the model input data (typically called ``filtered_func_data.nii.gz``).
Raises a :exc:`ValueError` if the file does not exist.
Raises a :exc:`.PathError` if the file does not exist.
:arg featdir: A FEAT directory.
"""
......@@ -464,7 +464,7 @@ def getMelodicFile(featdir):
components (if melodic ICA was performed as part of the FEAT
analysis). This file can be loaded as a :class:`.MelodicImage`.
Raises a :exc:`ValueError` if the file does not exist.
Raises a :exc:`.PathError` if the file does not exist.
"""
melfile = op.join(featdir, 'filtered_func_data.ica', 'melodic_IC')
return fslimage.addExt(melfile, mustExist=True)
......@@ -474,7 +474,7 @@ def getResidualFile(featdir):
"""Returns the name of the file in the FEAT results which contains
the model fit residuals (typically called ``res4d.nii.gz``).
Raises a :exc:`ValueError` if the file does not exist.
Raises a :exc:`.PathError` if the file does not exist.
:arg featdir: A FEAT directory.
"""
......@@ -485,7 +485,7 @@ def getResidualFile(featdir):
def getPEFile(featdir, ev):
"""Returns the path of the PE file for the specified EV.
Raises a :exc:`ValueError` if the file does not exist.
Raises a :exc:`.PathError` if the file does not exist.
:arg featdir: A FEAT directory.
:arg ev: The EV number (0-indexed).
......@@ -497,7 +497,7 @@ def getPEFile(featdir, ev):
def getCOPEFile(featdir, contrast):
"""Returns the path of the COPE file for the specified contrast.
Raises a :exc:`ValueError` if the file does not exist.
Raises a :exc:`.PathError` if the file does not exist.
:arg featdir: A FEAT directory.
:arg contrast: The contrast number (0-indexed).
......@@ -509,7 +509,7 @@ def getCOPEFile(featdir, contrast):
def getZStatFile(featdir, contrast):
"""Returns the path of the Z-statistic file for the specified contrast.
Raises a :exc:`ValueError` if the file does not exist.
Raises a :exc:`.PathError` if the file does not exist.
:arg featdir: A FEAT directory.
:arg contrast: The contrast number (0-indexed).
......@@ -521,7 +521,7 @@ def getZStatFile(featdir, contrast):
def getClusterMaskFile(featdir, contrast):
"""Returns the path of the cluster mask file for the specified contrast.
Raises a :exc:`ValueError` if the file does not exist.
Raises a :exc:`.PathError` if the file does not exist.
:arg featdir: A FEAT directory.
:arg contrast: The contrast number (0-indexed).
......
......@@ -769,10 +769,22 @@ EXTENSION_DESCRIPTIONS = ['Compressed NIFTI images',
"""Descriptions for each of the extensions in :data:`ALLOWED_EXTENSIONS`. """
REPLACEMENTS = {'.hdr' : ['.img', '.img.gz']}
"""Suffix replacements used by :func:`addExt` to resolve file path
ambiguities - see :func:`fsl.utils.path.addExt`.
"""
DEFAULT_EXTENSION = '.nii.gz'
"""The default file extension (TODO read this from ``$FSLOUTPUTTYPE``)."""
PathError = fslpath.PathError
"""Error raised by :mod:`fsl.utils.path` functions when an error occurs.
Made available in this module for convenience.
"""
def looksLikeImage(filename, allowedExts=None):
"""Returns ``True`` if the given file looks like an image, ``False``
otherwise.
......@@ -810,7 +822,8 @@ def addExt(prefix, mustExist=True):
return fslpath.addExt(prefix,
ALLOWED_EXTENSIONS,
mustExist,
DEFAULT_EXTENSION)
DEFAULT_EXTENSION,
replace=REPLACEMENTS)
def loadIndexedImageFile(filename):
......
......@@ -77,7 +77,7 @@ def isMelodicDir(path):
# Must contain an image file called melodic_IC
try:
fslimage.addExt(op.join(dirname, 'melodic_IC'), mustExist=True)
except ValueError:
except fslimage.PathError:
return False
# Must contain files called
......@@ -125,7 +125,7 @@ def getDataFile(meldir):
dataFile = op.join(topDir, 'filtered_func_data')
try: return fslimage.addExt(dataFile, mustExist=True)
except ValueError: return None
except fslimage.PathErrpr: return None
def getMeanFile(meldir):
......
......@@ -15,12 +15,18 @@ paths.
shallowest
addExt
removeExt
getExt
"""
import os.path as op
class PathError(Exception):
"""``Exception`` class raised by :func:`addExt` and :func:`getExt`. """
pass
def deepest(path, suffixes):
"""Finds the deepest directory which ends with one of the given
sequence of suffixes, or returns ``None`` if no directories end
......@@ -64,7 +70,11 @@ def shallowest(path, suffixes):
return None
def addExt(prefix, allowedExts, mustExist=True, defaultExt=None):
def addExt(prefix,
allowedExts,
mustExist=True,
defaultExt=None,
replace=None):
"""Adds a file extension to the given file ``prefix``.
If ``mustExist`` is False, and the file does not already have a
......@@ -77,17 +87,47 @@ def addExt(prefix, allowedExts, mustExist=True, defaultExt=None):
extension. A :exc:`ValueError` is raised if:
- No files exist with the given prefix and a supported extension.
- More than one file exists with the given prefix, and a supported
extension.
- ``replace`` is ``None``, and more than one file exists with the
given prefix, and a supported extension.
Otherwise the full file name is returned.
:arg prefix: The file name refix to modify.
:arg mustExist: Whether the file must exist or not.
:arg prefix: The file name prefix to modify.
:arg allowedExts: List of allowed file extensions.
:arg mustExist: Whether the file must exist or not.
:arg defaultExt: Default file extension to use.
:arg replace: If multiple files exist with the same ``prefix`` and
supported extensions (e.g. ``file.hdr`` and
``file.img``), this dictionary can be used to resolve
ambiguities. It must have the structure::
{
suffix : [replacement, ...],
...
}
If files with ``suffix`` and one of the ``replacement``
suffixes exists, the ``suffix`` file will
be ignored, and replaced with the ``replacement`` file.
If multiple ``replacement`` files exist alongside the
``suffix`` file, a ``PathError`` is raised.
.. note:: The primary use-case of the ``replace`` parameter is to resolve
ambiguity with respect to NIFTI and ANALYSE75 image pairs. By
specifying ``replace={'.hdr' : ['.img'. '.img.gz'}``, the
``addExt`` function is able to figure out what you mean when you
wish to add an extension to ``file``, and ``file.hdr`` and
either ``file.img`` or ``file.img.gz`` (but not both) exist.
"""
if replace is None:
replace = {}
if not mustExist:
# the provided file name already
......@@ -101,31 +141,74 @@ def addExt(prefix, allowedExts, mustExist=True, defaultExt=None):
# If the provided prefix already ends with a
# supported extension , check to see that it exists
if any([prefix.endswith(ext) for ext in allowedExts]):
extended = [prefix]
allPaths = [prefix]
# Otherwise, make a bunch of file names, one per
# supported extension, and test to see if exactly
# one of them exists.
else:
extended = [prefix + ext for ext in allowedExts]
allPaths = [prefix + ext for ext in allowedExts]
exists = [op.isfile(e) for e in extended]
exists = [op.isfile(e) for e in allPaths]
nexists = sum(exists)
# Could not find any supported file
# with the specified prefix
if not any(exists):
raise ValueError(
'Could not find a supported file with prefix {}'.format(prefix))
if nexists == 0:
raise PathError('Could not find a supported file '
'with prefix {}'.format(prefix))
# Ambiguity! More than one supported
# file with the specified prefix
if sum(exists) > 1:
raise ValueError('More than one file with prefix {}'.format(prefix))
# file with the specified prefix.
elif nexists > 1:
# Remove non-existent paths from the
# extended list, get all their
# suffixes, and potential replacements
allPaths = [allPaths[i] for i in range(len(allPaths)) if exists[i]]
suffixes = [getExt(e, allowedExts) for e in allPaths]
replacements = [replace.get(s) for s in suffixes]
hasReplace = [r is not None for r in replacements]
for p, r in zip(allPaths, replacements):
print ' {} replacements: {}'.format(p, r)
# If any replacement has been specified
# for any of the existing suffixes,
# see if we have a unique match for
# exactly one existing suffix, the
# one to be ignored/replaced.
if sum(hasReplace) == 1:
# Make sure there is exactly one potential
# replacement for this suffix. If there's
# more than one (e.g. file.hdr plus both
# file.img and file.img.gz) we can't resolve
# the ambiguity. In this case the code will
# fall through to the raise statement below.
toReplace = allPaths[hasReplace.index(True)]
replacements = replacements[hasReplace.index(True)]
replacements = [prefix + ext for ext in replacements]
replExists = [r in allPaths for r in replacements]
if sum(replExists) == 1:
replacedBy = replacements[replExists.index(True)]
allPaths[allPaths.index(toReplace)] = replacedBy
allPaths = list(set(allPaths))
exists = [True] * len(allPaths)
# There's more than one path match -
# we can't resolve the ambiguity
if len(allPaths) > 1:
raise PathError('More than one file with '
'prefix {}'.format(prefix))
# Return the full file name of the
# supported file that was found
extIdx = exists.index(True)
return extended[extIdx]
return allPaths[extIdx]
def removeExt(filename, allowedExts):
......@@ -151,3 +234,32 @@ def removeExt(filename, allowedExts):
# and trim it from the file name
return filename[:-extLen]
def getExt(filename, allowedExts=None):
"""Returns the extension from the given file name.
If ``allowedExts`` is ``None``, this function is equivalent to using::
os.path.splitext(filename)[1]
If ``allowedExts`` is provided, but the file does not end with an allowed
extension, a :exc:`PathError` is raised.
:arg allowedExts: Allowed/recognised file extensions.
"""
# If allowedExts is not specified,
# we just use op.splitext
if allowedExts is None:
return op.splitext(filename)[1]
# Otherwise, try and find a suffix match
extMatches = [filename.endswith(ext) for ext in allowedExts]
if not any(extMatches):
raise PathError('{} does not end in a supported extension ({})'.format(
filename, ', '.join(allowedExts)))
extIdx = extMatches.index(True)
return allowedExts[extIdx]
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment