Skip to content
Snippets Groups Projects
Commit b48ecb3d authored by Paul McCarthy's avatar Paul McCarthy
Browse files

Initial implementation of Label images. Very rough right now.

parent 0343b735
No related branches found
No related tags found
No related merge requests found
Showing
with 584 additions and 5 deletions
......@@ -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'
......
......@@ -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(
......
......@@ -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
......
#!/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)
......@@ -28,6 +28,9 @@ class ModelOpts(fsldisplay.DisplayOpts):
outline = props.Boolean(default=False)
showName = props.Boolean(default=False)
refImage = props.Choice()
......
......@@ -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
......
......@@ -9,3 +9,4 @@ import glvolume_funcs
import glrgbvector_funcs
import gllinevector_funcs
import glmodel_funcs
import gllabel_funcs
#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;
}
#!/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
#!/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)
......@@ -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
......
......@@ -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.
......
......@@ -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
......
#!/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()
......@@ -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,
......
#!/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')))
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
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