From b48ecb3dc9677eb4fd71a6e66db3004a8b41de18 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Mon, 1 Jun 2015 17:30:11 +0100
Subject: [PATCH] Initial implementation of Label images. Very rough right now.

---
 fsl/data/strings.py                           |   1 +
 fsl/fslview/displaycontext/__init__.py        |   1 +
 fsl/fslview/displaycontext/display.py         |   6 +-
 fsl/fslview/displaycontext/labelopts.py       |  32 +++++
 fsl/fslview/displaycontext/modelopts.py       |   3 +
 fsl/fslview/gl/__init__.py                    |   1 +
 fsl/fslview/gl/gl21/__init__.py               |   1 +
 fsl/fslview/gl/gl21/gllabel_frag.glsl         |  83 +++++++++++
 fsl/fslview/gl/gl21/gllabel_funcs.py          |  71 ++++++++++
 fsl/fslview/gl/gllabel.py                     | 133 ++++++++++++++++++
 fsl/fslview/gl/globject.py                    |   4 +-
 fsl/fslview/gl/shaders.py                     |   7 +-
 fsl/fslview/gl/textures/__init__.py           |   1 +
 fsl/fslview/gl/textures/lookuptabletexture.py | 103 ++++++++++++++
 fsl/fslview/layouts.py                        |   8 +-
 fsl/fslview/lookuptables.py                   |  86 +++++++++++
 fsl/fslview/luts/harvard-oxford-cortical.txt  |  48 +++++++
 17 files changed, 584 insertions(+), 5 deletions(-)
 create mode 100644 fsl/fslview/displaycontext/labelopts.py
 create mode 100644 fsl/fslview/gl/gl21/gllabel_frag.glsl
 create mode 100644 fsl/fslview/gl/gl21/gllabel_funcs.py
 create mode 100644 fsl/fslview/gl/gllabel.py
 create mode 100644 fsl/fslview/gl/textures/lookuptabletexture.py
 create mode 100644 fsl/fslview/lookuptables.py
 create mode 100644 fsl/fslview/luts/harvard-oxford-cortical.txt

