Source code for fsleyes.gl.gltensor

#
# gltensor.py - The GLTensor class.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :class:`GLTensor` class, for displaying tensor
ellipsoids in a :class:`.DTIFitTensor` overlay, or compatible :class:`.Image`
overlay.

See :mod:`.gl21.gltensor_funcs`.
"""


import numpy                as np

import OpenGL.GL            as gl

import fsl.data.image       as fslimage
import fsl.data.dtifit      as dtifit
import fsleyes.gl           as fslgl
import fsleyes.gl.resources as glresources
import fsleyes.gl.textures  as textures
from . import                  glvector


[docs]class GLTensor(glvector.GLVector): """The ``GLTensor`` class encapsulates the logic required to render :class:`.TensorImage` overlays. Most of the functionality is in the :mod:`.gl21.gltensor_funcs` module. .. note:: The ``GLTensor`` is not currently supported on versions of OpenGL older than 2.1 (and probably never will be). The eigenvalues and eigenvectors of the overlay are stored as 3D :class:`.ImageTexture` instances, using the :mod:`.gl.resources` module. These textures are added as attributes of a GLTensor instance - this is in addition to the textures that are used for :class:`.GLVector` instances (of which the ``GLTensor`` is a sub-class): ============== ================== ================== Attribute name Description Texture unit ============== ================== ================== ``v1Texture`` First eigenvector ``gl.GL_TEXTURE8`` ``v2Texture`` Second eigenvector ``gl.GL_TEXTURE9`` ``v3Texture`` Third eigenvector ``gl.GL_TEXTURE10`` ``l1Texture`` First eigenvalue ``gl.GL_TEXTURE11`` ``l2Texture`` Second eigenvalue ``gl.GL_TEXTURE12`` ``l3Texture`` Third eigenvalue ``gl.GL_TEXTURE13`` ============== ================== ================== """
[docs] def __init__(self, image, overlayList, displayCtx, canvas, threedee): """Create a ``GLTensor``. Prepares the eigenvalue and eigenvector textures, and calls the :func:`.gl21.gltensor_funcs.init` function. :arg image: A :class:`.DTIFitTensor` or compatible :class:`.Image` overlay. :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. """ prefilter = np.abs def prefilterRange(dmin, dmax): return max((0, dmin)), max((abs(dmin), abs(dmax))) # The overlay must either be a DTIFitTensor if isinstance(image, dtifit.DTIFitTensor): v1 = image.V1() v2 = image.V2() v3 = image.V3() l1 = image.L1() l2 = image.L2() l3 = image.L3() # Or an Image with 6 volumes containing # the unique tensor matrix elements else: decomp = dtifit.decomposeTensorMatrix(image.data) v1 = fslimage.Image(decomp[0]) v2 = fslimage.Image(decomp[1]) v3 = fslimage.Image(decomp[2]) l1 = fslimage.Image(decomp[3]) l2 = fslimage.Image(decomp[4]) l3 = fslimage.Image(decomp[5]) self.v1 = v1 self.v2 = v2 self.v3 = v3 self.l1 = l1 self.l2 = l2 self.l3 = l3 # Create a texture for each eigenvalue/ # vector, and add each of them as suitably # named attributes on this GLTensor # instance. names = ['v1', 'v2', 'v3', 'l1', 'l2', 'l3'] imgs = [ v1, v2, v3, l1, l2, l3] for name, img in zip(names, imgs): texName = '{}_{}_{}'.format(type(self).__name__, name, id(img)) if name[0] == 'v': nvals = 3 else: nvals = 1 tex = glresources.get( texName, textures.ImageTexture, texName, img, nvals=nvals, normaliseRange=img.dataRange) setattr(self, '{}Texture'.format(name), tex) glvector.GLVector.__init__(self, image, overlayList, displayCtx, canvas, threedee, prefilter=prefilter, prefilterRange=prefilterRange, vectorImage=v1, init=lambda: fslgl.gltensor_funcs.init( self))
[docs] def destroy(self): """Must be called when this ``GLTensor`` is no longer needed. Performs cleanup tasks. """ glvector.GLVector.destroy(self) fslgl.gltensor_funcs.destroy(self) names = ['v1', 'v2', 'v3', 'l1', 'l2', 'l3'] for name in names: attrName = '{}Texture'.format(name) tex = getattr(self, attrName) glresources.delete(tex.name) setattr(self, attrName, None)
[docs] def texturesReady(self): """Overrides :meth:`.GLVector.texturesReady`. Returns ``True`` if all of the textures are ready, ``False`` otherwise. """ return (glvector.GLVector.texturesReady(self) and self.v1Texture is not None and self.v2Texture is not None and self.v3Texture is not None and self.l1Texture is not None and self.l2Texture is not None and self.l3Texture is not None and self.v1Texture.ready() and self.v2Texture.ready() and self.v3Texture.ready() and self.l1Texture.ready() and self.l2Texture.ready() and self.l3Texture.ready())
[docs] def addListeners(self): """Overrides :meth:`.GLVector.addListeners`. Calls the base class implementation, and adds some property listeners to the :class:`.TensorOpts` instance associated with the overlay being displayed. """ glvector.GLVector.addListeners(self) name = self.name opts = self.opts opts.addListener('lighting', name, self.asyncUpdateShaderState) opts.addListener('orientFlip', name, self.asyncUpdateShaderState) opts.addListener('tensorScale', name, self.asyncUpdateShaderState) opts.addListener('tensorResolution', name, self.__tensorResolutionChanged)
[docs] def removeListeners(self): """Overrides :meth:`.GLVector.removeListeners`. Calls the base class implementation, and removes some property listeners. """ glvector.GLVector.removeListeners(self) name = self.name opts = self.opts opts.removeListener('lighting', name) opts.removeListener('orientFlip', name) opts.removeListener('tensorResolution', name) opts.removeListener('tensorScale', name)
[docs] def getDataResolution(self, xax, yax): """Overrides :meth:`.GLVector.getDataResolution`. Returns a pixel resolution suitable for off-screen rendering of this ``GLTensor``. """ res = list(glvector.GLVector.getDataResolution(self, xax, yax)) res[xax] *= 20 res[yax] *= 20 return res
[docs] def compileShaders(self): """Overrides :meth:`.GLVector.compileShaders`. Calls the :func:`.gl21.gltensor_funcs.compileShaders` function. """ fslgl.gltensor_funcs.compileShaders(self)
[docs] def updateShaderState(self): """Overrides :meth:`.GLVector.updateShaderState`. Calls the :func:`.gl21.gltensor_funcs.updateShaderState` function. """ return fslgl.gltensor_funcs.updateShaderState(self)
[docs] def preDraw(self, xform=None, bbox=None): """Overrides :meth:`.GLVector.preDraw`. Binds the eigenvalue and eigenvector textures, calls the :meth:`.GLVector.preDraw` method, and the :func:`.gl21.gltensor_funcs.preDraw` function. """ self.v1Texture.bindTexture(gl.GL_TEXTURE8) self.v2Texture.bindTexture(gl.GL_TEXTURE9) self.v3Texture.bindTexture(gl.GL_TEXTURE10) self.l1Texture.bindTexture(gl.GL_TEXTURE11) self.l2Texture.bindTexture(gl.GL_TEXTURE12) self.l3Texture.bindTexture(gl.GL_TEXTURE13) glvector.GLVector.preDraw(self, xform, bbox) fslgl.gltensor_funcs.preDraw(self, xform, bbox)
[docs] def draw2D(self, *args, **kwargs): """Overrides :meth:`.GLVector.draw2D`. Calls the :func:`.gl21.gltensor_funcs.draw2D` function. """ fslgl.gltensor_funcs.draw2D(self, *args, **kwargs)
[docs] def draw3D(self, *args, **kwargs): """Overrides :meth:`.GLVector.draw3D`. Calls the :func:`.gl21.gltensor_funcs.draw3D` function. """ fslgl.gltensor_funcs.draw3D(self, *args, **kwargs)
[docs] def postDraw(self, xform=None, bbox=None): """Overrides :meth:`.GLVector.postDraw`. Unbinds the eigenvalue and eigenvector textures, calls the :meth:`.GLVector.postDraw` method, and the :func:`.gl21.gltensor_funcs.postDraw` function. """ self.v1Texture.unbindTexture() self.v2Texture.unbindTexture() self.v3Texture.unbindTexture() self.l1Texture.unbindTexture() self.l2Texture.unbindTexture() self.l3Texture.unbindTexture() glvector.GLVector.postDraw(self, xform, bbox) fslgl.gltensor_funcs.postDraw(self, xform, bbox)
def __tensorResolutionChanged(self, *a): """Called when the :attr:`.TensorOpts.tensorResolution` property changes. Calls :meth:`.asyncUpdateShaderState`. """ self.asyncUpdateShaderState(alwaysNotify=True)