From 7f7dd1ed0f7c623214ea87aed69dd589384e2b6a Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Sun, 29 Jun 2014 11:22:07 +0100
Subject: [PATCH] Documented fsl.data package.

---
 fsl/data/__init__.py  |   7 +
 fsl/data/fslimage.py  | 359 +++++++++++++++++++++++++-----------------
 fsl/data/imagefile.py |  59 +++++--
 3 files changed, 264 insertions(+), 161 deletions(-)

diff --git a/fsl/data/__init__.py b/fsl/data/__init__.py
index e69de29bb..27a9696ea 100644
--- a/fsl/data/__init__.py
+++ b/fsl/data/__init__.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+#
+# __init__.py - fsl.data package.
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+"""Data structures and models."""
diff --git a/fsl/data/fslimage.py b/fsl/data/fslimage.py
index 23d563938..2a284ecf7 100644
--- a/fsl/data/fslimage.py
+++ b/fsl/data/fslimage.py
@@ -5,6 +5,9 @@
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
+"""Classes for representing 3D/4D images, display properties of images, and
+collections of images.
+"""
 
 import os
 import sys
@@ -28,13 +31,13 @@ log = logging.getLogger(__name__)
 
 
 def _loadImageFile(filename):
-    """
-    Given the name of an image file, loads it using nibabel. If the file
-    is large, and is gzipped, it is decompressed to a temporary location,
-    so that it can be memory-mapped.  A tuple is returned, consisting of
-    the nibabel image object, and the name of the file that it was loaded
-    from (either the passed-in file name, or the name of the temporary
-    decompressed file).
+    """Given the name of an image file, loads it using nibabel.
+
+    If the file is large, and is gzipped, it is decompressed to a temporary
+    location, so that it can be memory-mapped.  A tuple is returned,
+    consisting of the nibabel image object, and the name of the file that it
+    was loaded from (either the passed-in file name, or the name of the
+    temporary decompressed file).
     """
 
     # If we have a GUI, we can display a dialog
@@ -48,71 +51,113 @@ def _loadImageFile(filename):
         pass
 
     realFilename = filename
+    mbytes = op.getsize(filename) / 1048576.0
 
-    if filename.endswith('.nii.gz'):
-        
-        mbytes = op.getsize(filename) / 1048576.0
+    # The mbytes limit is arbitrary
+    if filename.endswith('.nii.gz') and mbytes > 512:
 
-        # This limit is arbitrary
-        if mbytes > 512:
+        unzipped, filename = tempfile.mkstemp(suffix='.nii')
 
-            unzipped, filename = tempfile.mkstemp(suffix='.nii')
+        unzipped = os.fdopen(unzipped)
 
-            unzipped = os.fdopen(unzipped)
+        msg = '{} is a large file ({} MB) - decompressing ' \
+              'to {}, to allow memory mapping...'.format(realFilename,
+                                                         mbytes,
+                                                         filename)
 
-            msg = '{} is a large file ({} MB) - decompressing ' \
-                  'to {}, to allow memory mapping...'.format(realFilename,
-                                                             mbytes,
-                                                             filename)
-
-            if not haveGui:
-                log.info(msg)
-            else:
-                busyDlg = wx.BusyInfo(msg, wx.GetTopLevelWindows()[0])
+        if not haveGui:
+            log.info(msg)
+        else:
+            busyDlg = wx.BusyInfo(msg, wx.GetTopLevelWindows()[0])
 
-            gzip = ['gzip', '-d', '-c', realFilename]
-            log.debug('Running {} > {}'.format(' '.join(gzip), filename))
+        gzip = ['gzip', '-d', '-c', realFilename]
+        log.debug('Running {} > {}'.format(' '.join(gzip), filename))
 
-            # If the gzip call fails, revert to loading from the gzipped file
-            try:
-                sp.call(gzip, stdout=unzipped)
-                unzipped.close()
+        # If the gzip call fails, revert to loading from the gzipped file
+        try:
+            sp.call(gzip, stdout=unzipped)
+            unzipped.close()
 
-            except OSError as e:
-                log.warn('gzip call failed ({}) - cannot memory '
-                         'map file: {}'.format(e, realFilename),
-                         exc_info=True)
-                unzipped.close()
-                os.remove(filename)
-                filename = realFilename
+        except OSError as e:
+            log.warn('gzip call failed ({}) - cannot memory '
+                     'map file: {}'.format(e, realFilename),
+                     exc_info=True)
+            unzipped.close()
+            os.remove(filename)
+            filename = realFilename
 
-            if haveGui:
-                busyDlg.Destroy()
+        if haveGui:
+            busyDlg.Destroy()
 
     return nib.load(filename), filename
 
 
 class Image(props.HasProperties):
-    """
-    Class which represents a 3D/4D image. Internally, the image is
-    loaded/stored using nibabel.
+    """Class which represents a 3D/4D image. Internally, the image is
+    loaded/stored using :mod:`nibabel`.
+
+    Arbitrary data may be associated with an :class:`Image` object, via the
+    :meth:`getAttribute` and :meth:`setAttribute` methods (which are just
+    front end wrappers around an internal ``dict`` object).
+
+    The following attributes are present on an :class:`Image` object:
+
+    :ivar nibImage:      The :mod:`nibabel` image object.
+    
+    :ivar data:          A reference to the image data, stored as a
+                         :mod`numpy` array.
+
+    :ivar display:       A :class:`ImageDisplay` object, defining how this
+                         image should be displayed.
+    
+    :ivar shape:         A list/tuple containing the number of voxels
+                         along each image dimension.
+    
+    :ivar pixdim:        A list/tuple containing the size of one voxel
+                         alon each image dimension.
+    
+    :ivar voxToWorldMat: A 4*4 array specifying the affine transformation
+                         for transforming voxel coordinates into real world
+                         coordinates.
+    
+    :ivar worldToVoxMat: A 4*4 array specifying the affine transformation
+                         for transforming real world coordinates into voxel
+                         coordinates.
+    
+    :ivar imageFile:     The name of the file that the image was loaded from.
+    
+    :ivar tempFile:      The name of the temporary file which was created (in
+                         the event that the image was large and was gzipped -
+                         see :func:`_loadImageFile`).
     """
 
