From ed2a830fd9e96eeb5687b5fe721289941fb7b96e Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Thu, 16 Feb 2017 16:54:32 +0000
Subject: [PATCH] Little vertex data cache built into the TriangleMesh class,
 and used by GiftiSurface. Adding comments to confusing model fit code to try
 and make refreshing our memories easier.

---
 fsl/data/featimage.py |  4 ++-
 fsl/data/gifti.py     | 21 +++++++++-------
 fsl/data/mesh.py      | 57 +++++++++++++++++++++++++++++++++++++------
 3 files changed, 64 insertions(+), 18 deletions(-)

diff --git a/fsl/data/featimage.py b/fsl/data/featimage.py
index 27a10f28f..12d2b3af9 100644
--- a/fsl/data/featimage.py
+++ b/fsl/data/featimage.py
@@ -332,7 +332,9 @@ class FEATImage(fslimage.Image):
         # We also take the absolute value
         # of the values in the contrast
         # vector, as the parameter estimates
-        # should be appropriately signed.
+        # should be appropriately signed,
+        # so we don't negative contrast
+        # vector values to invert them.
         contrast = np.array(contrast)
         nonzero  = sum(~np.isclose(contrast, 0))
         contrast = contrast / np.sqrt((contrast ** 2).sum())
diff --git a/fsl/data/gifti.py b/fsl/data/gifti.py
index 3302a974f..97227b4f6 100644
--- a/fsl/data/gifti.py
+++ b/fsl/data/gifti.py
@@ -68,7 +68,9 @@ class GiftiSurface(mesh.TriangleMesh):
 
 
     def loadVertexData(self, dataSource):
-        """Attempts to load data associated with each vertex of this
+        """Overrides the :meth:`.TriangleMesh.loadVertexData` method.
+
+        Attempts to load data associated with each vertex of this
         ``GiftiSurface`` from the given ``dataSource``.
 
         Currently, only the first ``DataArray`` contained in the
@@ -80,15 +82,17 @@ class GiftiSurface(mesh.TriangleMesh):
          - ``*.time.gii``
         """
 
-        # TODO support 4D 
-        # TODO make this more robust
-        vdata = nib.load(dataSource)
-        vdata = vdata.darrays[0].data
+        if dataSource.endswith('.gii'):
 
-        if vdata.size != self.vertices.shape[0]:
-            raise ValueError('Incompatible size: {}'.format(dataSource))
+            # TODO support 4D 
+            # TODO make this more robust
+            vdata = nib.load(dataSource)
+            vdata = vdata.darrays[0].data
+            
+        else:
+            vdata = None
 
-        return vdata
+        return mesh.TriangleMesh.loadVertexData(self, dataSource, vdata)
 
 
 ALLOWED_EXTENSIONS = ['.surf.gii', '.gii']
@@ -161,7 +165,6 @@ def relatedFiles(fname):
     """Given a GIFTI file, returns a list of other GIFTI files in the same
     directory which appear to be related with the given one.  Files which
     share the same prefix are assumed to be related to the given file.
-
     """
 
     try:
diff --git a/fsl/data/mesh.py b/fsl/data/mesh.py
index d9afd3cf2..ee3e0dea5 100644
--- a/fsl/data/mesh.py
+++ b/fsl/data/mesh.py
@@ -63,6 +63,8 @@ class TriangleMesh(object):
 
        getBounds
        loadVertexData
+       getVertexData
+       clearVertexData
     """
 
     
@@ -95,11 +97,12 @@ class TriangleMesh(object):
         if indices is None:
             indices = np.arange(data.shape[0])
 
-        self.vertices = np.array(data)
-        self.indices  = np.array(indices).reshape((-1, 3))
-
-        self.__loBounds = self.vertices.min(axis=0)
-        self.__hiBounds = self.vertices.max(axis=0)
+        self.vertices     = np.array(data)
+        self.indices      = np.array(indices).reshape((-1, 3))
+        
+        self.__vertexData = {}
+        self.__loBounds   = self.vertices.min(axis=0)
+        self.__hiBounds   = self.vertices.max(axis=0)
 
 
     def __repr__(self):
@@ -125,13 +128,51 @@ class TriangleMesh(object):
         return (self.__loBounds, self.__hiBounds)
 
 
-    def loadVertexData(self, dataSource):
+    def loadVertexData(self, dataSource, vertexData=None):
         """Attempts to load scalar data associated with each vertex of this
-        ``TriangleMesh`` from the given ``dataSource``.
+        ``TriangleMesh`` from the given ``dataSource``. The data is returned,
+        and also stored in an internal cache so it can be retrieved later
+        via the :meth:`getVertexData` method.
 
         This method may be overridden by sub-classes.
+
+        :arg dataSource: Path to the vertex data to load
+        :arg vertexData: The vertex data itself, if it has already been
+                         loaded.
         """
-        raise NotImplementedError('Not implemented yet')
+
+        nvertices = self.vertices.shape[0]
+
+        # Currently only white-space delimited
+        # text files are supported
+        if vertexData is None:
+            vertexData = np.loadtxt(dataSource)
+            vertexData.reshape(nvertices, -1)
+
+        if vertexData.shape[0] != nvertices:
+            raise ValueError('Incompatible size: {}'.format(dataSource))
+
+        self.__vertexData[dataSource] = vertexData
+
+        return vertexData
+
+
+    def getVertexData(self, dataSource):
+        """Returns the vertex data for the given ``dataSource`` from the
+        internal vertex data cache. If the given ``dataSource`` is not
+        in the cache, it is loaded via :meth:`loadVertexData`.
+        """
+
+        try:             return self.__vertexData[dataSource]
+        except KeyError: return self.loadVertexData(dataSource)
+
+    
+    def clearVertexData(self):
+        """Clears the internal vertex data cache - see the
+        :meth:`loadVertexData` and :meth:`getVertexData`  methods.
+        """
+
+        self.__vertexData = {}
 
 
 ALLOWED_EXTENSIONS     = ['.vtk']
-- 
GitLab