diff --git a/fsl/data/atlases.py b/fsl/data/atlases.py index 6ec39a89701554f55c4a943cdd3315e02cf2b004..b28eb952c98ccbda29f2097ca8eda9ae660cba4c 100644 --- a/fsl/data/atlases.py +++ b/fsl/data/atlases.py @@ -366,6 +366,15 @@ class AtlasLabel(object): """ return self.index < other.index + def __repr__(self): + """ + Represents AtlasLabel as string + """ + return '{}({}, index={}, value={})'.format( + self.__class__.__name__, self.name, + self.index, self.value, + ) + class AtlasDescription(object): """An ``AtlasDescription`` instance parses and stores the information @@ -574,11 +583,11 @@ class AtlasDescription(object): self.labels = list(sorted(self.labels)) - def find(self, index=None, value=None): + def find(self, index=None, value=None, name=None): """Find an :class:`.AtlasLabel` either by ``index``, or by ``value``. - Exactly one of ``index`` or ``value`` may be specified - a - ``ValueError`` is raised otherwise. If an invalid ``index`` or + Exactly one of ``index``, ``value``, or ``name`` may be specified - a + ``ValueError`` is raised otherwise. If an invalid ``index``, ``name``, or ``value`` is specified, an ``IndexError`` or ``KeyError`` will be raised. @@ -586,12 +595,25 @@ class AtlasDescription(object): labels, and a 3D ``LabelAtlas`` may have more values than labels. """ - if (index is None and value is None) or \ - (index is not None and value is not None): - raise ValueError('Only one of index or value may be specified') + if ((index is not None) + (value is not None) + (name is not None)) != 1: + raise ValueError('Only one of index, value, or name may be specified') + if index is not None: return self.labels[ index] + elif value is not None: return self.__labelsByValue[int(value)] + else: + matches = [l for l in self.labels if l.name == name] + if len(matches) == 0: + # look for partial matches only if there are no full matches + matches = [l for l in self.labels if l.name[:len(name)] == name] + if len(matches) == 0: + raise IndexError('No match for {} found in labels {}'.format( + name, tuple(l.name for l in self.labels) + )) + elif len(matches) > 1: + raise IndexError('Multiple matches for {} found in labels {}'.format( + name, tuple(l.name for l in self.labels) + )) + return matches[0] - if index is not None: return self.labels[ index] - else: return self.__labelsByValue[int(value)] def __eq__(self, other): @@ -612,6 +634,12 @@ class AtlasDescription(object): """ return self.name.lower() < other.name.lower() + def __repr__(self, ): + """ + String representation of AtlasDescription + """ + return '{}({})'.format(self.__class__.__name__, self.atlasID) + class Atlas(fslimage.Image): """This is the base class for the :class:`LabelAtlas` and @@ -849,6 +877,28 @@ class LabelAtlas(Atlas): return values, props + def get(self, label=None, index=None, value=None, name=None): + """ + Returns the binary image for given label + + Only one of the arguments should be used to define the label + + :arg label: AtlasLabel contained within this atlas + :arg index: index of the label + :arg value: value of the label + :arg name: string of the label + :return: image.Image with the mask + """ + if ((label is not None) + (index is not None) + + (value is not None) + (name is not None)) != 1: + raise ValueError('Only one of label, index, value, or name may be specified') + if label is None: + label = self.find(index=index, name=name, value=value) + elif label not in self.desc.labels: + raise ValueError("Unknown label provided") + arr = (self.data == label.value).astype(int) + return fslimage.Image(arr, name=label.name, header=self.header) + class ProbabilisticAtlas(Atlas): """A 4D atlas which contains one volume for each region. @@ -867,6 +917,27 @@ class ProbabilisticAtlas(Atlas): """ Atlas.__init__(self, atlasDesc, resolution, False, **kwargs) + def get(self, label=None, index=None, value=None, name=None): + """ + Returns the probabilistic image for given label + + Only one of the arguments should be used to define the label + + :arg label: AtlasLabel contained within this atlas + :arg index: index of the label + :arg value: value of the label + :arg name: string of the label + :return: image.Image with the probabilistic mask + """ + if ((label is not None) + (index is not None) + + (value is not None) + (name is not None)) != 1: + raise ValueError('Only one of label, index, value, or name may be specified') + if label is None: + label = self.find(index=index, value=value, name=name) + elif label not in self.desc.labels: + raise ValueError("Unknown label provided") + arr = self[..., label.index] + return fslimage.Image(arr, name=label.name, header=self.header) def proportions(self, location, *args, **kwargs): """Looks up and returns the proportions of of all regions at the given diff --git a/fsl/utils/filetree/trees/ProbtrackX.tree b/fsl/utils/filetree/trees/ProbtrackX.tree index 38db04c26930365b9046b8171ba28bfbd116ff39..06daa454663e653654814de6b36e8cb6f1214672 100644 --- a/fsl/utils/filetree/trees/ProbtrackX.tree +++ b/fsl/utils/filetree/trees/ProbtrackX.tree @@ -1,4 +1,4 @@ -basename=fdt +basename=fdt_paths probtrackx.log (log_cmd) {basename}.log (log_settings) diff --git a/tests/test_atlases.py b/tests/test_atlases.py index c2d257f601da2eaf61b5de4eb6f4cd3e148d8d7d..b2dfec113a00349dea0601f781f565561fa18b2c 100644 --- a/tests/test_atlases.py +++ b/tests/test_atlases.py @@ -99,6 +99,8 @@ def test_AtlasDescription(): tal = registry.getAtlasDescription('talairach') cort = registry.getAtlasDescription('harvardoxford-cortical') + assert str(tal) == 'AtlasDescription(talairach)' + assert str(cort) == 'AtlasDescription(harvardoxford-cortical)' assert tal.atlasID == 'talairach' assert tal.name == 'Talairach Daemon Labels' @@ -140,8 +142,6 @@ def test_AtlasDescription(): registry.getAtlasDescription('non-existent-atlas') - - def test_add_remove_atlas(): with tests.testdir() as testdir: @@ -235,6 +235,23 @@ def test_load_atlas(): assert isinstance(lblatlas, atlases.LabelAtlas) +def test_get(): + + reg = atlases.registry + reg.rescanAtlases() + + probatlas = reg.loadAtlas('harvardoxford-cortical') + lblatlas = reg.loadAtlas('talairach') + for atlas in (probatlas, lblatlas): + for idx, label in enumerate(atlas.desc.labels[:10]): + target = probatlas[..., idx] if atlas is probatlas else lblatlas.data == label.value + assert (target == atlas.get(label).data).all() + assert label.name == atlas.get(label).name + assert (target == atlas.get(index=label.index).data).all() + assert (target == atlas.get(value=label.value).data).all() + assert (target == atlas.get(name=label.name).data).all() + + def test_find(): reg = atlases.registry @@ -252,16 +269,31 @@ def test_find(): assert atlas .find(value=label.value) == label assert atlas .find(index=label.index) == label + assert atlas .find(name=label.name) == label assert atlas.desc.find(value=label.value) == label assert atlas.desc.find(index=label.index) == label + assert atlas.desc.find(name=label.name) == label + + if atlas is not lblatlas: + # lblatlas has a lot of very similar label names + assert atlas .find(name=label.name[:-2]) == label + assert atlas.desc.find(name=label.name[:-2]) == label with pytest.raises(ValueError): atlas.find() with pytest.raises(ValueError): atlas.find(index=1, value=1) + with pytest.raises(ValueError): + atlas.find(index=1, name=1) + with pytest.raises(ValueError): + atlas.find(value=1, name=1) with pytest.raises(IndexError): atlas.find(index=len(labels)) + with pytest.raises(IndexError): + atlas.find(name='InvalidROI') + with pytest.raises(IndexError): + atlas.find(name='') maxval = max([l.value for l in labels]) with pytest.raises(KeyError):