-    # How the image should be transformd into real world space.
     transform = props.Choice(
         collections.OrderedDict([
             ('affine', 'Use qform/sform transformation matrix'),
             ('pixdim', 'Use pixdims only'),
             ('id',     'Do not use qform/sform or pixdims')]),
         default='affine')
-
-    name      = props.String()
-    imageFile = props.FilePath()
-    tempFile  = props.FilePath()
+    """This property defines how the image should be transformd into real world
+    space.
+    
+      - ``affine``: Use the affine transformation matrix stored in the image
+                    (the ``qform``/``sform`` fields in NIFTI1 headers).
+                    
+      - ``pixdim``: Scale voxel sizes by the ``pixdim`` fields in the image
+                    header.
+      - ``id``:     Perform no scaling or transformation - voxels will be
+                    displayed as :math:`1mm^3` isotropic.
+    """
+    
+    name = props.String()
+    """The name of this image."""
+    
         
     def __init__(self, image):
-        """
-        Initialise an Image object with the given image data or file name.
+        """Initialise an Image object with the given image data or file name.
+
+        :arg image: A string containing the name of an image file to load, or
+                    a :mod:`numpy` array, or a :mod:`nibabel` image object.
         """
 
         # The image parameter may be the name of an image file
@@ -172,24 +217,27 @@ class Image(props.HasProperties):
 
 
     def _transformChanged(self, *a):
-        """
+        """This method is called when the :attr:`transform` property value
+        changes. It updates the :attr:`voxToWorldMat`, :attr:`worldToVoxMat`,
+        and :attr:`pixdim` attributes to reflect the new transformation
+        type.
         """
 
         if self.transform == 'affine':
             voxToWorldMat = self.nibImage.get_affine()
-            pixdims       = self.nibImage.get_header().get_zooms()
+            pixdim        = self.nibImage.get_header().get_zooms()
             
         elif self.transform == 'pixdim':
-            pixdims       = self.nibImage.get_header().get_zooms()
-            voxToWorldMat = np.diag([pixdims[0], pixdims[1], pixdims[2], 1.0])
+            pixdim        = self.nibImage.get_header().get_zooms()
+            voxToWorldMat = np.diag([pixdim[0], pixdim[1], pixdim[2], 1.0])
             
         elif self.transform == 'id':
             voxToWorldMat = np.identity(4)
