Source code for fsleyes.gl.gl21.glvolume_funcs

#
# glvolume_funcs.py - OpenGL 2.1 functions used by the GLVolume class.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides functions which are used by the :class:`.GLVolume`
class to render :class:`.Image` overlays in an OpenGL 2.1 compatible manner.

A :class:`.GLSLShader` is used to manage the ``glvolume`` vertex/fragment
shader programs.
"""


import logging

import numpy                as np
import OpenGL.GL            as gl

import fsl.transform.affine as affine
import fsleyes.gl.routines  as glroutines
import fsleyes.gl.shaders   as shaders
import fsleyes.gl.glvolume  as glvolume


log = logging.getLogger(__name__)


[docs]def init(self): """Calls :func:`compileShaders` and :func:`updateShaderState`. """ self.shader = None compileShaders( self) updateShaderState(self)
[docs]def destroy(self): """Cleans up the shader programs.""" if self.shader is not None: self.shader.destroy() self.shader = None
[docs]def compileShaders(self): """Loads the vertex/fragment shader source, and creates a :class:`.GLSLShader`. """ if self.shader is not None: self.shader.destroy() if self.threedee: prefix = 'glvolume_3d' else: prefix = 'glvolume' env = {'textureIs2D' : self.imageTexture.ndim == 2} vertSrc = shaders.getVertexShader( prefix) fragSrc = shaders.getFragmentShader(prefix) self.shader = shaders.GLSLShader(vertSrc, fragSrc, constants=env)
[docs]def updateShaderState(self): """Updates the parameters used by the shader programs, reflecting the current display properties. """ if not self.ready(): return opts = self.opts display = self.display shader = self.shader # The clipping range options are in the voxel value # range, but the shader needs them to be in image # texture value range (0.0 - 1.0). So let's scale # them. imageIsClip = opts.clipImage is None imgXform = self.imageTexture.invVoxValXform if imageIsClip: clipXform = imgXform else: clipXform = self.clipTexture.invVoxValXform clipLow = opts.clippingRange[0] * clipXform[0, 0] + clipXform[0, 3] clipHigh = opts.clippingRange[1] * clipXform[0, 0] + clipXform[0, 3] texZero = 0.0 * imgXform[ 0, 0] + imgXform[ 0, 3] imageShape = self.image.shape[:3] texShape = self.imageTexture.shape[:3] if len(texShape) == 2: texShape = list(texShape) + [1] if imageIsClip: clipImageShape = imageShape else: clipImageShape = opts.clipImage.shape[:3] # Create a single transformation matrix # which transforms from image texture values # to voxel values, and scales said voxel # values to colour map texture coordinates. img2CmapXform = affine.concat( self.colourTexture.getCoordinateTransform(), self.imageTexture.voxValXform) shader.load() changed = False changed |= shader.set('useSpline', opts.interpolation == 'spline') changed |= shader.set('imageShape', imageShape) changed |= shader.set('texShape', texShape) changed |= shader.set('clipLow', clipLow) changed |= shader.set('clipHigh', clipHigh) changed |= shader.set('texZero', texZero) changed |= shader.set('invertClip', opts.invertClipping) changed |= shader.set('useNegCmap', opts.useNegativeCmap) changed |= shader.set('imageIsClip', imageIsClip) changed |= shader.set('img2CmapXform', img2CmapXform) changed |= shader.set('clipImageShape', clipImageShape) changed |= shader.set('imageTexture', 0) changed |= shader.set('colourTexture', 1) changed |= shader.set('negColourTexture', 2) changed |= shader.set('clipTexture', 3) if self.threedee: blendFactor = (1 - opts.blendFactor) ** 2 clipPlanes = np.zeros((opts.numClipPlanes, 4), dtype=np.float32) d2tmat = opts.getTransform('display', 'texture') if opts.clipMode == 'intersection': clipMode = 1 elif opts.clipMode == 'union': clipMode = 2 elif opts.clipMode == 'complement': clipMode = 3 else: clipMode = 0 for i in range(opts.numClipPlanes): origin, normal = self.get3DClipPlane(i) origin = affine.transform(origin, d2tmat) normal = affine.transformNormal(normal, d2tmat) clipPlanes[i, :] = glroutines.planeEquation2(origin, normal) changed |= shader.set('numClipPlanes', opts.numClipPlanes) changed |= shader.set('clipMode', clipMode) changed |= shader.set('clipPlanes', clipPlanes, opts.numClipPlanes) changed |= shader.set('blendFactor', blendFactor) changed |= shader.set('stepLength', 1.0 / opts.getNumSteps()) changed |= shader.set('alpha', display.alpha / 100.0) shader.unload() return changed
[docs]def preDraw(self, xform=None, bbox=None): """Sets up the GL state to draw a slice from the given :class:`.GLVolume` instance. """ self.shader.load() if isinstance(self, glvolume.GLVolume): clipCoordXform = self.calculateClipCoordTransform() self.shader.set('clipCoordXform', clipCoordXform)
[docs]def draw2D(self, zpos, axes, xform=None, bbox=None): """Draws the specified 2D slice from the specified image on the canvas. :arg self: The :class:`.GLVolume` object which is managing the image to be drawn. :arg zpos: World Z position of slice to be drawn. :arg axes: x, y, z axis indices. :arg xform: A 4*4 transformation matrix to be applied to the vertex data. :arg bbox: An optional bounding box. """ vertices, voxCoords, texCoords = self.generateVertices2D( zpos, axes, bbox=bbox) if xform is not None: vertices = affine.transform(vertices, xform) self.shader.setAtt('vertex', vertices) self.shader.setAtt('voxCoord', voxCoords) self.shader.setAtt('texCoord', texCoords) self.shader.loadAtts() gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6)
[docs]def draw3D(self, xform=None, bbox=None): """Draws the image in 3D on the canvas. :arg self: The :class:`.GLVolume` object which is managing the image to be drawn. :arg xform: A 4*4 transformation matrix to be applied to the vertex data. :arg bbox: An optional bounding box. """ opts = self.opts tex = self.renderTexture1 proj = self.canvas.projectionMatrix vertices, voxCoords, texCoords = self.generateVertices3D(bbox) rayStep , texform = opts.calculateRayCastSettings(xform, proj) rayStep = affine.transformNormal( rayStep, self.imageTexture.texCoordXform(self.overlay.shape)) texform = affine.concat( texform, self.imageTexture.invTexCoordXform(self.overlay.shape)) if xform is not None: vertices = affine.transform(vertices, xform) self.shader.set( 'tex2ScreenXform', texform) self.shader.set( 'rayStep', rayStep) self.shader.setAtt('vertex', vertices) self.shader.setAtt('texCoord', texCoords) self.shader.loadAtts() tex.bindAsRenderTarget() gl.glDrawArrays(gl.GL_TRIANGLES, 0, 36) tex.unbindAsRenderTarget() self.shader.unloadAtts() self.shader.unload()
[docs]def drawAll(self, axes, zposes, xforms): """Draws all of the specified slices. """ nslices = len(zposes) vertices = np.zeros((nslices * 6, 3), dtype=np.float32) voxCoords = np.zeros((nslices * 6, 3), dtype=np.float32) texCoords = np.zeros((nslices * 6, 3), dtype=np.float32) for i, (zpos, xform) in enumerate(zip(zposes, xforms)): v, vc, tc = self.generateVertices2D(zpos, axes) vertices[ i * 6: i * 6 + 6, :] = affine.transform(v, xform) voxCoords[i * 6: i * 6 + 6, :] = vc texCoords[i * 6: i * 6 + 6, :] = tc self.shader.setAtt('vertex', vertices) self.shader.setAtt('voxCoord', voxCoords) self.shader.setAtt('texCoord', texCoords) self.shader.loadAtts() gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6 * nslices)
[docs]def postDraw(self, xform=None, bbox=None): """Cleans up the GL state after drawing from the given :class:`.GLVolume` instance. """ self.shader.unloadAtts() self.shader.unload() if self.threedee: self.drawClipPlanes(xform=xform)