Skip to content
Snippets Groups Projects
Commit 7f7dd1ed authored by Paul McCarthy's avatar Paul McCarthy
Browse files

Documented fsl.data package.

parent ba84cdf7
No related branches found
No related tags found
No related merge requests found
#!/usr/bin/env python
#
# __init__.py - fsl.data package.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""Data structures and models."""
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
# #
# Author: Paul McCarthy <pauldmccarthy@gmail.com> # Author: Paul McCarthy <pauldmccarthy@gmail.com>
# #
"""Classes for representing 3D/4D images, display properties of images, and
collections of images.
"""
import os import os
import sys import sys
...@@ -28,13 +31,13 @@ log = logging.getLogger(__name__) ...@@ -28,13 +31,13 @@ log = logging.getLogger(__name__)
def _loadImageFile(filename): def _loadImageFile(filename):
""" """Given the name of an image file, loads it using nibabel.
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, If the file is large, and is gzipped, it is decompressed to a temporary
so that it can be memory-mapped. A tuple is returned, consisting of location, so that it can be memory-mapped. A tuple is returned,
the nibabel image object, and the name of the file that it was loaded consisting of the nibabel image object, and the name of the file that it
from (either the passed-in file name, or the name of the temporary was loaded from (either the passed-in file name, or the name of the
decompressed file). temporary decompressed file).
""" """
# If we have a GUI, we can display a dialog # If we have a GUI, we can display a dialog
...@@ -48,71 +51,113 @@ def _loadImageFile(filename): ...@@ -48,71 +51,113 @@ def _loadImageFile(filename):
pass pass
realFilename = filename realFilename = filename
mbytes = op.getsize(filename) / 1048576.0
if filename.endswith('.nii.gz'): # The mbytes limit is arbitrary
if filename.endswith('.nii.gz') and mbytes > 512:
mbytes = op.getsize(filename) / 1048576.0
# This limit is arbitrary unzipped, filename = tempfile.mkstemp(suffix='.nii')
if mbytes > 512:
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 ' \ if not haveGui:
'to {}, to allow memory mapping...'.format(realFilename, log.info(msg)
mbytes, else:
filename) 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] gzip = ['gzip', '-d', '-c', realFilename]
log.debug('Running {} > {}'.format(' '.join(gzip), filename)) log.debug('Running {} > {}'.format(' '.join(gzip), filename))
# If the gzip call fails, revert to loading from the gzipped file # If the gzip call fails, revert to loading from the gzipped file
try: try:
sp.call(gzip, stdout=unzipped) sp.call(gzip, stdout=unzipped)
unzipped.close() unzipped.close()
except OSError as e: except OSError as e:
log.warn('gzip call failed ({}) - cannot memory ' log.warn('gzip call failed ({}) - cannot memory '
'map file: {}'.format(e, realFilename), 'map file: {}'.format(e, realFilename),
exc_info=True) exc_info=True)
unzipped.close() unzipped.close()
os.remove(filename) os.remove(filename)
filename = realFilename filename = realFilename
if haveGui: if haveGui:
busyDlg.Destroy() busyDlg.Destroy()
return nib.load(filename), filename return nib.load(filename), filename
class Image(props.HasProperties): class Image(props.HasProperties):
""" """Class which represents a 3D/4D image. Internally, the image is
Class which represents a 3D/4D image. Internally, the image is loaded/stored using :mod:`nibabel`.
loaded/stored using 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( transform = props.Choice(
collections.OrderedDict([ collections.OrderedDict([
('affine', 'Use qform/sform transformation matrix'), ('affine', 'Use qform/sform transformation matrix'),
('pixdim', 'Use pixdims only'), ('pixdim', 'Use pixdims only'),
('id', 'Do not use qform/sform or pixdims')]), ('id', 'Do not use qform/sform or pixdims')]),
default='affine') default='affine')
"""This property defines how the image should be transformd into real world
name = props.String() space.
imageFile = props.FilePath()
tempFile = props.FilePath() - ``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): 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 # The image parameter may be the name of an image file
...@@ -172,24 +217,27 @@ class Image(props.HasProperties): ...@@ -172,24 +217,27 @@ class Image(props.HasProperties):
def _transformChanged(self, *a): 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': if self.transform == 'affine':
voxToWorldMat = self.nibImage.get_affine() voxToWorldMat = self.nibImage.get_affine()
pixdims = self.nibImage.get_header().get_zooms() pixdim = self.nibImage.get_header().get_zooms()
elif self.transform == 'pixdim': elif self.transform == 'pixdim':
pixdims = self.nibImage.get_header().get_zooms() pixdim = self.nibImage.get_header().get_zooms()
voxToWorldMat = np.diag([pixdims[0], pixdims[1], pixdims[2], 1.0]) voxToWorldMat = np.diag([pixdim[0], pixdim[1], pixdim[2], 1.0])
elif self.transform == 'id': elif self.transform == 'id':
voxToWorldMat = np.identity(4) 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.voxToWorldMat = np.array(voxToWorldMat, dtype=np.float32)
self.worldToVoxMat = linalg.inv(self.voxToWorldMat) self.worldToVoxMat = linalg.inv(self.voxToWorldMat)
self.pixdims = pixdims self.pixdim = pixdim
self.voxToWorldMat = self.voxToWorldMat.transpose() self.voxToWorldMat = self.voxToWorldMat.transpose()
self.worldToVoxMat = self.worldToVoxMat.transpose() self.worldToVoxMat = self.worldToVoxMat.transpose()
...@@ -198,7 +246,7 @@ class Image(props.HasProperties): ...@@ -198,7 +246,7 @@ class Image(props.HasProperties):
# location (0, 0) to map to voxel location (0, 0) # location (0, 0) to map to voxel location (0, 0)
if self.transform in ['pixdim', 'id']: if self.transform in ['pixdim', 'id']:
for i in range(3): 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 self.worldToVoxMat[3, i] = -0.5
log.debug('Image {} transformation matrix changed: {}'.format( log.debug('Image {} transformation matrix changed: {}'.format(
...@@ -207,9 +255,8 @@ class Image(props.HasProperties): ...@@ -207,9 +255,8 @@ class Image(props.HasProperties):
def imageBounds(self, axis): def imageBounds(self, axis):
""" """Return the bounds (min, max) of the image, in real world
Return the bounds (min, max) of the image, in real world coordinates, along the specified 0-indexed axis.
coordinates, along the specified axis.
""" """
x, y, z = self.shape[:3] x, y, z = self.shape[:3]
...@@ -238,24 +285,23 @@ class Image(props.HasProperties): ...@@ -238,24 +285,23 @@ class Image(props.HasProperties):
def worldToVox(self, p, axes=None): def worldToVox(self, p, axes=None):
""" """Transforms the given set of points in voxel coordinates to points
Transforms the given set of points in voxel coordinates to in world coordinates, according to the current :attr:`transform`.
points in world coordinates, according to the affine
transformation specified in the image file. The returned array The returned array is either a :class:`numpy.float64` array, or a
is either a numpy.float64 array, or a single integer value, single ``int`` value, depending on the input. There is no guarantee
depending on the input. There is no guarantee that the returned that the returned array of voxel coordinates is within the bounds of
array of voxel coordinates is within the bounds of the image the image shape. Parameters:
shape. Parameters:
- p: N*A array, where N is the number of points, and A :arg p: N*A array, where N is the number of points, and A
is the number of axes to consider (default: 3) is the number of axes to consider (default: 3).
- axes: If None, it is assumed that the input p is a N*3 :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 array, with each point being specified by x,y,z
coordinates. If a single value in the range (0-2), coordinates. If a single value in the range (0-2),
it is assumed that p is a 1D array. Or, if a it is assumed that p is a 1D array. Or, if a
sequence of 2 or 3 values, p must be an array of sequence of 2 or 3 values, p must be an array of
N*2 or N*3, respectively. N*2 or N*3, respectively.
""" """
voxp = self._transform(p, self.worldToVoxMat, axes) voxp = self._transform(p, self.worldToVoxMat, axes)
...@@ -273,13 +319,13 @@ class Image(props.HasProperties): ...@@ -273,13 +319,13 @@ class Image(props.HasProperties):
def voxToWorld(self, p, axes=None): def voxToWorld(self, p, axes=None):
""" """Transforms the given set of points in world coordinates to
Transforms the given set of points in world coordinates to points in voxel coordinates, according to the current
points in voxel coordinates, according to the affine :attr:`transform`.
transformation specified in the image file. The returned
array is either a numpy.float64 array, or a single float The returned array is either a :class:`numpy.float64` array,
value, depending on the input. See the worldToVox or a single ``float`` value, depending on the input. See the
docstring for more details. :meth:`worldToVox` method for more details.
""" """
worldp = self._transform(p, self.voxToWorldMat, axes) worldp = self._transform(p, self.voxToWorldMat, axes)
...@@ -289,11 +335,11 @@ class Image(props.HasProperties): ...@@ -289,11 +335,11 @@ class Image(props.HasProperties):
def _transform(self, p, a, axes): def _transform(self, p, a, axes):
""" """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 Transforms the given set of points ``p`` according to the given affine
as a numpy.float64 array. See the worldToVox docstring for transformation ``a``. The transformed points are returned as a
more details. :class:``numpy.float64`` array.
""" """
p = self._fillPoints(p, axes) p = self._fillPoints(p, axes)
...@@ -313,9 +359,8 @@ class Image(props.HasProperties): ...@@ -313,9 +359,8 @@ class Image(props.HasProperties):
def _fillPoints(self, p, axes): def _fillPoints(self, p, axes):
""" """Used by the :meth:`_transform` method. Turns the given array p into
Used by the _transform method. Turns the given array p into a N*3 a N*3 array of x,y,z coordinates. The array p may be a 1D array, or an
array of x,y,z coordinates. The array p may be a 1D array, or an
N*2 or N*3 array. N*2 or N*3 array.
""" """
...@@ -347,16 +392,12 @@ class Image(props.HasProperties): ...@@ -347,16 +392,12 @@ class Image(props.HasProperties):
def getAttribute(self, name): def getAttribute(self, name):
""" """Retrieve the attribute with the given name."""
Retrieve the attribute with the given name.
"""
return self._attributes[name] return self._attributes[name]
def setAttribute(self, name, value): 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 self._attributes[name] = value
log.debug('Attribute set on {}: {} = {}'.format( log.debug('Attribute set on {}: {} = {}'.format(
...@@ -364,31 +405,48 @@ class Image(props.HasProperties): ...@@ -364,31 +405,48 @@ class Image(props.HasProperties):
class ImageDisplay(props.HasProperties): class ImageDisplay(props.HasProperties):
""" """A class which describes how an image should be displayed.
A class which describes how an image should be displayed. There should
be no need to manually instantiate ImageDisplay objects - one is created There should be no need to manually instantiate :class:`ImageDisplay`
for each Image object, and is accessed via the Image.display attribute. objects - one is created for each :class:`Image` object, and is accessed
If a single image needs to be displayed in different ways, then create via the :attr:`Image.display` instance attribute. If a single image needs
away, and manage your own ImageDisplay objects. 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 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 actually display an :class:`Image` to adhere to the properties stored in
associated ImageDisplay object. the associated :class:`ImageDisplay` object.
""" """
enabled = props.Boolean(default=True) enabled = props.Boolean(default=True)
alpha = props.Real(minval=0.0, maxval=1.0, default=1.0) """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, displayRange = props.Bounds(ndims=1, editLimits=True,
labels=['Min.', 'Max.']) 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) samplingRate = props.Int(minval=1, maxval=16, default=1, clamped=True)
rangeClip = props.Boolean(default=False) """Only display every Nth voxel (a performance tweak)."""
cmap = props.ColourMap(default=mplcm.Greys_r)
volume = props.Int(minval=0, maxval=0, default=0, clamped=True)
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): def is4DImage(self):
"""Returns ``True`` if this image is 4D, ``False`` otherwise.
"""
return len(self.image.shape) > 3 and self.image.shape[3] > 1 return len(self.image.shape) > 3 and self.image.shape[3] > 1
_view = props.VGroup(('enabled', _view = props.VGroup(('enabled',
props.Widget('volume', enabledWhen=is4DImage), props.Widget('volume', enabledWhen=is4DImage),
'displayRange', 'displayRange',
...@@ -420,9 +478,9 @@ class ImageDisplay(props.HasProperties): ...@@ -420,9 +478,9 @@ class ImageDisplay(props.HasProperties):
def __init__(self, image): def __init__(self, image):
""" """Create an :class:`ImageDisplay` for the specified image.
Create an ImageDisplay for the specified image. The image
parameter should be an Image object (defined above). :arg image: A :class:`Image` object.
""" """
self.image = image self.image = image
...@@ -453,44 +511,58 @@ class ImageDisplay(props.HasProperties): ...@@ -453,44 +511,58 @@ class ImageDisplay(props.HasProperties):
class ImageList(props.HasProperties): class ImageList(props.HasProperties):
""" """Class representing a collection of images to be displayed together.
Class representing a collection of images to be displayed together.
Contains a List property containing Image objects, and some other Contains a :class:`props.properties_types.List` property containing
properties on which listeners may register themselves to be notified :class:`Image` objects, and some other properties on which listeners may
when the properties of the image collection changes (e.g. image register themselves to be notified when the properties of the image
bounds). 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): 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)) 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) 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) 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) 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')) 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): def __init__(self, images=None):
""" """Create an ImageList object from the given sequence of
Create an ImageList object from the given sequence of Image objects. :class:`Image` objects."""
"""
if images is None: images = [] if images is None: images = []
...@@ -514,10 +586,9 @@ class ImageList(props.HasProperties): ...@@ -514,10 +586,9 @@ class ImageList(props.HasProperties):
def _imageListChanged(self, *a): def _imageListChanged(self, *a):
""" """Called whenever an item is added or removed from the :attr:`images`
Called whenever an item is added or removed from the list. Registers list. Registers listeners with the properties of each image, and
listeners with the properties of each image, and updates the image calls the :meth:`_updateImageBounds` method.
bounds
""" """
for img in self.images: for img in self.images:
...@@ -535,9 +606,9 @@ class ImageList(props.HasProperties): ...@@ -535,9 +606,9 @@ class ImageList(props.HasProperties):
def _updateImageBounds(self, *a): def _updateImageBounds(self, *a):
""" """Called whenever an item is added or removed from the
Called whenever an item is added or removed from the list, or an :attr:`images` list, or an image property changes. Updates
image property changes. Updates the xyz bounds. the :attr:`bounds` property.
""" """
if len(self.images) == 0: if len(self.images) == 0:
...@@ -574,7 +645,7 @@ class ImageList(props.HasProperties): ...@@ -574,7 +645,7 @@ class ImageList(props.HasProperties):
def __iter__( self): return self.images.__iter__() def __iter__( self): return self.images.__iter__()
def __contains__(self, item): return self.images.__contains__(item) def __contains__(self, item): return self.images.__contains__(item)
def __setitem__( self, key, val): return self.images.__setitem__(key, val) 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 index( self, item): return self.images.index(item)
def count( self, item): return self.images.count(item) def count( self, item): return self.images.count(item)
def append( self, item): return self.images.append(item) def append( self, item): return self.images.append(item)
......
...@@ -5,28 +5,38 @@ ...@@ -5,28 +5,38 @@
# #
# Author: Paul McCarthy <pauldmccarthy@gmail.com> # Author: Paul McCarthy <pauldmccarthy@gmail.com>
# #
"""Convenience functions for adding/stripping supported
file extensions to/from image file names.
"""
import os import os
import os.path as op 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'] _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', _descriptions = ['NIFTI1 images',
'ANALYZE75 images', 'ANALYZE75 images',
'NIFTI1/ANALYZE75 headers', 'NIFTI1/ANALYZE75 headers',
'Compressed NIFTI1 images', 'Compressed NIFTI1 images',
'Compressed ANALYZE75/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' _defaultExt = '.nii.gz'
"""The default file extension (TODO read this from ``$FSLOUTPUTTYPE``)."""
def wildcard(allowedExts=None): 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: if allowedExts is None:
...@@ -38,7 +48,7 @@ def wildcard(allowedExts=None): ...@@ -38,7 +48,7 @@ def wildcard(allowedExts=None):
exts = ['*{}'.format(ext) for ext in allowedExts] 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) print '|'.join(wcParts)
return '|'.join(wcParts) return '|'.join(wcParts)
...@@ -47,8 +57,13 @@ def wildcard(allowedExts=None): ...@@ -47,8 +57,13 @@ def wildcard(allowedExts=None):
def isSupported(filename, 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. 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 if allowedExts is None: allowedExts = _allowedExts
...@@ -58,8 +73,13 @@ def isSupported(filename, allowedExts=None): ...@@ -58,8 +73,13 @@ def isSupported(filename, allowedExts=None):
def removeExt(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. 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 if allowedExts is None: allowedExts = _allowedExts
...@@ -85,22 +105,27 @@ def addExt( ...@@ -85,22 +105,27 @@ def addExt(
mustExist=False, mustExist=False,
allowedExts=None, allowedExts=None,
defaultExt=None): defaultExt=None):
""" """Adds a file extension to the given file ``prefix``.
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.
If mustExist is True, the function checks to see if any files exist If ``mustExist`` is False (the default), and the file does not already
that have the given prefix, and a supported file extension. A have a supported extension, the default extension is appended and the new
ValueError is raised if: 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. - No files exist with the given prefix and a supported extension.
- More than one file exists with the given prefix, and a supported - More than one file exists with the given prefix, and a supported
extension. extension.
Otherwise the full file name is returned. 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 if allowedExts is None: allowedExts = _allowedExts
......
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