-            pixdims       = [1.0, 1.0, 1.0]
+            pixdim        = [1.0, 1.0, 1.0]
 
         self.voxToWorldMat = np.array(voxToWorldMat, dtype=np.float32)
         self.worldToVoxMat = linalg.inv(self.voxToWorldMat)
-        self.pixdims       = pixdims
+        self.pixdim        = pixdim
 
         self.voxToWorldMat = self.voxToWorldMat.transpose()
         self.worldToVoxMat = self.worldToVoxMat.transpose()
@@ -198,7 +246,7 @@ class Image(props.HasProperties):
         # location (0, 0) to map to voxel location (0, 0)
         if self.transform in ['pixdim', 'id']:
             for i in range(3):
-                self.voxToWorldMat[3, i] =  self.pixdims[i] * 0.5
+                self.voxToWorldMat[3, i] =  self.pixdim[i] * 0.5
                 self.worldToVoxMat[3, i] = -0.5
                 
         log.debug('Image {} transformation matrix changed: {}'.format(
@@ -207,9 +255,8 @@ class Image(props.HasProperties):
 
 
     def imageBounds(self, axis):
-        """
-        Return the bounds (min, max) of the image, in real world
-        coordinates, along the specified axis.
+        """Return the bounds (min, max) of the image, in real world
+        coordinates, along the specified 0-indexed axis.
         """
 
         x, y, z = self.shape[:3]
@@ -238,24 +285,23 @@ class Image(props.HasProperties):
 
 
     def worldToVox(self, p, axes=None):
-        """
-        Transforms the given set of points in voxel coordinates to
-        points in world coordinates, according to the affine
-        transformation specified in the image file. The returned array
-        is either a numpy.float64 array, or a single integer value,
-        depending on the input. There is no guarantee that the returned
-        array of voxel coordinates is within the bounds of the image
-        shape. Parameters:
+        """Transforms the given set of points in voxel coordinates to points
+        in world coordinates, according to the current :attr:`transform`.
+
+        The returned array is either a :class:`numpy.float64` array, or a
+        single ``int`` value, depending on the input. There is no guarantee
+        that the returned array of voxel coordinates is within the bounds of
+        the image shape. Parameters:
         
-          - p:    N*A array, where N is the number of points, and A
-                  is the number of axes to consider (default: 3)
+        :arg p:    N*A array, where N is the number of points, and A
+                   is the number of axes to consider (default: 3).
         
-          - axes: If None, it is assumed that the input p is a N*3
-                  array, with each point being specified by x,y,z
-                  coordinates. If a single value in the range (0-2),
-                  it is assumed that p is a 1D array. Or, if a
-                  sequence of 2 or 3 values, p must be an array of
-                  N*2 or N*3, respectively.
+        :arg axes: If ``None``, it is assumed that the input p is a N*3
+                   array, with each point being specified by x,y,z
+                   coordinates. If a single value in the range (0-2),
+                   it is assumed that p is a 1D array. Or, if a
+                   sequence of 2 or 3 values, p must be an array of
+                   N*2 or N*3, respectively.
         """
 
         voxp = self._transform(p, self.worldToVoxMat, axes)
@@ -273,13 +319,13 @@ class Image(props.HasProperties):
 
 
     def voxToWorld(self, p, axes=None):
-        """
-        Transforms the given set of points in world coordinates to
-        points in voxel coordinates, according to the affine
-        transformation specified in the image file.  The returned
-        array is either a numpy.float64 array, or a single float
-        value, depending on the input. See the worldToVox
-        docstring for more details. 
+        """Transforms the given set of points in world coordinates to
+        points in voxel coordinates, according to the current
+        :attr:`transform`.
+
+        The returned array is either a :class:`numpy.float64` array,
+        or a single ``float`` value, depending on the input. See the
+        :meth:`worldToVox` method for more details.
         """
 
         worldp = self._transform(p, self.voxToWorldMat, axes)
@@ -289,11 +335,11 @@ class Image(props.HasProperties):
 
         
     def _transform(self, p, a, axes):
-        """
-        Transforms the given set of points p according to the given
-        affine transformation a. The transformed points are returned
-        as a numpy.float64 array. See the worldToVox docstring for
-        more details. 
+        """Used by the :meth:`worldToVox` and :meth:`voxToWorld` methods.
+        
+        Transforms the given set of points ``p`` according to the given affine
+        transformation ``a``. The transformed points are returned as a
+        :class:``numpy.float64`` array.
         """
 
         p = self._fillPoints(p, axes)
@@ -313,9 +359,8 @@ class Image(props.HasProperties):
 
 
     def _fillPoints(self, p, axes):
-        """
-        Used by the _transform method. Turns the given array p into a N*3
-        array of x,y,z coordinates. The array p may be a 1D array, or an
+        """Used by the :meth:`_transform` method. Turns the given array p into
+        a N*3 array of x,y,z coordinates. The array p may be a 1D array, or an
         N*2 or N*3 array.
         """
 
@@ -347,16 +392,12 @@ class Image(props.HasProperties):
 
         
     def getAttribute(self, name):
-        """
-        Retrieve the attribute with the given name.
-        """
+        """Retrieve the attribute with the given name."""
         return self._attributes[name]
 
         
     def setAttribute(self, name, value):
-        """
-        Set an attribute with the given name and the given value.
-        """
+        """Set an attribute with the given name and the given value."""
         self._attributes[name] = value
         
         log.debug('Attribute set on {}: {} = {}'.format(
@@ -364,31 +405,48 @@ class Image(props.HasProperties):
 
 
 class ImageDisplay(props.HasProperties):
-    """
-    A class which describes how an image should be displayed. There should
-    be no need to manually instantiate ImageDisplay objects - one is created
-    for each Image object, and is accessed via the Image.display attribute.
-    If a single image needs to be displayed in different ways, then create
-    away, and manage your own ImageDisplay objects.
+    """A class which describes how an image should be displayed.
+
+    There should be no need to manually instantiate :class:`ImageDisplay`
+    objects - one is created for each :class:`Image` object, and is accessed
+    via the :attr:`Image.display` instance attribute.  If a single image needs
+    to be displayed in different ways, then create away, and manage your own
+    :class:`ImageDisplay` objects.
 
     This class doesn't have any functionality - it is up to things which
-    actually display an Image to adhere to the properties stored in the
-    associated ImageDisplay object.
+    actually display an :class:`Image` to adhere to the properties stored in
+    the associated :class:`ImageDisplay` object.
     """
 
-    enabled      = props.Boolean(default=True)
-    alpha        = props.Real(minval=0.0, maxval=1.0, default=1.0)
+    enabled = props.Boolean(default=True)
+    """Should this image be displayed at all?"""
+    
+    alpha = props.Real(minval=0.0, maxval=1.0, default=1.0)
+    """Transparency - 1.0 is fully opaque, and 0.0 is fully transparent."""
+    
     displayRange = props.Bounds(ndims=1, editLimits=True,
                                 labels=['Min.', 'Max.'])
+    """Image values which map to the minimum and maximum colour map colours."""
+
     samplingRate = props.Int(minval=1, maxval=16, default=1, clamped=True)
-    rangeClip    = props.Boolean(default=False)
-    cmap         = props.ColourMap(default=mplcm.Greys_r)
-    volume       = props.Int(minval=0, maxval=0, default=0, clamped=True)
+    """Only display every Nth voxel (a performance tweak)."""
 
+    rangeClip = props.Boolean(default=False)
+    """If ``True``, don't display voxel values which are beyond the
+    :attr:`displayRange`.
+    """
+    
+    cmap = props.ColourMap(default=mplcm.Greys_r)
+    """The colour map, a :class:`matplotlib.colors.Colourmap` instance."""
+    
+    volume = props.Int(minval=0, maxval=0, default=0, clamped=True)
+    """If a 4D image, the current volume to display."""
 
     def is4DImage(self):
+        """Returns ``True`` if this image is 4D, ``False`` otherwise.
+        """
         return len(self.image.shape) > 3 and self.image.shape[3] > 1
-    
+
     _view = props.VGroup(('enabled',
                           props.Widget('volume', enabledWhen=is4DImage),
                           'displayRange',
@@ -420,9 +478,9 @@ class ImageDisplay(props.HasProperties):
 
 
     def __init__(self, image):
-        """
-        Create an ImageDisplay for the specified image. The image
-        parameter should be an Image object (defined above).
+        """Create an :class:`ImageDisplay` for the specified image.
+
+        :arg image: A :class:`Image` object.
         """
 
         self.image = image
@@ -453,44 +511,58 @@ class ImageDisplay(props.HasProperties):
             
 
 class ImageList(props.HasProperties):
-    """
-    Class representing a collection of images to be displayed together.
-    Contains a List property containing Image objects, and some other 
-    properties on which listeners may register themselves to be notified
-    when the properties of the image collection changes (e.g. image
-    bounds).
+    """Class representing a collection of images to be displayed together.
+
+    Contains a :class:`props.properties_types.List` property containing
+    :class:`Image` objects, and some other properties on which listeners may
+    register themselves to be notified when the properties of the image
+    collection changes (e.g. image bounds).
+
+    An :class:`ImageList` object has a few wrapper methods around the
+    :attr:`images` property, allowing the :class:`ImageList` to be used
+    as if it were a list itself.
     """
 
     def _validateImage(self, atts, images):
+        """Returns ``True`` if all objects in the given ``images`` list are
+        :class:`Image` objects, ``False`` otherwise.
+        """
         return all(map(lambda img: isinstance(img, Image), images))
 
-    # The images property contains a list of Image objects
+
     images = props.List(validateFunc=_validateImage, allowInvalid=False)
+    """A list of :class:`Image` objects. to be displayed"""
 
-    # Index of the currently 'selected' image. This property
-    # is not used by the ImageList, but is provided so that
-    # other things can control and listen for changes to
-    # the currently selected image
+    
     selectedImage = props.Int(minval=0, clamped=True)
+    """Index of the currently 'selected' image. This property is not used by
+    the :class:`ImageList`, but is provided so that other things can control
+    and listen for changes to the currently selected image
+    """
 
-    # The bounds property contains the min/max values of
-    # a bounding box (in real world coordinates) which
-    # is big enough to contain all of the images in the
-    # 'images' list. This property shouid be read-only,
-    # but I don't have a way to enforce it (yet).
+    
     bounds = props.Bounds(ndims=3)
+    """This property contains the min/max values of
+    a bounding box (in real world coordinates) which
+    is big enough to contain all of the images in the
+    :attr:`images` list. This property shouid be
+    read-only, but I don't have a way to enforce it
+    (yet). 
+    """
 
-    # The location property contains the currently 'selected'
-    # 3D location in the image list space. This property
-    # is not used directly by the ImageList object, but it
-    # is here so that the location selection can be synchronised
-    # across multiple displays.
+    
     location = props.Point(ndims=3, labels=('X', 'Y', 'Z'))
+    """The location property contains the currently 'selected'
+    3D location in the image list space. This property
+    is not used directly by the ImageList object, but it
+    is here so that the location selection can be synchronised
+    across multiple displays.
+    """
 
+    
     def __init__(self, images=None):
-        """
-        Create an ImageList object from the given sequence of Image objects.
-        """
+        """Create an ImageList object from the given sequence of
+        :class:`Image` objects."""
         
         if images is None: images = []
 
@@ -514,10 +586,9 @@ class ImageList(props.HasProperties):
 
 
     def _imageListChanged(self, *a):
-        """
-        Called whenever an item is added or removed from the list. Registers
-        listeners with the properties of each image, and updates the image
-        bounds
+        """Called whenever an item is added or removed from the :attr:`images`
+        list. Registers listeners with the properties of each image, and
+        calls the :meth:`_updateImageBounds` method.
         """ 
         
         for img in self.images:
@@ -535,9 +606,9 @@ class ImageList(props.HasProperties):
 
     
     def _updateImageBounds(self, *a):
-        """
-        Called whenever an item is added or removed from the list, or an
-        image property changes. Updates the xyz bounds.
+        """Called whenever an item is added or removed from the
+        :attr:`images` list, or an image property changes. Updates
+        the :attr:`bounds` property.
         """
 
         if len(self.images) == 0:
@@ -574,7 +645,7 @@ class ImageList(props.HasProperties):
     def __iter__(    self):            return self.images.__iter__()
     def __contains__(self, item):      return self.images.__contains__(item)
     def __setitem__( self, key, val):  return self.images.__setitem__(key, val)
-    def __delitem(   self, key):       return self.images.__delitem__(key)
+    def __delitem__( self, key):       return self.images.__delitem__(key)
     def index(       self, item):      return self.images.index(item)
     def count(       self, item):      return self.images.count(item)
     def append(      self, item):      return self.images.append(item)
diff --git a/fsl/data/imagefile.py b/fsl/data/imagefile.py
index 6b7a51f2e..f11c78054 100644
--- a/fsl/data/imagefile.py
+++ b/fsl/data/imagefile.py
@@ -5,28 +5,38 @@
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
+"""Convenience functions for adding/stripping supported
+file extensions to/from image file names.
+"""
 
 import os
 import os.path as op
 
-# The file extensions which we understand. This list is used
-# as the default if if the allowedExts parameter is not passed
-# to any of the functions in this module.
+
 _allowedExts = ['.nii', '.img', '.hdr', '.nii.gz', '.img.gz']
+"""The file extensions which we understand. This list is used as the default
+if if the ``allowedExts`` parameter is not passed to any of the functions in
+this module.
+"""
 
 _descriptions = ['NIFTI1 images',
                  'ANALYZE75 images',
                  'NIFTI1/ANALYZE75 headers',
                  'Compressed NIFTI1 images',
                  'Compressed ANALYZE75/NIFTI1 images']
+"""Descriptions for each of the extensions in :data:`_allowedExts`. """
 
 
-# The default file extension (TODO read this from $FSLOUTPUTTYPE)
 _defaultExt  = '.nii.gz'
+"""The default file extension (TODO read this from ``$FSLOUTPUTTYPE``)."""
 
 
 def wildcard(allowedExts=None):
-    """
+    """Returns a wildcard string for use in a file dialog, to limit
+    the acceptable file types.
+    
+    :arg allowedExts: A list of strings containing the allowed file
+                      extensions.
     """
     
     if allowedExts is None:
@@ -38,7 +48,7 @@ def wildcard(allowedExts=None):
 
     exts = ['*{}'.format(ext) for ext in allowedExts]
 
-    wcParts = ['|'.join((desc,ext)) for (desc,ext) in zip(descs, exts)]
+    wcParts = ['|'.join((desc, ext)) for (desc, ext) in zip(descs, exts)]
 
     print  '|'.join(wcParts)
     return '|'.join(wcParts)
@@ -47,8 +57,13 @@ def wildcard(allowedExts=None):
 
 def isSupported(filename, allowedExts=None):
     """
-    Returns True if the given file has a supported extension, False
+    Returns ``True`` if the given file has a supported extension, ``False``
     otherwise.
+
+    :arg filename:    The file name to test.
+    
+    :arg allowedExts: A list of strings containing the allowed file
+                      extensions.
     """
 
     if allowedExts is None: allowedExts = _allowedExts
@@ -58,8 +73,13 @@ def isSupported(filename, allowedExts=None):
 
 def removeExt(filename, allowedExts=None):
     """
-    Removes the extension from the given file name. Raises a ValueError
+    Removes the extension from the given file name. Raises a :exc:`ValueError`
     if the file has an unsupported extension.
+
+    :arg filename:    The file name to strip.
+    
+    :arg allowedExts: A list of strings containing the allowed file
+                      extensions.    
     """
 
     if allowedExts is None: allowedExts = _allowedExts
@@ -85,22 +105,27 @@ def addExt(
         mustExist=False,
         allowedExts=None,
         defaultExt=None):
-    """
-    Adds a file extension to the given file prefix. If mustExist is False
-    (the default), 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.
+    """Adds a file extension to the given file ``prefix``.
 
-    If mustExist is True, the function checks to see if any files exist
-    that have the given prefix, and a supported file extension.  A
-    ValueError is raised if:
+    If ``mustExist`` is False (the default), 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 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 allowedExts is None: allowedExts = _allowedExts
-- 
GitLab