Skip to content
Snippets Groups Projects
Commit 3bffd560 authored by Paul McCarthy's avatar Paul McCarthy :mountain_bicyclist:
Browse files

Merge branch 'mnt/gifti-metadata' into 'main'

MNT: Make GIfTI metadata available

See merge request fsl/fslpy!416
parents 28adcae2 edf034f0
No related branches found
No related tags found
No related merge requests found
......@@ -134,7 +134,7 @@ variables:
rules:
- if: $SKIP_TESTS != null
when: never
- if: $CI_COMMIT_MESSAGE =~ /skip-tests/
- if: $CI_COMMIT_MESSAGE =~ /\[skip-tests\]/
when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: on_success
......
......@@ -2,6 +2,17 @@ This document contains the ``fslpy`` release history in reverse chronological
order.
3.15.0 (Under development)
--------------------------
Changed
^^^^^^^
* All metadata stored in GIfTI files is now copied by :class:`.GiftiMesh`
instances into their :class:`.Meta` store (!416).
3.14.1 (Thursday 31st August 2023)
----------------------------------
......
......@@ -440,11 +440,12 @@ class BrainStructure(object):
secondary_str = 'AnatomicalStructureSecondary'
primary = "other"
secondary = None
for meta in [gifti_obj] + gifti_obj.darrays:
if primary_str in meta.meta.metadata:
primary = meta.meta.metadata[primary_str]
if secondary_str in meta.meta.metadata:
secondary = meta.meta.metadata[secondary_str]
for obj in [gifti_obj] + gifti_obj.darrays:
if primary_str in obj.meta:
primary = obj.meta[primary_str]
if secondary_str in obj.meta:
secondary = obj.meta[secondary_str]
anatomy = cls.from_string(primary, issurface=True)
anatomy.secondary = None if secondary is None else secondary.lower()
return anatomy
......
......@@ -101,9 +101,24 @@ class GiftiMesh(fslmesh.Mesh):
for i, v in enumerate(vertices):
if i == 0: key = infile
else: key = '{}_{}'.format(infile, i)
else: key = f'{infile}_{i}'
self.addVertices(v, key, select=(i == 0), fixWinding=fixWinding)
self.setMeta(infile, surfimg)
self.meta[infile] = surfimg
# Copy all metadata entries for the GIFTI image
for k, v in surfimg.meta.items():
self.meta[k] = v
# and also for each GIFTI data array - triangles
# are stored under "faces", and pointsets are
# stored under "vertices"/[0,1,2...] (as there may
# be multiple pointsets in a file)
self.meta['vertices'] = {}
for i, arr in enumerate(surfimg.darrays):
if arr.intent == constants.NIFTI_INTENT_POINTSET:
self.meta['vertices'][i] = dict(arr.meta)
elif arr.intent == constants.NIFTI_INTENT_TRIANGLE:
self.meta['faces'] = dict(arr.meta)
if vdata is not None:
self.addVertexData(infile, vdata)
......@@ -130,7 +145,7 @@ class GiftiMesh(fslmesh.Mesh):
continue
self.addVertices(vertices[0], sfile, select=False)
self.setMeta(sfile, surfimg)
self.meta[sfile] = surfimg
def loadVertices(self, infile, key=None, *args, **kwargs):
......@@ -154,10 +169,10 @@ class GiftiMesh(fslmesh.Mesh):
for i, v in enumerate(vertices):
if i == 0: key = infile
else: key = '{}_{}'.format(infile, i)
else: key = f'{infile}_{i}'
vertices[i] = self.addVertices(v, key, *args, **kwargs)
self.setMeta(infile, surfimg)
self.meta[infile] = surfimg
return vertices
......@@ -221,12 +236,12 @@ def loadGiftiMesh(filename):
vdata = [d for d in gimg.darrays if d.intent not in (pscode, tricode)]
if len(triangles) != 1:
raise ValueError('{}: GIFTI surface files must contain '
'exactly one triangle array'.format(filename))
raise ValueError(f'{filename}: GIFTI surface files must '
'contain exactly one triangle array')
if len(pointsets) == 0:
raise ValueError('{}: GIFTI surface files must contain '
'at least one pointset array'.format(filename))
raise ValueError(f'{filename}: GIFTI surface files must '
'contain at least one pointset array')
vertices = [ps.data for ps in pointsets]
indices = np.atleast_2d(triangles[0].data)
......@@ -276,14 +291,14 @@ def prepareGiftiVertexData(darrays, filename=None):
intents = {d.intent for d in darrays}
if len(intents) != 1:
raise ValueError('{} contains multiple (or no) intents'
': {}'.format(filename, intents))
raise ValueError(f'{filename} contains multiple '
f'(or no) intents: {intents}')
intent = intents.pop()
if intent in (constants.NIFTI_INTENT_POINTSET,
constants.NIFTI_INTENT_TRIANGLE):
raise ValueError('{} contains surface data'.format(filename))
raise ValueError(f'{filename} contains surface data')
# Just a single array - return it as-is.
# n.b. Storing (M, N) data in a single
......@@ -298,8 +313,8 @@ def prepareGiftiVertexData(darrays, filename=None):
vdata = [d.data for d in darrays]
if any([len(d.shape) != 1 for d in vdata]):
raise ValueError('{} contains one or more non-vector '
'darrays'.format(filename))
raise ValueError(f'{filename} contains one or '
'more non-vector darrays')
vdata = np.vstack(vdata).T
vdata = vdata.reshape(vdata.shape[0], -1)
......@@ -374,7 +389,7 @@ def relatedFiles(fname, ftypes=None):
def searchhcp(match, ftype):
prefix, space = match
template = '{}.*.{}{}'.format(prefix, space, ftype)
template = f'{prefix}.*.{space}{ftype}'
return glob.glob(op.join(dirname, template))
# BIDS style - extract all entities (kv
......
......@@ -147,6 +147,8 @@ def testdir(contents=None, suffix=""):
shutil.rmtree(self.testdir)
return ctx(contents)
testdir.__test__ = False
def make_dummy_files(paths):
"""Creates dummy files for all of the given paths. """
......
......@@ -19,6 +19,8 @@ def test_meta():
for k, v in data.items():
assert m.getMeta(k) == v
assert m.meta == data
assert list(data.keys()) == list(m.metaKeys())
assert list(data.values()) == list(m.metaValues())
assert list(data.items()) == list(m.metaItems())
......
......@@ -7,12 +7,9 @@
"""This module provides the :class:`Meta` class. """
import collections
class Meta(object):
"""The ``Meta`` class is intended to be used as a mixin for other classes. It
is simply a wrapper for a dictionary of key-value pairs.
class Meta:
"""The ``Meta`` class is intended to be used as a mixin for other classes.
It is simply a wrapper for a dictionary of key-value pairs.
It has a handful of methods allowing you to add and access additional
metadata associated with an object.
......@@ -20,6 +17,7 @@ class Meta(object):
.. autosummary::
:nosignatures:
meta
metaKeys
metaValues
metaItems
......@@ -32,11 +30,17 @@ class Meta(object):
"""Initialises a ``Meta`` instance. """
new = super(Meta, cls).__new__(cls)
new.__meta = collections.OrderedDict()
new.__meta = {}
return new
@property
def meta(self):
"""Return a reference to the metadata dictionary. """
return self.__meta
def metaKeys(self):
"""Returns the keys contained in the metadata dictionary
(``dict.keys``).
......
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