Commit 8cadf624 authored by Paul McCarthy's avatar Paul McCarthy
Browse files

Removing spurious whitespace (just found out that emacs can do it for me).

parent 4b520dd4
......@@ -5,7 +5,7 @@
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""The :mod:`fsl` package is a library which contains convenience classes
and functions for use by FSL python tools. It is broadly split into the
and functions for use by FSL python tools. It is broadly split into the
following sub-packages:
.. autosummary::
......
#!/usr/bin/env python
#
# atlases.py - API which provides access to the atlas image files contained
# atlases.py - API which provides access to the atlas image files contained
# in $FSLDIR/data/atlases/
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
......@@ -68,7 +68,7 @@ class AtlasRegistry(notifier.Notifier):
``$FSLDIR/data/atlases``, and builds a list of :class:`AtlasDescription`
instances, each of which contains information about one atlas.
The :meth:`addAtlas` method allows other atlases to be added to the
registry. Whenever a new atlas is added, the ``AtlasRegistry`` notifies
any registered listeners via the :class:`.Notifier` interface with the
......@@ -81,18 +81,18 @@ class AtlasRegistry(notifier.Notifier):
The ``AtlasRegistry`` stores a list of all known atlases via the
:mod:`.settings` module. When an ``AtlasRegistry`` is created, it loads
in any previously known atlases. Whenever a new atlas is added, this
in any previously known atlases. Whenever a new atlas is added, this
list is updated. See the :meth:`__getKnownAtlases` and
:meth:`_saveKnownAtlases` methods.
"""
def __init__(self):
"""Create an ``AtlasRegistry``. """
# A list of all AtlasDescription
# instances in existence, sorted
# by AtlasDescription.name.
# instances in existence, sorted
# by AtlasDescription.name.
self.__atlasDescs = []
......@@ -105,9 +105,9 @@ class AtlasRegistry(notifier.Notifier):
log.debug('Initialising atlas registry')
self.__atlasDescs = []
# Get $FSLDIR atlases
# Get $FSLDIR atlases
fslPaths = []
if platform.fsldir is not None:
if platform.fsldir is not None:
fsldir = op.join(platform.fsldir, 'data', 'atlases')
fslPaths = sorted(glob.glob(op.join(fsldir, '*.xml')))
......@@ -115,14 +115,14 @@ class AtlasRegistry(notifier.Notifier):
# been loaded in the past
extraIDs, extraPaths = self.__getKnownAtlases()
# FSLDIR atlases first, any
# FSLDIR atlases first, any
# other atlases second.
atlasPaths = list(fslPaths) + extraPaths
atlasIDs = [None] * len(fslPaths) + extraIDs
with self.skipAll():
for atlasID, atlasPath in zip(atlasIDs, atlasPaths):
# The FSLDIR atlases are probably
# listed twice - from the above glob,
# and from the saved extraPaths. So
......@@ -137,7 +137,7 @@ class AtlasRegistry(notifier.Notifier):
'specification {}'.format(atlasPath),
exc_info=True)
def listAtlases(self):
"""Returns a list containing :class:`AtlasDescription` objects for
all available atlases. The atlases are ordered in terms of the
......@@ -157,17 +157,17 @@ class AtlasRegistry(notifier.Notifier):
"""Returns an :class:`AtlasDescription` instance describing the
atlas with the given ``atlasID``.
"""
for desc in self.__atlasDescs:
if desc.atlasID == atlasID:
return desc
raise KeyError('Unknown atlas ID: {}'.format(atlasID))
def loadAtlas(self, atlasID, loadSummary=False, resolution=None):
"""Loads and returns an :class:`Atlas` instance for the atlas
with the given ``atlasID``.
with the given ``atlasID``.
:arg loadSummary: If ``True``, a 3D :class:`LabelAtlas` image is
loaded. Otherwise, if the atlas is probabilistic,
......@@ -203,8 +203,8 @@ class AtlasRegistry(notifier.Notifier):
atlas with the given ID already exists, this new atlas
is given a unique id.
:arg save: If ``True`` (the default), this atlas will be saved
so that it will be available in future instantiations.
:arg save: If ``True`` (the default), this atlas will be saved
so that it will be available in future instantiations.
"""
filename = op.abspath(filename)
......@@ -233,7 +233,7 @@ class AtlasRegistry(notifier.Notifier):
self.__saveKnownAtlases()
self.notify(topic='add', value=desc)
return desc
......@@ -248,15 +248,15 @@ class AtlasRegistry(notifier.Notifier):
log.debug('Removing atlas from registry: {} / {}'.format(
desc.atlasID,
desc.specPath))
self.__atlasDescs.pop(i)
break
self.__saveKnownAtlases()
self.notify(topic='remove', value=desc)
def __getKnownAtlases(self):
"""Returns a list of tuples containing the IDs and paths of all known
atlases .
......@@ -282,15 +282,15 @@ class AtlasRegistry(notifier.Notifier):
paths = [e[1] for e in atlases]
return names, paths
except:
return [], []
def __saveKnownAtlases(self):
"""Saves the IDs and paths of all atlases which are currently in
the registry. The atlases are saved via the :mod:`.settings` module.
"""
"""
if self.__atlasDescs is None:
return
......@@ -302,10 +302,10 @@ class AtlasRegistry(notifier.Notifier):
atlases = ['{}={}'.format(name, path) for name, path in atlases]
atlases = op.pathsep.join(atlases)
fslsettings.write('fsl.data.atlases', atlases)
class AtlasDescription(object):
"""An ``AtlasDescription`` instance parses and stores the information
stored in the FSL XML file that describes a single FSL atlas. An XML
......@@ -325,11 +325,11 @@ class AtlasDescription(object):
# label, Otherwise, if type is
# Label, path to 3D label file
# (identical to the summaryimagefile
# below). The path must be specified
# below). The path must be specified
# as relative to the location of this
# XML file.
<summaryimagefile> # Path to 3D summary file, with each
<summaryimagefile> # Path to 3D summary file, with each
</summaryimagefile> # region having value (index + 1)
</images>
......@@ -342,7 +342,7 @@ class AtlasDescription(object):
# index - For probabilistic atlases, index of corresponding volume in
# 4D image file. For label images, the value of voxels which
# are in the corresponding region.
#
#
# x |
# y |- XYZ *voxel* coordinates into the first image of the <images>
# | list
......@@ -352,7 +352,7 @@ class AtlasDescription(object):
</data>
</atlas>
Each ``AtlasDescription`` is assigned an identifier, which is simply the
XML file name describing the atlas, sans-suffix, and converted to lower
case. For exmaple, the atlas described by:
......@@ -363,7 +363,7 @@ class AtlasDescription(object):
``harvardoxford-cortical``
This identifier is intended to be unique.
......@@ -371,26 +371,26 @@ class AtlasDescription(object):
================= ======================================================
``atlasID`` The atlas ID, as described above.
``name`` Name of the atlas.
``specPath`` Path to the atlas XML specification file.
``atlasType`` Atlas type - either *probabilistic* or *label*.
``images`` A list of images available for this atlas - usually
:math:`1mm^3` and :math:`2mm^3` images are present.
``summaryImages`` For probabilistic atlases, a list of *summary* images,
which are just 3D labelled variants of the atlas.
``pixdims`` A list of ``(x, y, z)`` pixdim tuples in mm, one for
each image in ``images``.
``xforms`` A list of affine transformation matrices (as ``4*4``
``numpy`` arrays), one for each image in ``images``,
defining the voxel to world coordinate transformations.
``labels`` A list of ``AtlasLabel`` objects, describing each
region / label in the atlas.
================= ======================================================
......@@ -417,7 +417,7 @@ class AtlasDescription(object):
MNI152 space).
"""
def __init__(self, filename, atlasID=None):
"""Create an ``AtlasDescription`` instance.
......@@ -440,7 +440,7 @@ class AtlasDescription(object):
self.specPath = op.abspath(filename)
self.name = header.find('name').text
self.atlasType = header.find('type').text.lower()
# Spelling error in some of the atlas.xml files.
if self.atlasType == 'probabalistic':
self.atlasType = 'probabilistic'
......@@ -452,7 +452,7 @@ class AtlasDescription(object):
self.xforms = []
atlasDir = op.dirname(self.specPath)
for image in images:
imagefile = image.find('imagefile') .text
summaryimagefile = image.find('summaryimagefile').text
......@@ -485,7 +485,7 @@ class AtlasDescription(object):
coords = np.zeros((len(labels), 3), dtype=np.float32)
for i, label in enumerate(labels):
al = AtlasLabel()
al.name = label.text
al.index = int( label.attrib['index'])
......@@ -502,7 +502,7 @@ class AtlasDescription(object):
# into world coordinates
coords = transform.transform(coords, self.xforms[0])
# Update the coordinates
# Update the coordinates
# in our label objects
for i, label in enumerate(self.labels):
......@@ -514,12 +514,12 @@ class AtlasDescription(object):
"""
return self.atlasID == other.atlasID
def __neq__(self, other):
"""Compares the ``atlasID`` of this ``AtlasDescription`` with another.
"""
return self.atlasID != other.atlasID
return self.atlasID != other.atlasID
def __cmp__(self, other):
"""Compares this ``AtlasDescription`` with another by their ``name``
......@@ -534,11 +534,11 @@ class Atlas(fslimage.Image):
logic common to both.
"""
def __init__(self, atlasDesc, resolution=None, isLabel=False):
"""Initialise an ``Atlas``.
:arg atlasDesc: The :class:`AtlasDescription` instance which
:arg atlasDesc: The :class:`AtlasDescription` instance which
describes the atlas.
:arg resolution: Desired isotropic resolution in millimetres.
......@@ -552,13 +552,13 @@ class Atlas(fslimage.Image):
# If a reslution has not been provided,
# choose the atlas image with the
# highest resolution.
#
#
# We divide by three to get the atlas
# image index because there are three
# pixdim values for each atlas.
res = resolution
reses = np.concatenate(atlasDesc.pixdims)
if resolution is None: imageIdx = np.argmin(reses) / 3
else: imageIdx = np.argmin(np.abs(reses - res)) / 3
......@@ -575,7 +575,7 @@ class Atlas(fslimage.Image):
self.desc = atlasDesc
class LabelAtlas(Atlas):
"""A 3D atlas which contains integer labels for each region.
......@@ -593,7 +593,7 @@ class LabelAtlas(Atlas):
"""
Atlas.__init__(self, atlasDesc, resolution, True)
def label(self, worldLoc):
"""Looks up and returns the label of the region at the given world
location, or ``None`` if the location is out of bounds.
......@@ -609,16 +609,16 @@ class LabelAtlas(Atlas):
voxelLoc[1] >= self.shape[1] or \
voxelLoc[2] >= self.shape[2]:
return None
val = self[voxelLoc[0], voxelLoc[1], voxelLoc[2]]
if self.desc.atlasType == 'label':
return val
elif self.desc.atlasType == 'probabilistic':
return val - 1
class ProbabilisticAtlas(Atlas):
"""A 4D atlas which contains one volume for each region.
......@@ -633,10 +633,10 @@ class ProbabilisticAtlas(Atlas):
the atlas.
:arg resolution: Desired isotropic resolution in millimetres.
"""
"""
Atlas.__init__(self, atlasDesc, resolution, False)
def proportions(self, worldLoc):
"""Looks up the region probabilities for the given location.
......@@ -657,7 +657,7 @@ class ProbabilisticAtlas(Atlas):
voxelLoc[1] >= self.shape[1] or \
voxelLoc[2] >= self.shape[2]:
return []
return self[voxelLoc[0], voxelLoc[1], voxelLoc[2], :]
......
......@@ -5,7 +5,7 @@
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :class:`.DTIFitTensor` class, which encapsulates
the diffusion tensor data generated by the FSL ``dtifit`` tool.
the diffusion tensor data generated by the FSL ``dtifit`` tool.
The following utility functions are also defined:
......@@ -34,11 +34,11 @@ log = logging.getLogger(__name__)
def getDTIFitDataPrefix(path):
"""Returns the prefix (a.k,a, base name) used for the ``dtifit`` file
names in the given directory, or ``None`` if the ``dtifit`` files could
"""Returns the prefix (a.k,a, base name) used for the ``dtifit`` file
names in the given directory, or ``None`` if the ``dtifit`` files could
not be identified.
"""
v1s = glob.glob(op.join(path, '*_V1.*'))
v2s = glob.glob(op.join(path, '*_V2.*'))
v3s = glob.glob(op.join(path, '*_V3.*'))
......@@ -58,8 +58,8 @@ def getDTIFitDataPrefix(path):
if prefix not in prefixes: prefixes[prefix] = [f]
else: prefixes[prefix].append(f)
# Discard any prefixes which are
# Discard any prefixes which are
# not present for every file type.
for prefix, files in list(prefixes.items()):
if len(files) != 6:
......@@ -82,7 +82,7 @@ def getDTIFitDataPrefix(path):
# If there's more than one remaining
# prefix, I don't know what to do -
# just return the first one.
# just return the first one.
if len(prefixes) > 1:
log.warning('Multiple dtifit prefixes detected: {}'.format(prefixes))
......@@ -93,12 +93,12 @@ def isDTIFitPath(path):
"""Returns ``True`` if the given directory path looks like it contains
``dtifit`` data, ``False`` otherwise.
"""
return getDTIFitDataPrefix(path) is not None
def looksLikeTensorImage(image):
"""Returns ``True`` if the given :class:`.Image` looks like it could
"""Returns ``True`` if the given :class:`.Image` looks like it could
contain tensor matrix data, ``False`` otherwise.
"""
......@@ -114,7 +114,8 @@ def decomposeTensorMatrix(data):
the unique elements of diffusion tensor matrices at
every voxel.
:returns:
:returns: A tuple containing the principal eigenvectors and
eigenvalues of the tensor matrix.
"""
# The image contains 6 volumes, corresponding
......@@ -141,13 +142,13 @@ def decomposeTensorMatrix(data):
matrices[:, 2, 1] = data[..., 4].flat
matrices[:, 2, 2] = data[..., 5].flat
# Calculate the eigenvectors and
# Calculate the eigenvectors and
# values on all of those matrices
vals, vecs = npla.eig(matrices)
vecShape = list(shape) + [3]
# Grr, np.linalg.eig does not
# sort the eigenvalues/vectors,
# Grr, np.linalg.eig does not
# sort the eigenvalues/vectors,
# so we have to do it ourselves.
order = vals.argsort(axis=1)
i = np.arange(nvoxels)[:, np.newaxis]
......@@ -173,7 +174,7 @@ class DTIFitTensor(fslimage.Nifti):
separate NIFTI images.
"""
def __init__(self, path):
"""Create a ``DTIFitTensor``.
......@@ -202,9 +203,15 @@ class DTIFitTensor(fslimage.Nifti):
self.dataSource = op.abspath(path)
self.name = '{}'.format(op.basename(path))
def V1(self): return self.__v1
def V2(self): return self.__v2
def V3(self): return self.__v3
def L1(self): return self.__l1
def L2(self): return self.__l2
def L3(self): return self.__l3
def V1(self):
return self.__v1
def V2(self):
return self.__v2
def V3(self):
return self.__v3
def L1(self):
return self.__l1
def L2(self):
return self.__l2
def L3(self):
return self.__l3
......@@ -51,7 +51,7 @@ import fsl.utils.path as fslpath
import fsl.utils.transform as transform
from . import image as fslimage
from . import featdesign
from . import featdesign
log = logging.getLogger(__name__)
......@@ -70,7 +70,7 @@ def isFEATImage(path):
dirname = op.dirname( path)
filename = op.basename(path)
return filename.startswith('filtered_func_data') and isFEATDir(dirname)
return filename.startswith('filtered_func_data') and isFEATDir(dirname)
def isFEATDir(path):
......@@ -88,10 +88,10 @@ def isFEATDir(path):
"""
path = op.abspath(path)
if op.isdir(path): dirname = path
else: dirname = op.dirname(path)
if not dirname.endswith('.feat'):
return False
......@@ -99,11 +99,11 @@ def isFEATDir(path):
fslimage.addExt(op.join(dirname, 'filtered_func_data'), mustExist=True)
except fslimage.PathError:
return False
if not op.exists(op.join(dirname, 'design.fsf')): return False
if not op.exists(op.join(dirname, 'design.mat')): return False
if not op.exists(op.join(dirname, 'design.con')): return False
return True
......@@ -117,7 +117,7 @@ def hasStats(featdir):
return True
except:
return False
def hasMelodicDir(featdir):
"""Returns ``True`` if the data for the given FEAT directory has had
......@@ -150,7 +150,7 @@ def getReportFile(featdir):
"""Returns the path to the FEAT report index file, or ``None`` if there
is no report.
"""
report = op.join(featdir, 'report.html')
if op.exists(report): return report
else: return None
......@@ -158,9 +158,9 @@ def getReportFile(featdir):
def loadContrasts(featdir):
"""Loads the contrasts from a FEAT directory. Returns a tuple containing:
- A list of names, one for each contrast.
- A list of contrast vectors (each of which is a list itself).
:arg featdir: A FEAT directory.
......@@ -172,7 +172,7 @@ def loadContrasts(featdir):
designcon = op.join(featdir, 'design.con')
log.debug('Loading FEAT contrasts from {}'.format(designcon))
with open(designcon, 'rt') as f:
while True:
......@@ -183,7 +183,7 @@ def loadContrasts(featdir):
num = [c for c in tkns[0] if c.isdigit()]
num = int(''.join(num))
# The /ContrastName field may not
# The /ContrastName field may not
# actually have a name specified
if len(tkns) > 1:
name = tkns[1].strip()
......@@ -248,7 +248,7 @@ def loadSettings(featdir):
key = key[5:-1]
settings[key] = val
return settings
......@@ -259,7 +259,7 @@ def loadDesign(featdir, settings):
:arg settings: Dictionary containing FEAT settings (see
:func:`loadSettings`).
:returns: a :class:`.FEATFSFDesign` instance which represents the
design matrix.
"""
......@@ -273,7 +273,7 @@ def getThresholds(settings):
The following keys will be present. Threshold values will be ``None``
if the respective statistical thresholding was not carried out:
- ``p``: P-value thresholding
- ``z``: Z-statistic thresholding
......@@ -378,14 +378,14 @@ def loadClusterResults(featdir, settings, contrast):
class Cluster(object):
def __init__(self, **kwargs):
for name, val in kwargs.items():
attrName = colmap[name]
if val is not None:
val = float(val)
setattr(self, attrName, val)
# This dict provides a mapping between
# This dict provides a mapping between
# Cluster object attribute names, and
# the corresponding column name in the
# cluster.txt file.
......@@ -484,7 +484,7 @@ def getDataFile(featdir):
def getMelodicFile(featdir):
"""Returns the name of the file in the FEAT results which contains the
"""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`.
......@@ -505,7 +505,7 @@ def getResidualFile(featdir):
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.
......@@ -524,7 +524,7 @@ def getCOPEFile(featdir, contrast):