Commit 99cc593f authored by Paul McCarthy's avatar Paul McCarthy 🚵
Browse files

RF,ENH: Support for GIFTI files with ultiple pointsets, and with vertex

data. Will prpbably need tweaking.
parent e92f083a
...@@ -19,6 +19,7 @@ are available: ...@@ -19,6 +19,7 @@ are available:
GiftiMesh GiftiMesh
loadGiftiMesh loadGiftiMesh
loadGiftiVertexData loadGiftiVertexData
prepareGiftiVertexData
relatedFiles relatedFiles
""" """
...@@ -34,15 +35,13 @@ import fsl.data.constants as constants ...@@ -34,15 +35,13 @@ import fsl.data.constants as constants
import fsl.data.mesh as fslmesh import fsl.data.mesh as fslmesh
# We include '.gii' here because not all surface ALLOWED_EXTENSIONS = ['.gii']
# GIFTIs follow the file suffix convention.
ALLOWED_EXTENSIONS = ['.surf.gii', '.gii']
"""List of file extensions that a file containing Gifti surface data """List of file extensions that a file containing Gifti surface data
is expected to have. is expected to have.
""" """
EXTENSION_DESCRIPTIONS = ['GIFTI surface file', 'GIFTI file'] EXTENSION_DESCRIPTIONS = ['GIFTI file']
"""A description for each of the :data:`ALLOWED_EXTENSIONS`. """ """A description for each of the :data:`ALLOWED_EXTENSIONS`. """
...@@ -60,7 +59,8 @@ class GiftiMesh(fslmesh.Mesh): ...@@ -60,7 +59,8 @@ class GiftiMesh(fslmesh.Mesh):
"""Load the given GIFTI file using ``nibabel``, and extracts surface """Load the given GIFTI file using ``nibabel``, and extracts surface
data using the :func:`loadGiftiMesh` function. data using the :func:`loadGiftiMesh` function.
:arg infile: A GIFTI surface file (``*.surf.gii``). :arg infile: A GIFTI file (``*..gii``) which contains a surface
definition.
:arg fixWinding: Passed through to the :meth:`addVertices` method :arg fixWinding: Passed through to the :meth:`addVertices` method
for the first vertex set. for the first vertex set.
...@@ -76,17 +76,20 @@ class GiftiMesh(fslmesh.Mesh): ...@@ -76,17 +76,20 @@ class GiftiMesh(fslmesh.Mesh):
name = fslpath.removeExt(op.basename(infile), ALLOWED_EXTENSIONS) name = fslpath.removeExt(op.basename(infile), ALLOWED_EXTENSIONS)
infile = op.abspath(infile) infile = op.abspath(infile)
surfimg, vertices, indices = loadGiftiMesh(infile) surfimg, indices, vertices, vdata = loadGiftiMesh(infile)
fslmesh.Mesh.__init__(self, fslmesh.Mesh.__init__(self,
indices, indices,
name=name, name=name,
dataSource=infile) dataSource=infile)
self.addVertices(vertices, infile, fixWinding=fixWinding) for v in vertices:
self.addVertices(v, infile, fixWinding=fixWinding)
self.setMeta(infile, surfimg) self.setMeta(infile, surfimg)
if vdata is not None:
self.addVertexData(infile, vdata)
# Find and load all other # Find and load all other
# surfaces in the same directory # surfaces in the same directory
# as the specfiied one. # as the specfiied one.
...@@ -157,9 +160,10 @@ def loadGiftiMesh(filename): ...@@ -157,9 +160,10 @@ def loadGiftiMesh(filename):
The image is expected to contain the following``<DataArray>`` elements: The image is expected to contain the following``<DataArray>`` elements:
- one comprising ``NIFTI_INTENT_POINTSET`` data (the surface vertices)
- one comprising ``NIFTI_INTENT_TRIANGLE`` data (vertex indices - one comprising ``NIFTI_INTENT_TRIANGLE`` data (vertex indices
defining the triangles). defining the triangles).
- one or more comprising ``NIFTI_INTENT_POINTSET`` data (the surface
vertices)
A ``ValueError`` will be raised if this is not the case. A ``ValueError`` will be raised if this is not the case.
...@@ -169,42 +173,62 @@ def loadGiftiMesh(filename): ...@@ -169,42 +173,62 @@ def loadGiftiMesh(filename):
- The loaded ``nibabel.gifti.GiftiImage`` instance - The loaded ``nibabel.gifti.GiftiImage`` instance
- A ``(N, 3)`` array containing ``N`` vertices. - A ``(M, 3)`` array containing the vertex indices for
- A ``(M, 3))`` array containing the vertex indices for
``M`` triangles. ``M`` triangles.
"""
gimg = nib.load(filename) - A list of at least one ``(N, 3)`` arrays containing ``N``
vertices.
pointsetCode = constants.NIFTI_INTENT_POINTSET - A ``(M, N)`` numpy array containing ``N`` data points for
triangleCode = constants.NIFTI_INTENT_TRIANGLE ``M`` vertices, or ``None`` if the file does not contain
any vertex data.
"""
pointsets = [d for d in gimg.darrays if d.intent == pointsetCode] gimg = nib.load(filename)
triangles = [d for d in gimg.darrays if d.intent == triangleCode]
if len(gimg.darrays) != 2: pscode = constants.NIFTI_INTENT_POINTSET
raise ValueError('{}: GIFTI surface files must contain ' tricode = constants.NIFTI_INTENT_TRIANGLE
'exactly one pointset array and one '
'triangle array'.format(filename))
if len(pointsets) != 1: pointsets = [d for d in gimg.darrays if d.intent == pscode]
raise ValueError('{}: GIFTI surface files must contain ' triangles = [d for d in gimg.darrays if d.intent == tricode]
'exactly one pointset array'.format(filename)) vdata = [d for d in gimg.darrays if d.intent not in (pscode, tricode)]
if len(triangles) != 1: if len(triangles) != 1:
raise ValueError('{}: GIFTI surface files must contain ' raise ValueError('{}: GIFTI surface files must contain '
'exactly one triangle array'.format(filename)) 'exactly one triangle array'.format(filename))
vertices = pointsets[0].data if len(pointsets) == 0:
raise ValueError('{}: GIFTI surface files must contain '
'at least one pointset array'.format(filename))
vertices = [ps.data for ps in pointsets]
indices = triangles[0].data indices = triangles[0].data
return gimg, vertices, indices if len(vdata) == 0: vdata = None
else: vdata = prepareGiftiVertexData(vdata, filename)
return gimg, indices, vertices, vdata
def loadGiftiVertexData(filename): def loadGiftiVertexData(filename):
"""Loads vertex data from the given GIFTI file. """Loads vertex data from the given GIFTI file.
See :func:`prepareGiftiVertexData`.
Returns a tuple containing:
- The loaded ``nibabel.gifti.GiftiImage`` object
- A ``(M, N)`` numpy array containing ``N`` data points for ``M``
vertices
"""
gimg = nib.load(filename)
return gimg, prepareGiftiVertexData(gimg.darrays, filename)
def prepareGiftiVertexData(darrays, filename=None):
"""Prepares vertex data from the given list of GIFTI data arrays.
It is assumed that the given file does not contain any It is assumed that the given file does not contain any
``NIFTI_INTENT_POINTSET`` or ``NIFTI_INTENT_TRIANGLE`` data arrays, and ``NIFTI_INTENT_POINTSET`` or ``NIFTI_INTENT_TRIANGLE`` data arrays, and
which contains either: which contains either:
...@@ -215,17 +239,11 @@ def loadGiftiVertexData(filename): ...@@ -215,17 +239,11 @@ def loadGiftiVertexData(filename):
- One or more ``(M, 1)`` data arrays each containing a single data point - One or more ``(M, 1)`` data arrays each containing a single data point
for ``M`` vertices, and all with the same intent code for ``M`` vertices, and all with the same intent code
Returns a tuple containing: Returns a ``(M, N)`` numpy array containing ``N`` data points for ``M``
vertices.
- The loaded ``nibabel.gifti.GiftiImage`` object
- A ``(M, N)`` numpy array containing ``N`` data points for ``M``
vertices
""" """
gimg = nib.load(filename) intents = set([d.intent for d in darrays])
intents = set([d.intent for d in gimg.darrays])
if len(intents) != 1: if len(intents) != 1:
raise ValueError('{} contains multiple (or no) intents' raise ValueError('{} contains multiple (or no) intents'
...@@ -235,20 +253,19 @@ def loadGiftiVertexData(filename): ...@@ -235,20 +253,19 @@ def loadGiftiVertexData(filename):
if intent in (constants.NIFTI_INTENT_POINTSET, if intent in (constants.NIFTI_INTENT_POINTSET,
constants.NIFTI_INTENT_TRIANGLE): constants.NIFTI_INTENT_TRIANGLE):
raise ValueError('{} contains surface data'.format(filename)) raise ValueError('{} contains surface data'.format(filename))
# Just a single array - return it as-is. # Just a single array - return it as-is.
# n.b. Storing (M, N) data in a single # n.b. Storing (M, N) data in a single
# DataArray goes against the GIFTI spec, # DataArray goes against the GIFTI spec,
# but hey, it happens. # but hey, it happens.
if len(gimg.darrays) == 1: if len(darrays) == 1:
vdata = gimg.darrays[0].data vdata = darrays[0].data
return gimg, vdata.reshape(vdata.shape[0], -1) return vdata.reshape(vdata.shape[0], -1)
# Otherwise extract and concatenate # Otherwise extract and concatenate
# multiple 1-dimensional arrays # multiple 1-dimensional arrays
vdata = [d.data for d in gimg.darrays] vdata = [d.data for d in darrays]
if any([len(d.shape) != 1 for d in vdata]): if any([len(d.shape) != 1 for d in vdata]):
raise ValueError('{} contains one or more non-vector ' raise ValueError('{} contains one or more non-vector '
...@@ -257,7 +274,7 @@ def loadGiftiVertexData(filename): ...@@ -257,7 +274,7 @@ def loadGiftiVertexData(filename):
vdata = np.vstack(vdata).T vdata = np.vstack(vdata).T
vdata = vdata.reshape(vdata.shape[0], -1) vdata = vdata.reshape(vdata.shape[0], -1)
return gimg, vdata return vdata
def relatedFiles(fname, ftypes=None): def relatedFiles(fname, ftypes=None):
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment