diff --git a/fsl/data/featresults.py b/fsl/data/featresults.py
index c2fddd25de2f70fe01e8b0d7078429bf11f2452f..4772f05b09c228f669a06c3d6dc8e62e6de2dcb9 100644
--- a/fsl/data/featresults.py
+++ b/fsl/data/featresults.py
@@ -43,7 +43,6 @@ The following functions return the names of various files of interest:
 
 
 import                        logging
-import                        glob
 import os.path             as op
 import numpy               as np
 
@@ -104,11 +103,12 @@ def hasStats(featdir):
     for the given FEAT analysis, ``False`` otherwise.
     """
 
-    statdir   = op.join(featdir, 'stats')
-    statfiles = glob.glob(op.join(statdir, '*'))
-    
-    return op.exists(statdir) and len(statfiles) > 0
-
+    try:
+        getZStatFile(featdir, 0)
+        return True
+    except:
+        return False
+        
 
 def hasMelodicDir(featdir):
     """Returns ``True`` if the data for the given FEAT directory has had
@@ -465,71 +465,83 @@ 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.
+
     :arg featdir: A FEAT directory.
     """
-    
-    # Assuming here that there is only
-    # one file called filtered_func_data.*
-    return glob.glob((op.join(featdir, 'filtered_func_data.*')))[0]
+    datafile = op.join(featdir, 'filtered_func_data')
+    return fslimage.addExt(datafile, mustExist=True)
 
 
 def getMelodicFile(featdir):
-    """Returns the name of the file in the FEAT results which contains the
-    melodic components. This file can be loaded as a :class:`.MelodicImage`.
+    """Returns the name of the file in the FEAT results which contains the melodic
+    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.
     """
-    return op.join(featdir, 'filtered_func_data.ica', 'melodic_IC.nii.gz')
+    melfile = op.join(featdir, 'filtered_func_data.ica', 'melodic_IC')
+    return fslimage.addExt(melfile, mustExist=True)
 
 
 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.
+
     :arg featdir: A FEAT directory.
     """
-    
-    # Assuming here that there is only
-    # one file called stats/res4d.*
-    return glob.glob((op.join(featdir, 'stats', 'res4d.*')))[0]
+    resfile = op.join(featdir, 'stats', 'res4d')
+    return fslimage.addExt(resfile, mustExist=True)
 
     
 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.
+
     :arg featdir: A FEAT directory.
     :arg ev:      The EV number (0-indexed).
     """
-    pefile = op.join(featdir, 'stats', 'pe{}.*'.format(ev + 1))
-    return glob.glob(pefile)[0]
+    pefile = op.join(featdir, 'stats', 'pe{}'.format(ev + 1))
+    return fslimage.addExt(pefile, mustExist=True)
 
 
 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.
+
     :arg featdir:  A FEAT directory.
     :arg contrast: The contrast number (0-indexed). 
     """
-    copefile = op.join(featdir, 'stats', 'cope{}.*'.format(contrast + 1))
-    return glob.glob(copefile)[0]
+    copefile = op.join(featdir, 'stats', 'cope{}'.format(contrast + 1))
+    return fslimage.addExt(copefile, mustExist=True)
 
 
 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.
+
     :arg featdir:  A FEAT directory.
     :arg contrast: The contrast number (0-indexed). 
     """
-    zfile = op.join(featdir, 'stats', 'zstat{}.*'.format(contrast + 1))
-    return glob.glob(zfile)[0]
+    zfile = op.join(featdir, 'stats', 'zstat{}'.format(contrast + 1))
+    return fslimage.addExt(zfile, mustExist=True)
 
 
 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.
+
     :arg featdir:  A FEAT directory.
     :arg contrast: The contrast number (0-indexed). 
     """
-    mfile = op.join(featdir, 'cluster_mask_zstat{}.*'.format(contrast + 1))
-    return glob.glob(mfile)[0]
+    mfile = op.join(featdir, 'cluster_mask_zstat{}'.format(contrast + 1))
+    return fslimage.addExt(mfile, mustExist=True)
 
 
 def getEVNames(settings):
diff --git a/fsl/data/image.py b/fsl/data/image.py
index f122dcd7d7d7de1f107f52560a721e1084662734..d8dae6f625db57f8f3ccbb2950961f7740bff8bc 100644
--- a/fsl/data/image.py
+++ b/fsl/data/image.py
@@ -45,6 +45,8 @@ import props
 
 import fsl.utils.transform as transform
 import fsl.utils.status    as status
+import fsl.utils.path      as fslpath
+import fsl.data.strings    as fslstrings
 import fsl.data.constants  as constants
 
 
@@ -604,97 +606,26 @@ def looksLikeImage(filename, allowedExts=None):
     return any(map(lambda ext: filename.endswith(ext), allowedExts))
 
 
-def removeExt(filename, allowedExts=None):
+def removeExt(filename):
     """Removes the extension from the given file name. Returns the filename
     unmodified if it does not have a supported extension.
 
