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

atlases.listAtlases function made thread-safe, because FSLeyes has a

habit of calling it mutliple times from different threads. Some updates
to AtlasDescription class - am intending to allow users to select
atlas resolution.
parent a54a36ba
No related branches found
No related tags found
No related merge requests found
...@@ -88,6 +88,7 @@ import xml.etree.ElementTree as et ...@@ -88,6 +88,7 @@ import xml.etree.ElementTree as et
import os.path as op import os.path as op
import glob import glob
import collections import collections
import threading
import logging import logging
import numpy as np import numpy as np
...@@ -109,28 +110,49 @@ def listAtlases(refresh=False): ...@@ -109,28 +110,49 @@ def listAtlases(refresh=False):
loaded from the atlas files. Otherwise, previously loaded from the atlas files. Otherwise, previously
loaded descriptions are returned (see loaded descriptions are returned (see
:attr:`ATLAS_DESCRIPTIONS`). :attr:`ATLAS_DESCRIPTIONS`).
.. note:: This function is thread-safe, because *FSLeyes* calls it
in a multi-threaded manner (to avoid blocking the GUI).
""" """
_setAtlasDir() _setAtlasDir()
if ATLAS_DIR is None: if ATLAS_DIR is None:
return [] return []
# Make sure the atlas description
# refresh is only performed by one
# thread. If a thread is loading
# the descriptions, any other thread
# which enters the function will
# block here until the descriptions
# are loaded. When it continues,
# it will see a populated
# ATLAS_DESCRIPTIONS list.
LOAD_ATLAS_LOCK.acquire()
if len(ATLAS_DESCRIPTIONS) == 0: if len(ATLAS_DESCRIPTIONS) == 0:
refresh = True refresh = True
if not refresh: try:
return list(ATLAS_DESCRIPTIONS.values()) if refresh:
log.debug('Loading atlas descriptions')
atlasFiles = glob.glob(op.join(ATLAS_DIR, '*.xml'))
atlasDescs = map(AtlasDescription, atlasFiles)
atlasDescs = sorted(atlasDescs, key=lambda d: d.name)
atlasFiles = glob.glob(op.join(ATLAS_DIR, '*.xml')) ATLAS_DESCRIPTIONS.clear()
atlasDescs = map(AtlasDescription, atlasFiles)
atlasDescs = sorted(atlasDescs, key=lambda d: d.name)
ATLAS_DESCRIPTIONS.clear() for i, desc in enumerate(atlasDescs):
desc.index = i
ATLAS_DESCRIPTIONS[desc.atlasID] = desc
else:
atlasDescs = list(ATLAS_DESCRIPTIONS.values())
for i, desc in enumerate(atlasDescs): finally:
desc.index = i LOAD_ATLAS_LOCK.release()
ATLAS_DESCRIPTIONS[desc.atlasID] = desc
return list(atlasDescs) return list(atlasDescs)
...@@ -189,12 +211,24 @@ class AtlasDescription(object): ...@@ -189,12 +211,24 @@ class AtlasDescription(object):
================= ====================================================== ================= ======================================================
``atlasID`` The atlas ID, as described above. ``atlasID`` The atlas ID, as described above.
``name`` Name of the atlas. ``name`` Name of the atlas.
``atlasType`` Atlas type - either *probabilistic* or *label*. ``atlasType`` Atlas type - either *probabilistic* or *label*.
``images`` A list of images available for this atlas - usually ``images`` A list of images available for this atlas - usually
:math:`1mm^3` and :math:`2mm^3` images are present. :math:`1mm^3` and :math:`2mm^3` images are present.
``summaryImages`` For probabilistic atlases, a list of *summary* images, ``summaryImages`` For probabilistic atlases, a list of *summary* images,
which are just 3D labelled variants of the atlas. 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 ``labels`` A list of ``AtlasLabel`` objects, describing each
region / label in the atlas. region / label in the atlas.
================= ====================================================== ================= ======================================================
...@@ -216,8 +250,9 @@ class AtlasDescription(object): ...@@ -216,8 +250,9 @@ class AtlasDescription(object):
.. note:: The ``x``, ``y`` and ``z`` label coordinates are pre-calculated .. note:: The ``x``, ``y`` and ``z`` label coordinates are pre-calculated
centre-of-gravity coordinates, as listed in the atlas xml file. centre-of-gravity coordinates, as listed in the atlas xml file.
They are in the coordinate system defined by the atlas image They are in the coordinate system defined by the transformation
transformation matrix (typically MNI152 space). matrix for the first image in the ``images`` list.(typically
MNI152 space).
""" """
...@@ -244,6 +279,9 @@ class AtlasDescription(object): ...@@ -244,6 +279,9 @@ class AtlasDescription(object):
images = header.findall('images') images = header.findall('images')
self.images = [] self.images = []
self.summaryImages = [] self.summaryImages = []
self.pixdims = []
self.xforms = []
for image in images: for image in images:
imagefile = image.find('imagefile') .text imagefile = image.find('imagefile') .text
...@@ -252,8 +290,12 @@ class AtlasDescription(object): ...@@ -252,8 +290,12 @@ class AtlasDescription(object):
imagefile = op.join(ATLAS_DIR, '.' + imagefile) imagefile = op.join(ATLAS_DIR, '.' + imagefile)
summaryimagefile = op.join(ATLAS_DIR, '.' + summaryimagefile) summaryimagefile = op.join(ATLAS_DIR, '.' + summaryimagefile)
i = fslimage.Image(imagefile, loadData=False)
self.images .append(imagefile) self.images .append(imagefile)
self.summaryImages.append(summaryimagefile) self.summaryImages.append(summaryimagefile)
self.pixdims .append(i.pixdim[:3])
self.xforms .append(i.voxToWorldMat)
# A container object used for # A container object used for
# storing atlas label information # storing atlas label information
...@@ -286,8 +328,7 @@ class AtlasDescription(object): ...@@ -286,8 +328,7 @@ class AtlasDescription(object):
# Load the appropriate transformation matrix # Load the appropriate transformation matrix
# and transform all those voxel coordinates # and transform all those voxel coordinates
# into world coordinates # into world coordinates
xform = fslimage.Image(self.images[0], loadData=False).voxToWorldMat coords = transform.transform(coords, self.xforms[0].T)
coords = transform.transform(coords, xform.T)
# Update the coordinates # Update the coordinates
# in our label objects # in our label objects
...@@ -433,6 +474,12 @@ all atlases contained in ``$FSLDIR/data/atlases/``. ...@@ -433,6 +474,12 @@ all atlases contained in ``$FSLDIR/data/atlases/``.
""" """
LOAD_ATLAS_LOCK = threading.Lock()
"""This is used as a mutual-exclusion lock by the :func:`listAtlases`
function, to make it thread-safe.
"""
def _setAtlasDir(): def _setAtlasDir():
"""Called by the :func:`listAtlases`, :func:`getAtlasDescription` and """Called by the :func:`listAtlases`, :func:`getAtlasDescription` and
:func:`loadAtlas` functions. :func:`loadAtlas` functions.
......
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