#
# glrgbvector.py - The GLRGBVector class.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This mdoule provides the :class:`GLRGBVector` class, for displaying 3D
vector :class:`.Image` overlays in RGB mode.
"""
import numpy as np
import OpenGL.GL as gl
import fsl.data.dtifit as dtifit
import fsleyes.gl as fslgl
import fsleyes.gl.routines as glroutines
import fsleyes.gl.glvector as glvector
[docs]class GLRGBVector(glvector.GLVector):
"""The ``GLRGBVector`` class encapsulates the logic required to render a
``x*y*z*3`` :class:`.Image` instance as a vector image, where the
direction of the vector at each voxel is represented by a combination of
three colours (one colour per axis). The ``GLRGBVector`` class assumes
that the :class:`.Display` instance associated with the ``Image`` overlay
holds a reference to a :class:`.RGBVectorOpts` instance, which contains
``GLRGBVector``-specific display settings. The ``GLRGBVector`` is a
sub-class of the :class:`.GLVector` class, and uses the functionality
provided by ``GLVector``.
A ``GLRGBVector`` can only show the magnitude of a vector, not its
orientation. Therefore, the absolute values of the :class:`.Image`
instance are stored in the :class:`.ImageTexture`. This is accomplished
by passing a ``prefilter`` function to :meth:`.GLVector.__init__`, which
forces the image values to be unsigned.
The ``GLRGBVector`` uses two OpenGL version-specific modules, the
:mod:`.gl14.glrgbvector_funcs` and :mod:`.gl21.glrgbvector_funcs` modules,
to manage the vertex/fragment shader programs that are used in rendering.
These modules are assumed to provide the following functions:
========================================== ===============================
``init(GLRGBVector)`` Perform any necessary
initialisation.
``destroy(GLRGBVector)`` Perform any necessary clean up.
``compileShaders(GLRGBVector)`` Compiles vertex/fragment
shaders.
``updateShaderState(GLRGBVector)`` Updates vertex/fragment
shaders.
``preDraw(GLRGBVector, xform, bbox)`` Prepare the GL state for
drawing.
``draw2D(GLRGBVector, zpos, xform, bbox)`` Draw the slice specified by
``zpos``.
``draw3D(GLRGBVector, zpos, xform)`` Draw the volume in 3D
``drawAll(GLRGBVector, zposes, xforms)`` Draw all slices specified by
``zposes``.
``postDraw(GLRGBVector, xform, bbox)`` Clean up the GL state after
drawing.
========================================== ===============================
"""
[docs] def __init__(self, image, overlayList, displayCtx, canvas, threedee):
"""Create a ``GLRGBVector``.
:arg image: An :class:`.Image` or :class:`.DTIFitTensor`
instance.
:arg overlayList: The :class:`.OverlayList`
:arg displayCtx: The :class:`.DisplayContext` managing the scene.
:arg canvas: The canvas doing the drawing.
:arg threedee: 2D or 3D rendering
"""
# If the overlay is a DTIFitTensor, use the
# V1 image is the vector data. Otherwise,
# assume that the overlay is the vector image.
if isinstance(image, dtifit.DTIFitTensor): vecImage = image.V1()
else: vecImage = image
def prefilter(data):
# make absolute and scale to
# unit length if required
data = np.abs(data)
if self.opts.unitLength:
with np.errstate(invalid='ignore'):
x = data[0, ...]
y = data[1, ...]
z = data[2, ...]
lens = np.sqrt(x ** 2 + y ** 2 + z ** 2)
data[0, ...] = x / lens
data[1, ...] = y / lens
data[2, ...] = z / lens
return data
def prefilterRange(dmin, dmax):
if self.unitLength:
return 0, 1
else:
return max((0, dmin)), max((abs(dmin), abs(dmax)))
glvector.GLVector.__init__(self,
image,
overlayList,
displayCtx,
canvas,
threedee,
prefilter=prefilter,
prefilterRange=prefilterRange,
vectorImage=vecImage,
init=lambda: fslgl.glrgbvector_funcs.init(
self))
self.opts.addListener('interpolation',
self.name,
self.__interpChanged)
self.opts.addListener('unitLength',
self.name,
self.__unitLengthChanged)
[docs] def destroy(self):
"""Must be called when this ``GLRGBVector`` is no longer needed.
Removes some property listeners from the :class:`.RGBVectorOpts`
instance, calls the OpenGL version-specific ``destroy``
function, and calls the :meth:`.GLVector.destroy` method.
"""
self.opts.removeListener('interpolation', self.name)
self.opts.removeListener('unitLength', self.name)
fslgl.glrgbvector_funcs.destroy(self)
glvector.GLVector.destroy(self)
[docs] def refreshImageTexture(self):
"""Overrides :meth:`.GLVector.refreshImageTexture`. Calls the base
class implementation.
"""
opts = self.opts
if opts.interpolation == 'none': interp = gl.GL_NEAREST
else: interp = gl.GL_LINEAR
glvector.GLVector.refreshImageTexture(self, interp)
[docs] def refreshAuxTexture(self, which):
"""Overrides :meth:`.GLVector.refreshAuxTexture`. Calls the base
class implementation.
"""
opts = self.opts
if opts.interpolation == 'none': interp = gl.GL_NEAREST
else: interp = gl.GL_LINEAR
glvector.GLVector.refreshAuxTexture(self, which, interp)
def __interpChanged(self, *a):
"""Called when the :attr:`.RGBVectorOpts.interpolation` property
changes. Updates the :class:`.ImageTexture` interpolation.
"""
opts = self.opts
if opts.interpolation == 'none': interp = gl.GL_NEAREST
else: interp = gl.GL_LINEAR
self.imageTexture .set(interp=interp)
self.modulateTexture.set(interp=interp)
self.clipTexture .set(interp=interp)
self.colourTexture .set(interp=interp)
self.asyncUpdateShaderState(alwaysNotify=True)
def __unitLengthChanged(self, *a):
"""Called when :attr:`.RGBVectorOpts.unitLength` changes. Refreshes
the texture data.
"""
self.imageTexture.refresh()
[docs] def compileShaders(self):
"""Overrides :meth:`.GLVector.compileShaders`. Calls the OpenGL
version-specific ``compileShaders`` function.
"""
fslgl.glrgbvector_funcs.compileShaders(self)
[docs] def updateShaderState(self):
"""Overrides :meth:`.GLVector.compileShaders`. Calls the OpenGL
version-specific ``updateShaderState`` function.
"""
return fslgl.glrgbvector_funcs.updateShaderState(self)
[docs] def preDraw(self, xform=None, bbox=None):
"""Overrides :meth:`.GLVector.preDraw`. Calls the base class
implementation, and the OpenGL version-specific ``preDraw`` function.
"""
glvector.GLVector.preDraw(self, xform, bbox)
fslgl.glrgbvector_funcs.preDraw(self, xform, bbox)
[docs] def draw2D(self, *args, **kwargs):
"""Overrides :meth:`.GLVector.draw2D`. Calls the OpenGL
version-specific ``draw2D`` function.
"""
with glroutines.enabled((gl.GL_CULL_FACE)):
gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL)
gl.glCullFace(gl.GL_BACK)
gl.glFrontFace(self.frontFace())
fslgl.glrgbvector_funcs.draw2D(self, *args, **kwargs)
[docs] def draw3D(self, *args, **kwargs):
"""Overrides :meth:`.GLVector.draw3D`. Calls the OpenGL
version-specific ``draw3D`` function.
"""
fslgl.glrgbvector_funcs.draw3D(self, *args, **kwargs)
[docs] def drawAll(self, *args, **kwargs):
"""Overrides :meth:`.GLVector.drawAll`. Calls the OpenGL
version-specific ``drawAll`` function.
"""
fslgl.glrgbvector_funcs.drawAll(self, *args, **kwargs)
[docs] def postDraw(self, xform=None, bbox=None):
"""Overrides :meth:`.GLVector.postDraw`. Calls the base class
implementation, and the OpenGL version-specific ``postDraw``
function.
"""
glvector.GLVector.postDraw(self, xform, bbox)
fslgl.glrgbvector_funcs.postDraw(self, xform, bbox)