diff --git a/fsl/data/strings.py b/fsl/data/strings.py
index 047a8c0b4..aa6b48b08 100644
--- a/fsl/data/strings.py
+++ b/fsl/data/strings.py
@@ -316,6 +316,7 @@ choices = TypeDict({
 
     'Display.overlayType.volume'     : '3D/4D volume',
     'Display.overlayType.mask'       : '3D/4D mask image',
+    'Display.overlayType.label'      : 'Label image',
     'Display.overlayType.rgbvector'  : '3-direction vector image (RGB)',
     'Display.overlayType.linevector' : '3-direction vector image (Line)',
     'Display.overlayType.model'      : '3D model' 
diff --git a/fsl/fslview/displaycontext/__init__.py b/fsl/fslview/displaycontext/__init__.py
index 0cad30f68..a70f549b0 100644
--- a/fsl/fslview/displaycontext/__init__.py
+++ b/fsl/fslview/displaycontext/__init__.py
@@ -20,6 +20,7 @@ from maskopts       import MaskOpts
 from vectoropts     import VectorOpts
 from vectoropts     import LineVectorOpts
 from modelopts      import ModelOpts
+from labelopts      import LabelOpts
 
 
 ALL_OVERLAY_TYPES = list(set(
diff --git a/fsl/fslview/displaycontext/display.py b/fsl/fslview/displaycontext/display.py
index 534ac88e2..5fd7329a4 100644
--- a/fsl/fslview/displaycontext/display.py
+++ b/fsl/fslview/displaycontext/display.py
@@ -244,12 +244,13 @@ class Display(props.SyncableHasProperties):
 import volumeopts
 import vectoropts
 import maskopts
+import labelopts
 import modelopts
 
 
 OVERLAY_TYPES = td.TypeDict({
 
-    'Image' : ['volume', 'mask', 'rgbvector', 'linevector'],
+    'Image' : ['volume', 'mask', 'rgbvector', 'linevector', 'label'],
     'Model' : ['model']
 })
 """This dictionary provides a mapping between the overlay classes, and
@@ -265,7 +266,8 @@ DISPLAY_OPTS_MAP = {
     'rgbvector'  : vectoropts.VectorOpts,
     'linevector' : vectoropts.LineVectorOpts,
     'mask'       : maskopts.  MaskOpts,
-    'model'      : modelopts. ModelOpts
+    'model'      : modelopts. ModelOpts,
+    'label'      : labelopts. LabelOpts,
 }
 """This dictionary provides a mapping between each overlay type, and
 the :class:`DisplayOpts` subclass which contains overlay type-specific
diff --git a/fsl/fslview/displaycontext/labelopts.py b/fsl/fslview/displaycontext/labelopts.py
new file mode 100644
index 000000000..f644d1587
--- /dev/null
+++ b/fsl/fslview/displaycontext/labelopts.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+#
+# labelopts.py -
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+import props
+
+import volumeopts
+
+import fsl.fslview.lookuptables as lookuptables
+
+
+class LabelOpts(volumeopts.ImageOpts):
+
+    
+    lut       = props.Choice()
+    outline   = props.Boolean(default=False)
+    showNames = props.Boolean(default=False)
+
+
+    def __init__(self, overlay, *args, **kwargs):
+
+        luts  = lookuptables.all_luts
+        names = [lut.lutName() for lut in luts]
+
+        self.getProp('lut').setChoices(luts, names, self)
+
+        self.lut = luts[0]
+
+        volumeopts.ImageOpts.__init__(self, overlay, *args, **kwargs)
diff --git a/fsl/fslview/displaycontext/modelopts.py b/fsl/fslview/displaycontext/modelopts.py
index 530d4c94e..254bf1b2a 100644
--- a/fsl/fslview/displaycontext/modelopts.py
+++ b/fsl/fslview/displaycontext/modelopts.py
@@ -28,6 +28,9 @@ class ModelOpts(fsldisplay.DisplayOpts):
     
     outline    = props.Boolean(default=False)
 
+
+    showName   = props.Boolean(default=False)
+
     
     refImage   = props.Choice()
 
diff --git a/fsl/fslview/gl/__init__.py b/fsl/fslview/gl/__init__.py
index d0f7e3b21..a35cbbbe1 100644
--- a/fsl/fslview/gl/__init__.py
+++ b/fsl/fslview/gl/__init__.py
@@ -212,6 +212,7 @@ def bootstrap(glVersion=None):
     thismod.glrgbvector_funcs  = glpkg.glrgbvector_funcs
     thismod.gllinevector_funcs = glpkg.gllinevector_funcs
     thismod.glmodel_funcs      = glpkg.glmodel_funcs
+    thismod.gllabel_funcs      = glpkg.gllabel_funcs
     thismod._bootstrapped      = True
 
 
diff --git a/fsl/fslview/gl/gl21/__init__.py b/fsl/fslview/gl/gl21/__init__.py
index 5e1427459..2c0843a8b 100644
--- a/fsl/fslview/gl/gl21/__init__.py
+++ b/fsl/fslview/gl/gl21/__init__.py
@@ -9,3 +9,4 @@ import glvolume_funcs
 import glrgbvector_funcs
 import gllinevector_funcs
 import glmodel_funcs
+import gllabel_funcs
diff --git a/fsl/fslview/gl/gl21/gllabel_frag.glsl b/fsl/fslview/gl/gl21/gllabel_frag.glsl
new file mode 100644
index 000000000..d5eb3a682
--- /dev/null
+++ b/fsl/fslview/gl/gl21/gllabel_frag.glsl
@@ -0,0 +1,83 @@
+#version 120
+
+#pragma include spline_interp.glsl
+#pragma include test_in_bounds.glsl
+
+uniform sampler3D imageTexture;
+
+uniform sampler1D lutTexture;
+
+uniform mat4      voxValXform;
+
+uniform vec3      imageShape;
+
+uniform float     numLabels;
+
+uniform bool      useSpline;
+
+uniform bool      outline;
+
+varying vec3      fragVoxCoord;
+
+varying vec3      fragTexCoord;
+
+
+void main(void) {
+
+    vec3 voxCoord = fragVoxCoord;
+
+    if (!test_in_bounds(voxCoord, imageShape)) {
+        
+        gl_FragColor = vec4(0, 0, 0, 0);
+        return;
+    }
+
+    float voxValue;
+    if (useSpline) voxValue = spline_interp(imageTexture,
+                                            fragTexCoord,
+                                            imageShape,
+                                            0);
+    else           voxValue = texture3D(    imageTexture,
+                                            fragTexCoord).r;
+
+    float lutCoord = (voxValXform * vec4(voxValue, 0, 0, 1)).x / numLabels;
+    vec4 colour    = texture1D(lutTexture, lutCoord);
+
+    if (outline) {
+
+        // TODO take into account resolution
+        vec3 off = 0.1 / imageShape;
+
+        float left   = texture3D(imageTexture,
+                                 fragTexCoord + vec3(-off.x, 0,      0))    .r;
+        float right  = texture3D(imageTexture,
+                                 fragTexCoord + vec3( off.x, 0,      0))    .r;
+        float top    = texture3D(imageTexture,
+                                 fragTexCoord + vec3( 0,     off.y,  0))    .r;
+        float bottom = texture3D(imageTexture,
+                                 fragTexCoord + vec3( 0,    -off.y,  0))    .r;
+        float back   = texture3D(imageTexture,
+                                 fragTexCoord + vec3( 0,     0,     -off.z)).r;
+        float front  = texture3D(imageTexture,
+                                 fragTexCoord + vec3( 0,     0,      off.z)).r;
+
+
+        voxValue = (voxValXform * vec4(voxValue, 0, 0, 1)).x;
+        left     = (voxValXform * vec4(left,     0, 0, 1)).x;
+        right    = (voxValXform * vec4(right,    0, 0, 1)).x;
+        top      = (voxValXform * vec4(top,      0, 0, 1)).x;
+        bottom   = (voxValXform * vec4(bottom,   0, 0, 1)).x;
+        back     = (voxValXform * vec4(back,     0, 0, 1)).x;
+        front    = (voxValXform * vec4(front,    0, 0, 1)).x;
+
+        if (abs(voxValue - top)    < 0.001 &&
+            abs(voxValue - bottom) < 0.001 &&
+            abs(voxValue - left)   < 0.001 &&
+            abs(voxValue - right)  < 0.001 &&
+            abs(voxValue - back)   < 0.001 &&
+            abs(voxValue - front)  < 0.001)
+          colour.a = 0.0;
+    }
+
+    gl_FragColor = colour;
+}
diff --git a/fsl/fslview/gl/gl21/gllabel_funcs.py b/fsl/fslview/gl/gl21/gllabel_funcs.py
new file mode 100644
index 000000000..8a7e4dbf9
--- /dev/null
+++ b/fsl/fslview/gl/gl21/gllabel_funcs.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+#
+# gllabel_funcs.py -
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+import OpenGL.GL              as gl
+import numpy                  as np
+
+import fsl.fslview.gl.shaders as shaders
+import                           glvolume_funcs
+
+
+def compileShaders(self):
+
+    vertShaderSrc = shaders.getVertexShader(  self,
+                                              sw=self.display.softwareMode)
+    fragShaderSrc = shaders.getFragmentShader(self,
+                                              sw=self.display.softwareMode)
+    self.shaders = shaders.compileShaders(vertShaderSrc, fragShaderSrc)
+
+    self.vertexPos       = gl.glGetAttribLocation( self.shaders,
+                                                   'vertex')
+    self.voxCoordPos     = gl.glGetAttribLocation( self.shaders,
+                                                   'voxCoord')
+    self.texCoordPos     = gl.glGetAttribLocation( self.shaders,
+                                                   'texCoord') 
+    self.imageTexturePos = gl.glGetUniformLocation(self.shaders,
+                                                   'imageTexture')
+    self.lutTexturePos   = gl.glGetUniformLocation(self.shaders,
+                                                   'lutTexture')
+    self.voxValXformPos  = gl.glGetUniformLocation(self.shaders,
+                                                   'voxValXform') 
+    self.imageShapePos   = gl.glGetUniformLocation(self.shaders,
+                                                   'imageShape')
+    self.useSplinePos    = gl.glGetUniformLocation(self.shaders,
+                                                   'useSpline')
+    self.numLabelsPos    = gl.glGetUniformLocation(self.shaders,
+                                                   'numLabels')
+    self.outlinePos      = gl.glGetUniformLocation(self.shaders,
+                                                   'outline')
+
+
+def updateShaderState(self):
+
+    display = self.display
+    opts    = self.displayOpts
+
+    gl.glUseProgram(self.shaders)
+
+
+    gl.glUniform1f( self.outlinePos,       opts.outline)
+    gl.glUniform1f( self.numLabelsPos,     64)
+    gl.glUniform1f( self.useSplinePos,     display.interpolation == 'spline')
+    gl.glUniform3fv(self.imageShapePos, 1, np.array(self.image.shape[:3],
+                                                     dtype=np.float32))
+    
+    vvx = self.imageTexture.voxValXform.ravel('C')
+    gl.glUniformMatrix4fv(self.voxValXformPos, 1, False, vvx)
+
+    gl.glUniform1i(self.imageTexturePos, 0)
+    gl.glUniform1i(self.lutTexturePos,   1) 
+
+    gl.glUseProgram(0)
+
+
+preDraw  = glvolume_funcs.preDraw
+draw     = glvolume_funcs.draw
+drawAll  = glvolume_funcs.drawAll
+postDraw = glvolume_funcs.postDraw
diff --git a/fsl/fslview/gl/gllabel.py b/fsl/fslview/gl/gllabel.py
new file mode 100644
index 000000000..f50fa5bed
--- /dev/null
+++ b/fsl/fslview/gl/gllabel.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+#
+# gllabel.py -
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+import OpenGL.GL           as gl
+
+import fsl.utils.transform as transform
+import fsl.fslview.gl      as fslgl
+import resources           as glresources
+import routines            as glroutines
+import                        globject
+import                        textures
+
+
+class GLLabel(globject.GLImageObject):
+
+    
+    def __init__(self, image, display):
+
+        globject.GLImageObject.__init__(self, image, display)
+
+
+        imageTexName = '{}_{}' .format(type(self).__name__, id(image))
+        lutTexName   = '{}_lut'.format(self.name)
+
+        self.lutTexture   = textures.LookupTableTexture(lutTexName)
+        self.imageTexture = glresources.get(
+            imageTexName,
+            textures.ImageTexture,
+            imageTexName,
+            image,
+            display)
+
+        self.vertexAttrBuffer = gl.glGenBuffers(1)
+
+        fslgl.gllabel_funcs.compileShaders(   self)
+        fslgl.gllabel_funcs.updateShaderState(self)
+        
+        self.refreshLutTexture()
+
+        self.addListeners()
+
+
+    def addListeners(self):
+
+        display = self.display
+        opts    = self.displayOpts
+
+        def shaderUpdate(*a):
+            fslgl.gllabel_funcs.updateShaderState(self)
+            self.onUpdate()
+
+        def lutUpdate(*a):
+            self.refreshLutTexture()
+            self.onUpdate()
+
+        opts   .addListener('outline',    self.name, shaderUpdate)
+        opts   .addListener('lut',        self.name, lutUpdate)
+        display.addListener('alpha',      self.name, lutUpdate)
+        display.addListener('brightness', self.name, lutUpdate)
+        display.addListener('contrast',   self.name, lutUpdate)
+
+
+    def removeListeners(self):
+        display = self.display
+        opts    = self.displayOpts
+
+        opts   .removeListener('outline',    self.name)
+        opts   .removeListener('lut',        self.name)
+        display.removeListener('alpha',      self.name)
+        display.removeListener('brightness', self.name)
+        display.removeListener('contrast',   self.name) 
+
+    
+    def destroy(self):
+
+        glresources.delete(self.imageTexture.getTextureName())
+        self.lutTexture.destroy()
+
+        self.removeListeners()
+        fslgl.gllabel_funcs.destroy(self)
+
+        
+    def setAxes(self, xax, yax):
+        """This method should be called when the image display axes change."""
+        
+        self.xax = xax
+        self.yax = yax
+        self.zax = 3 - xax - yax
+
+        
+    def generateVertices(self, zpos, xform):
+        vertices, voxCoords, texCoords = glroutines.slice2D(
+            self.image.shape[:3],
+            self.xax,
+            self.yax,
+            zpos, 
+            self.displayOpts.getTransform('voxel',   'display'),
+            self.displayOpts.getTransform('display', 'voxel'))
+
+        if xform is not None: 
+            vertices = transform.transform(vertices, xform)
+
+        return vertices, voxCoords, texCoords 
+
+
+    def refreshLutTexture(self, *a):
+
+        display = self.display
+        opts    = self.displayOpts
+
+        self.lutTexture.set(alpha=display.alpha / 100.0,
+                            lut=opts.lut)
+
+        
+    def preDraw(self):
+
+        self.imageTexture.bindTexture(gl.GL_TEXTURE0)
+        self.lutTexture  .bindTexture(gl.GL_TEXTURE1)
+        fslgl.gllabel_funcs.preDraw(self)
+
+    
+    def draw(self, zpos, xform=None):
+        fslgl.gllabel_funcs.draw(self, zpos, xform)
+
+
+    def postDraw(self):
+        self.imageTexture.unbindTexture()
+        self.lutTexture  .unbindTexture()
+        fslgl.gllabel_funcs.postDraw(self)
diff --git a/fsl/fslview/gl/globject.py b/fsl/fslview/gl/globject.py
index 8466cd6c0..41dea3119 100644
--- a/fsl/fslview/gl/globject.py
+++ b/fsl/fslview/gl/globject.py
@@ -240,6 +240,7 @@ import glmask
 import glrgbvector
 import gllinevector
 import glmodel
+import gllabel
 
 
 GLOBJECT_OVERLAY_TYPE_MAP = {
@@ -247,7 +248,8 @@ GLOBJECT_OVERLAY_TYPE_MAP = {
     'mask'       : glmask      .GLMask,
     'rgbvector'  : glrgbvector .GLRGBVector,
     'linevector' : gllinevector.GLLineVector,
-    'model'      : glmodel     .GLModel
+    'model'      : glmodel     .GLModel,
+    'label'      : gllabel     .GLLabel
 }
 """This dictionary provides a mapping between all available overlay types (see
 the :attr:`.Display.overlayType` property), and the :class:`GLObject` subclass
diff --git a/fsl/fslview/gl/shaders.py b/fsl/fslview/gl/shaders.py
index fc4d01e14..7b966a37c 100644
--- a/fsl/fslview/gl/shaders.py
+++ b/fsl/fslview/gl/shaders.py
@@ -37,6 +37,11 @@ _shaderTypePrefixMap = td.TypeDict({
     ('GLVolume',     'vert', True)  : 'glvolume_sw',
     ('GLVolume',     'frag', False) : 'glvolume',
     ('GLVolume',     'frag', True)  : 'glvolume_sw',
+
+    ('GLLabel',      'vert', False) : 'glvolume',
+    ('GLLabel',      'vert', True)  : 'glvolume_sw', 
+    ('GLLabel',      'frag', False) : 'gllabel',
+    ('GLLabel',      'frag', True)  : 'gllabel_sw', 
     
     ('GLRGBVector',  'vert', False) : 'glvolume',
     ('GLRGBVector',  'vert', True)  : 'glvolume_sw',
@@ -53,7 +58,7 @@ _shaderTypePrefixMap = td.TypeDict({
     ('GLModel',      'vert', False) : 'glmodel',
     ('GLModel',      'vert', True)  : 'glmodel',
     ('GLModel',      'frag', False) : 'glmodel',
-    ('GLModel',      'frag', True)  : 'glmodel', 
+    ('GLModel',      'frag', True)  : 'glmodel',
 })
 """This dictionary provides a mapping between :class:`.GLObject` types,
 and file name prefixes, identifying the shader programs to use.
diff --git a/fsl/fslview/gl/textures/__init__.py b/fsl/fslview/gl/textures/__init__.py
index a10bec9ac..8ddaf9bc7 100644
--- a/fsl/fslview/gl/textures/__init__.py
+++ b/fsl/fslview/gl/textures/__init__.py
@@ -19,6 +19,7 @@ from texture            import Texture
 from texture            import Texture2D
 from imagetexture       import ImageTexture
 from colourmaptexture   import ColourMapTexture
+from lookuptabletexture import LookupTableTexture
 from selectiontexture   import SelectionTexture
 from rendertexture      import RenderTexture
 from rendertexture      import GLObjectRenderTexture
diff --git a/fsl/fslview/gl/textures/lookuptabletexture.py b/fsl/fslview/gl/textures/lookuptabletexture.py
new file mode 100644
index 000000000..8cb2ec93f
--- /dev/null
+++ b/fsl/fslview/gl/textures/lookuptabletexture.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+#
+# lookuptabletexture.py -
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+import logging
+
+import OpenGL.GL as gl
+import numpy     as np
+
+import texture
+
+
+log = logging.getLogger(__name__)
+
+
+class LookupTableTexture(texture.Texture):
+
+    def __init__(self, name):
+        
+        texture.Texture.__init__(self, name, 1)
+        
+        self.__lut    = None
+        self.__alpha  = None
+        self.__border = None
+
+
+    def set(self, **kwargs):
+
+        lut    = kwargs.get('lut',    self)
+        alpha  = kwargs.get('alpha',  self)
+        border = kwargs.get('border', self)
+
+        if lut    is not self: self.__lut    = lut
+        if alpha  is not self: self.__alpha  = alpha
+        if border is not self: self.__border = border
+
+        self.__refresh()
+
+
+    def refresh(self):
+        self.__refresh()
+
+        
+    def __refresh(self):
+
+        alpha  = self.__alpha
+        border = self.__border
+
+        # TODO check maximum lut label value,
+        #      and use corresponding number 
+        #      of slots in colour map
+        data   = np.zeros((64, 4), dtype=np.uint8)
+
+        if self.__lut is not None:
+            values  = self.__lut.values()
+            colours = self.__lut.colours()
+
+            for value, colour in zip(values, colours):
+
+                data[value, :3] = [np.floor(c * 255) for c in colour]
+
+                if alpha is not None: data[value, 3] = alpha * 255
+                else:                 data[value, 3] = 255
+
+
+        data = data.ravel('C')
+
+        self.bindTexture()
+
+        if border is not None:
+            if alpha is not None:
+                border[3] = alpha * 255
+                
+            gl.glTexParameterfv(gl.GL_TEXTURE_1D,
+                                gl.GL_TEXTURE_BORDER_COLOR,
+                                border)
+            gl.glTexParameteri( gl.GL_TEXTURE_1D,
+                                gl.GL_TEXTURE_WRAP_S,
+                                gl.GL_CLAMP_TO_BORDER) 
+        else:
+            gl.glTexParameteri(gl.GL_TEXTURE_1D,
+                               gl.GL_TEXTURE_WRAP_S,
+                               gl.GL_CLAMP_TO_EDGE)
+
+        gl.glTexParameteri(gl.GL_TEXTURE_1D,
+                           gl.GL_TEXTURE_MAG_FILTER,
+                           gl.GL_NEAREST)
+        gl.glTexParameteri(gl.GL_TEXTURE_1D,
+                           gl.GL_TEXTURE_MIN_FILTER,
+                           gl.GL_NEAREST) 
+
+        gl.glTexImage1D(gl.GL_TEXTURE_1D,
+                        0,
+                        gl.GL_RGBA8,
+                        64,
+                        0,
+                        gl.GL_RGBA,
+                        gl.GL_UNSIGNED_BYTE,
+                        data)
+        self.unbindTexture()
diff --git a/fsl/fslview/layouts.py b/fsl/fslview/layouts.py
index 325bbf729..6e2242bba 100644
--- a/fsl/fslview/layouts.py
+++ b/fsl/fslview/layouts.py
@@ -32,6 +32,7 @@ from fsl.fslview.displaycontext            import MaskOpts
 from fsl.fslview.displaycontext            import VectorOpts
 from fsl.fslview.displaycontext            import LineVectorOpts
 from fsl.fslview.displaycontext            import ModelOpts
+from fsl.fslview.displaycontext            import LabelOpts
 
 from fsl.fslview.displaycontext            import SceneOpts
 from fsl.fslview.displaycontext            import OrthoOpts
@@ -171,7 +172,11 @@ ModelOptsToolBarLayout = [
     widget(ModelOpts, 'colour'),
     widget(ModelOpts, 'outline'),
     widget(ModelOpts, 'refImage'),
-    actions.ActionButton(OverlayDisplayToolBar, 'more')] 
+    actions.ActionButton(OverlayDisplayToolBar, 'more')]
+
+LabelOptsToolBarLayout = [
+    widget(LabelOpts, 'lut'),
+    widget(LabelOpts, 'outline')]
 
 
 DisplayLayout = props.VGroup(
@@ -277,6 +282,7 @@ layouts = td.TypeDict({
     ('OverlayDisplayToolBar', 'MaskOpts')       : MaskOptsToolBarLayout,
     ('OverlayDisplayToolBar', 'VectorOpts')     : VectorOptsToolBarLayout,
     ('OverlayDisplayToolBar', 'ModelOpts')      : ModelOptsToolBarLayout,
+    ('OverlayDisplayToolBar', 'LabelOpts')      : LabelOptsToolBarLayout,
 
     ('OverlayDisplayPanel',   'Display')        : DisplayLayout,
     ('OverlayDisplayPanel',   'VolumeOpts')     : VolumeOptsLayout,
diff --git a/fsl/fslview/lookuptables.py b/fsl/fslview/lookuptables.py
new file mode 100644
index 000000000..e433efff6
--- /dev/null
+++ b/fsl/fslview/lookuptables.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+#
+# lookuptables.py -
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+
+class LookupTable(object):
+
+    
+    def __init__(self, lutName):
+        self.__lutName = lutName
+        self.__names   = {}
+        self.__colours = {}
+
+
+    def __len__(self):
+        return len(self.__names.keys())
+
+        
+    def lutName(self):
+        return self.__lutName
+
+
+    def values(self):
+        return sorted(self.__names.keys())
+
+
+    def names(self):
+        return [self.__names[v] for v in self.values()]
+
+    
+    def colours(self):
+        return [self.__colours[v] for v in self.values()]
+
+    
+    def name(self, value):
+        return self.__names[value]
+
+        
+    def colour(self, value):
+        return self.__colours[value]
+
+
+    def set(self, value, name, colour):
+
+        # At the moment, we are restricting
+        # lookup tables to be unsigned 16 bit.
+        # See gl/textures/lookuptabletexture.py
+        if not isinstance(value, (int, long)) or \
+           value < 0 or value > 65535:
+            raise ValueError('Lookup table values must be '
+                             '16 bit unsigned integers.')
+          
+        self.__names[  value] = name
+        self.__colours[value] = colour
+
+
+    def load(self, lutFile):
+        
+        with open(lutFile, 'rt') as f:
+            lines = f.readlines()
+
+            for line in lines:
+                tkns = line.split()
+
+                label = int(     tkns[0])
+                r     = float(   tkns[1])
+                g     = float(   tkns[2])
+                b     = float(   tkns[3])
+                lName = ' '.join(tkns[4:])
+
+                self.set(label, lName, (r, g, b))
+
+        return self
+
+
+import os.path as op
+
+all_luts = []
+dirname  = op.join(op.dirname(__file__), 'luts')
+
+all_luts.append(
+    LookupTable('Harvard-Oxford').load(
+        op.join(dirname, 'harvard-oxford-cortical.txt')))
diff --git a/fsl/fslview/luts/harvard-oxford-cortical.txt b/fsl/fslview/luts/harvard-oxford-cortical.txt
new file mode 100644
index 000000000..8863dc889
--- /dev/null
+++ b/fsl/fslview/luts/harvard-oxford-cortical.txt
@@ -0,0 +1,48 @@
+1  0.00000 0.93333 0.00000 Frontal Pole
+2  0.62745 0.32157 0.17647 Insular Cortex
+3  1.00000 0.85490 0.72549 Superior Frontal Gyrus
+4  0.00000 0.80784 0.81961 Middle Frontal Gyrus
+5  0.49804 1.00000 0.83137 Inferior Frontal Gyrus, pars triangularis
+6  0.69804 0.13333 0.13333 Inferior Frontal Gyrus, pars opercularis
+7  0.93333 0.00000 0.00000 Precentral Gyrus
+8  0.13333 0.54510 0.13333 Temporal Pole
+9  0.81569 0.12549 0.56471 Superior Temporal Gyrus, anterior division
+10 0.67843 1.00000 0.18431 Superior Temporal Gyrus, posterior division
+11 0.94118 0.90196 0.54902 Middle Temporal Gyrus, anterior division
+12 0.67843 0.84706 0.90196 Middle Temporal Gyrus, posterior division
+13 0.93333 0.93333 0.00000 Middle Temporal Gyrus, temporooccipital part
+14 0.19608 0.80392 0.19608 Inferior Temporal Gyrus, anterior division
+15 1.00000 0.00000 1.00000 Inferior Temporal Gyrus, posterior division
+16 0.69020 0.18824 0.37647 Inferior Temporal Gyrus, temporooccipital part
+17 0.00000 1.00000 0.49804 Postcentral Gyrus
+18 0.96078 0.87059 0.70196 Superior Parietal Lobule
+19 1.00000 0.64706 0.00000 Supramarginal Gyrus, anterior division
+20 1.00000 0.27059 0.00000 Supramarginal Gyrus, posterior division
+21 0.80392 0.35686 0.27059 Angular Gyrus
+22 1.00000 0.75294 0.79608 Lateral Occipital Cortex, superior division
+23 0.59608 0.98431 0.59608 Lateral Occipital Cortex, inferior division
+24 0.39216 0.58431 0.92941 Intracalcarine Cortex
+25 0.62745 0.12549 0.94118 Frontal Medial Cortex
+26 0.93333 0.50980 0.93333 Juxtapositional Lobule Cortex (formerly Supplementary Motor Cortex)
+27 0.93333 0.78824 0.00000 Subcallosal Cortex
+28 0.85490 0.43922 0.83922 Paracingulate Gyrus
+29 1.00000 0.24314 0.58824 Cingulate Gyrus, anterior division
+30 0.00000 0.00000 1.00000 Cingulate Gyrus, posterior division
+31 0.15294 0.25098 0.54510 Precuneous Cortex
+32 0.98039 0.50196 0.44706 Cuneal Cortex
+33 1.00000 0.43137 0.70588 Frontal Orbital Cortex
+34 1.00000 0.38824 0.27843 Parahippocampal Gyrus, anterior division
+35 1.00000 1.00000 0.00000 Parahippocampal Gyrus, posterior division
+36 0.00000 0.39216 0.00000 Lingual Gyrus
+37 0.80392 0.36078 0.36078 Temporal Fusiform Cortex, anterior division
+38 0.64706 0.16471 0.16471 Temporal Fusiform Cortex, posterior division
+39 0.60000 0.19608 0.80000 Temporal Occipital Fusiform Cortex
+40 0.00000 1.00000 1.00000 Occipital Fusiform Gyrus
+41 0.86667 0.62745 0.86667 Frontal Operculum Cortex
+42 0.52941 0.80784 0.92157 Central Opercular Cortex
+43 0.82353 0.70588 0.54902 Parietal Operculum Cortex
+44 1.00000 0.84314 0.00000 Planum Polare
+45 0.00000 0.00000 0.50196 Heschl's Gyrus (includes H1 and H2)
+46 0.18039 0.54510 0.34118 Planum Temporale
+47 0.40000 0.80392 0.66667 Supracalcarine Cortex
+48 0.00000 1.00000 0.00000 Occipital Pole
-- 
GitLab