-    :arg filename:    The file name to strip.
-    
-    :arg allowedExts: A list of strings containing the allowed file
-                      extensions.    
-    """
-
-    if allowedExts is None: allowedExts = ALLOWED_EXTENSIONS
-
-    # figure out the extension of the given file
-    extMatches = map(lambda ext: filename.endswith(ext), allowedExts)
-
-    # the file does not have a supported extension
-    if not any(extMatches):
-        return filename
+    See :func:`~fsl.utils.path.removeExt`.
 
-    # figure out the length of the matched extension
-    extIdx = extMatches.index(True)
-    extLen = len(allowedExts[extIdx])
-
-    # and trim it from the file name
-    return filename[:-extLen]
+    :arg filename: The file name to strip.
+    """
+    return fslpath.removeExt(filename, ALLOWED_EXTENSIONS)
 
 
-def addExt(prefix, mustExist=True, allowedExts=None, defaultExt=None):
+def addExt(prefix, mustExist=True):
     """Adds a file extension to the given file ``prefix``.
 
-    If ``mustExist`` is False, and the file does not already have a 
-    supported extension, the default extension is appended and the new
-    file name returned. If the prefix already has a supported extension,
-    it is returned unchanged.
-
-    If ``mustExist`` is ``True`` (the default), the function checks to see 
-    if any files exist that have the given prefix, and a supported file 
-    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.
-
-    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 allowedExts: List of allowed file extensions.
-    :arg defaultExt:  Default file extension to use.
+    See :func:`~fsl.utils.path.addExt`.
     """
-
-    if allowedExts is None: allowedExts = ALLOWED_EXTENSIONS
-    if defaultExt  is None: defaultExt  = DEFAULT_EXTENSION
-
-    if not mustExist:
-
-        # the provided file name already
-        # ends with a supported extension 
-        if any(map(lambda ext: prefix.endswith(ext), allowedExts)):
-            return prefix
-
-        return prefix + defaultExt
-
-    # If the provided prefix already ends with a
-    # supported extension , check to see that it exists
-    if any(map(lambda ext: prefix.endswith(ext), allowedExts)):
-        extended = [prefix]
-        
-    # Otherwise, make a bunch of file names, one per
-    # supported extension, and test to see if exactly
-    # one of them exists.
-    else:
-        extended = map(lambda ext: prefix + ext, allowedExts)
-
-    exists = map(op.isfile, extended)
-
-    # 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))
-
-    # Ambiguity! More than one supported
-    # file with the specified prefix
-    if len(filter(bool, exists)) > 1:
-        raise ValueError('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 fslpath.addExt(prefix,
+                          ALLOWED_EXTENSIONS,
+                          mustExist,
+                          DEFAULT_EXTENSION)
 
 
 def loadImage(filename):
diff --git a/fsl/utils/path.py b/fsl/utils/path.py
index 4c22cd41034676fce4dcc02fa18de2d55c56c9a4..cacc25b0f9756e4055bad480efc21c0166c2ec04 100644
--- a/fsl/utils/path.py
+++ b/fsl/utils/path.py
@@ -12,7 +12,9 @@ paths.
    :nosignatures:
 
    deepest
-   shallowest 
+   shallowest
+   addExt
+   removeExt
 """
 
 
@@ -60,3 +62,92 @@ def shallowest(path, suffixes):
         return path
 
     return None 
+
+
+def addExt(prefix, allowedExts, mustExist=True, defaultExt=None):
+    """Adds a file extension to the given file ``prefix``.
+
+    If ``mustExist`` is False, and the file does not already have a 
+    supported extension, the default extension is appended and the new
+    file name returned. If the prefix already has a supported extension,
+    it is returned unchanged.
+
+    If ``mustExist`` is ``True`` (the default), the function checks to see 
+    if any files exist that have the given prefix, and a supported file 
+    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.
+
+    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 allowedExts: List of allowed file extensions.
+    :arg defaultExt:  Default file extension to use.
+    """
+
+    if not mustExist:
+
+        # the provided file name already
+        # ends with a supported extension 
+        if any(map(lambda ext: prefix.endswith(ext), allowedExts)):
+            return prefix
+
+        if defaultExt is not None: return prefix + defaultExt
+        else:                      return None
+
+    # If the provided prefix already ends with a
+    # supported extension , check to see that it exists
+    if any(map(lambda ext: prefix.endswith(ext), allowedExts)):
+        extended = [prefix]
+        
+    # Otherwise, make a bunch of file names, one per
+    # supported extension, and test to see if exactly
+    # one of them exists.
+    else:
+        extended = map(lambda ext: prefix + ext, allowedExts)
+
+    exists = map(op.isfile, extended)
+
+    # 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))
+
+    # Ambiguity! More than one supported
+    # file with the specified prefix
+    if len(filter(bool, exists)) > 1:
+        raise ValueError('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]
+
+
+def removeExt(filename, allowedExts):
+    """Removes the extension from the given file name. Returns the filename
+    unmodified if it does not have a supported extension.
+
+    :arg filename:    The file name to strip.
+    
+    :arg allowedExts: A list of strings containing the allowed file
+                      extensions.    
+    """
+
+    # figure out the extension of the given file
+    extMatches = map(lambda ext: filename.endswith(ext), allowedExts)
+
+    # the file does not have a supported extension
+    if not any(extMatches):
+        return filename
+
+    # figure out the length of the matched extension
+    extIdx = extMatches.index(True)
+    extLen = len(allowedExts[extIdx])
+
+    # and trim it from the file name
+    return filename[:-extLen]