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

TensorImage now derives from the Nifti1 class, rather than the Image

class. Associated changes to RGB/Line/Vector code so they support
TensorImage overlays. More changes need to be made, methinks.
parent 5d7de43e
No related branches found
No related tags found
No related merge requests found
......@@ -61,13 +61,13 @@ def isPathToTensorData(path):
return getTensorDataPrefix(path) is not None
class TensorImage(fslimage.Image):
class TensorImage(fslimage.Nifti1):
"""The ``TensorImage`` class is able to load and encapsulate the diffusion
tensor data generated by the FSL ``dtifit`` tool.
"""
def __init__(self, path, *args, **kwargs):
def __init__(self, path):
"""Create a ``TensorImage``.
:arg path: A path to a ``dtifit`` directory. Alternately, the ``path``
......@@ -75,8 +75,6 @@ class TensorImage(fslimage.Image):
``{'v1', 'v2', 'v3', 'l1', 'l2', 'l3'}``, which specify
paths to images containing the tensor eigenvectors and
eigenvalues.
All other arguments are passed through to :meth:`.Image.__init__`.
"""
dtifitDir = isinstance(path, basestring)
......@@ -95,20 +93,15 @@ class TensorImage(fslimage.Image):
l2 = op.join(path, '{}_L2'.format(prefix))
l3 = op.join(path, '{}_L3'.format(prefix))
paths = {
'v1' : v1,
'v2' : v2,
'v3' : v3,
'l1' : l1,
'l2' : l2,
'l3' : l3
}
paths = {'v1' : v1, 'v2' : v2, 'v3' : v3,
'l1' : l1, 'l2' : l2, 'l3' : l3}
else:
paths = path
fslimage.Image.__init__(self, paths['v1'], *args, **kwargs)
fslimage.Nifti1.__init__(self, paths['v1'], loadData=False)
self.__v1 = self
self.__v1 = fslimage.Image(paths['v1'])
self.__v2 = fslimage.Image(paths['v2'])
self.__v3 = fslimage.Image(paths['v3'])
self.__l1 = fslimage.Image(paths['l1'])
......
......@@ -497,7 +497,7 @@ OVERLAY_TYPES = td.TypeDict({
'Image' : ['volume', 'mask', 'rgbvector', 'linevector', 'label'],
'Model' : ['model'],
'TensorImage' : ['tensor', 'volume', 'rgbvector', 'linevector'],
'TensorImage' : ['tensor', 'rgbvector', 'linevector'],
})
"""This dictionary provides a mapping between all overlay classes,
and the possible values that the :attr:`Display.overlayType` property
......
......@@ -47,7 +47,7 @@ def init(self):
self.lineVertices = None
self._vertexResourceName = '{}_{}_vertices'.format(
type(self).__name__, id(self.image))
type(self).__name__, id(self.vectorImage))
compileShaders( self)
updateShaderState(self)
......@@ -114,15 +114,15 @@ def updateVertices(self):
:meth:`.GLLineVertices.calculateHash` method), this function does nothing.
"""
image = self.image
if self.lineVertices is None:
self.lineVertices = glresources.get(
self._vertexResourceName, gllinevector.GLLineVertices, self)
if hash(self.lineVertices) != self.lineVertices.calculateHash(self):
log.debug('Re-generating line vertices for {}'.format(image))
log.debug('Re-generating line vertices '
'for {}'.format(self.vectorImage))
self.lineVertices.refresh(self)
glresources.set(self._vertexResourceName,
self.lineVertices,
......@@ -131,8 +131,9 @@ def updateVertices(self):
def updateShaderState(self):
"""Updates all fragment/vertex shader program variables. """
opts = self.displayOpts
image = self.vectorImage
opts = self.displayOpts
gl.glEnable(arbvp.GL_VERTEX_PROGRAM_ARB)
gl.glEnable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
......@@ -144,7 +145,7 @@ def updateShaderState(self):
voxValXform = self.imageTexture.voxValXform
cmapXform = self.xColourTexture.getCoordinateTransform()
shape = np.array(list(self.image.shape[:3]) + [0], dtype=np.float32)
shape = np.array(list(image.shape[:3]) + [0], dtype=np.float32)
invShape = 1.0 / shape
modThreshold = [opts.modThreshold / 100.0, 0.0, 0.0, 0.0]
offset = [0.5, 0.5, 0.5, 0.0]
......
......@@ -80,7 +80,7 @@ def updateShaderState(self):
voxValXform = self.imageTexture.voxValXform
cmapXform = self.xColourTexture.getCoordinateTransform()
shape = list(self.image.shape[:3]) + [0]
shape = list(self.vectorImage.shape[:3]) + [0]
modThreshold = [opts.modThreshold / 100.0, 0.0, 0.0, 0.0]
shaders.setFragmentProgramMatrix(0, voxValXform)
......
......@@ -48,9 +48,6 @@ def init(self):
self.texCoordBuffer = gl.glGenBuffers(1)
self.vertexIDBuffer = gl.glGenBuffers(1)
self._vertexResourceName = '{}_{}_vertices'.format(
type(self).__name__, id(self.image))
opts = self.displayOpts
def vertexUpdate(*a):
......@@ -137,8 +134,9 @@ def compileShaders(self):
def updateShaderState(self):
"""Updates all variables used by the vertex/fragment shaders. """
opts = self.displayOpts
image = self.vectorImage
opts = self.displayOpts
# The coordinate transformation matrices for
# each of the three colour textures are identical,
......@@ -146,7 +144,7 @@ def updateShaderState(self):
cmapXform = self.xColourTexture.getCoordinateTransform()
voxValXform = self.imageTexture.voxValXform
useSpline = False
imageShape = np.array(self.image.shape[:3], dtype=np.float32)
imageShape = np.array(image.shape[:3], dtype=np.float32)
voxValXform = np.array(voxValXform, dtype=np.float32).ravel('C')
cmapXform = np.array(cmapXform, dtype=np.float32).ravel('C')
......@@ -168,7 +166,7 @@ def updateShaderState(self):
gl.glUniform1i(self.zColourTexturePos, 4)
directed = opts.directed
imageDims = self.image.pixdim[:3]
imageDims = image.pixdim[:3]
d2vMat = opts.getTransform('display', 'voxel')
v2dMat = opts.getTransform('voxel', 'display')
......@@ -207,7 +205,7 @@ def draw(self, zpos, xform=None):
"""
image = self.image
image = self.vectorImage
opts = self.displayOpts
v2dMat = opts.getTransform('voxel', 'display')
resolution = np.array([opts.resolution] * 3)
......
......@@ -98,7 +98,7 @@ def updateShaderState(self):
voxValXform = self.imageTexture.voxValXform
cmapXform = self.xColourTexture.getCoordinateTransform()
useSpline = opts.interpolation == 'spline'
imageShape = np.array(self.image.shape, dtype=np.float32)
imageShape = np.array(self.vectorImage.shape, dtype=np.float32)
gl.glUseProgram(self.shaders)
......
......@@ -82,6 +82,7 @@ class GLLabel(globject.GLImageObject):
representation can be updated when they change.
"""
image = self.image
display = self.display
opts = self.displayOpts
name = self.name
......@@ -137,6 +138,7 @@ class GLLabel(globject.GLImageObject):
opts .addListener('volume', name, imageUpdate, weak=False)
opts .addListener('resolution', name, imageUpdate, weak=False)
opts .addListener('transform', name, update, weak=False)
image .addListener('data', name, update, weak=False)
opts.lut.addListener('labels', name, lutUpdate, weak=False)
# See comment in GLVolume.addDisplayListeners about this
......@@ -153,6 +155,8 @@ class GLLabel(globject.GLImageObject):
"""Called by :meth:`destroy`. Removes all of the listeners that were
added by :meth:`addListeners`.
"""
image = self.image
display = self.display
opts = self.displayOpts
name = self.name
......@@ -166,6 +170,7 @@ class GLLabel(globject.GLImageObject):
opts .removeListener( 'volume', name)
opts .removeListener( 'resolution', name)
opts .removeListener( 'transform', name)
image .removeListener( 'data', name)
opts.lut.removeListener( 'labels', name)
if self.__syncListenersRegistered:
......
......@@ -19,6 +19,7 @@ import logging
import numpy as np
import fsl.utils.transform as transform
import fsl.data.tensorimage as tensorimage
import fsl.fsleyes.gl as fslgl
import fsl.fsleyes.gl.glvector as glvector
import fsl.fsleyes.gl.routines as glroutines
......@@ -55,20 +56,32 @@ class GLLineVector(glvector.GLVector):
def __init__(self, image, display):
"""Create a ``GLLineVector`` instance.
:arg image: The :class:`.Image` instance.
:arg image: An :class:`.Image` or :class:`.TensorImage` instance.
:arg display: The associated :class:`.Display` instance.
"""
glvector.GLVector.__init__(self, image, display)
# If the overlay is a TensorImage, use the
# V1 image is the vector data. Otherwise,
# assume that the overlay is the vector image.
if isinstance(image, tensorimage.TensorImage): vecImage = image.V1()
else: vecImage = image
glvector.GLVector.__init__(self, image, display, vectorImage=vecImage)
fslgl.gllinevector_funcs.init(self)
def update(*a):
self.onUpdate()
self.displayOpts.addListener(
'lineWidth', self.name, update, weak=False)
self.displayOpts.addListener('lineWidth',
self.name,
update,
weak=False)
self.vectorImage.addListener('data',
self.name,
update,
weak=False)
def destroy(self):
......@@ -78,6 +91,7 @@ class GLLineVector(glvector.GLVector):
function, and calls the :meth:`.GLVector.destroy` method.
"""
self.displayOpts.removeListener('lineWidth', self.name)
self.vectorImage.removeListener('data', self.name)
fslgl.gllinevector_funcs.destroy(self)
glvector.GLVector.destroy(self)
......@@ -247,7 +261,7 @@ class GLLineVertices(object):
"""
opts = glvec.displayOpts
image = glvec.image
image = glvec.vectorImage
# Extract a sub-sample of the vector image
# at the current display resolution
......
......@@ -314,7 +314,7 @@ class GLSimpleObject(GLObject):
class GLImageObject(GLObject):
"""The ``GLImageObject`` class is the base class for all GL representations
of :class:`.Image` instances.
of :class:`.Nifti1` instances.
"""
def __init__(self, image, display):
......@@ -323,7 +323,8 @@ class GLImageObject(GLObject):
This constructor adds the following attributes to this instance:
=============== =======================================================
``image`` A reference to the :class:`.Image` being displayed.
``image`` A reference to the :class:`.Nifti1` overlay being
displayed.
``display`` A reference to the :class:`.Display` instance
associated with the ``image``.
``displayOpts`` A reference to the :class:`.DisplayOpts` instance,
......@@ -331,7 +332,7 @@ class GLImageObject(GLObject):
is assumed to be a sub-class of :class:`.ImageOpts`.
=============== =======================================================
:arg image: The :class:`.Image` instance
:arg image: The :class:`.Nifti1` instance
:arg display: An associated :class:`.Display` instance.
"""
......@@ -341,18 +342,9 @@ class GLImageObject(GLObject):
self.display = display
self.displayOpts = display.getDisplayOpts()
self.image.addListener('data', self.name, self.__imageDataChanged)
log.memory('{}.init ({})'.format(type(self).__name__, id(self)))
def __imageDataChanged(self, *a):
"""Called when the :attr:`.Image.data` changes. Calls
:meth:`GLObject.onUpdate`.
"""
self.onUpdate()
def __del__(self):
"""Prints a log message."""
log.memory('{}.del ({})'.format(type(self).__name__, id(self)))
......@@ -363,7 +355,6 @@ class GLImageObject(GLObject):
implementation. It clears references to the :class:`.Image`,
:class:`.Display`, and :class:`.DisplayOpts` instances.
"""
self.image.removeListener('data', self.name)
self.image = None
self.display = None
self.displayOpts = None
......
......@@ -12,6 +12,7 @@ vector :class:`.Image` overlays in RGB mode.
import numpy as np
import OpenGL.GL as gl
import fsl.data.tensorimage as tensorimage
import fsl.fsleyes.gl as fslgl
import fsl.fsleyes.gl.glvector as glvector
......@@ -60,17 +61,31 @@ class GLRGBVector(glvector.GLVector):
def __init__(self, image, display):
"""Create a ``GLRGBVector``.
:arg image: The :class:`.Image` instance.
:arg image: An :class:`.Image` or :class:`.TensorImage` instance.
:arg display: The associated :class:`.Display` instance.
"""
glvector.GLVector.__init__(self, image, display, self.__prefilter)
# If the overlay is a TensorImage, use the
# V1 image is the vector data. Otherwise,
# assume that the overlay is the vector image.
if isinstance(image, tensorimage.TensorImage): vecImage = image.V1()
else: vecImage = image
glvector.GLVector.__init__(self,
image,
display,
prefilter=np.abs,
vectorImage=vecImage)
fslgl.glrgbvector_funcs.init(self)
self.displayOpts.addListener('interpolation',
self.name,
self.__interpChanged)
self.vectorImage.addListener('data',
self.name,
self.__dataChanged)
def destroy(self):
"""Must be called when this ``GLRGBVector`` is no longer needed.
......@@ -91,13 +106,6 @@ class GLRGBVector(glvector.GLVector):
self.__setInterp()
def __prefilter(self, data):
"""This method is passed to :meth:`.GLVector.__init__` as the
``prefilter`` parameter. It removes the sign from the given data.
"""
return np.abs(data)
def __setInterp(self):
"""Updates the interpolation setting on the :class:`.ImageTexture`
that contains the vector :class:`.Image` being displayed.
......@@ -109,7 +117,14 @@ class GLRGBVector(glvector.GLVector):
self.imageTexture.set(interp=interp)
def __dataChanged(self, *a):
"""Called when the :attr:`.Image.data` of the vector image
changes. Calls :meth:`.GLObject.onUpdate`.
"""
self.onUpdate()
def __interpChanged(self, *a):
"""Called when the :attr:`.RGBVectorOpts.interpolation` property
changes. Updates the :class:`.ImageTexture` interpolation.
......
......@@ -5,8 +5,11 @@
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
import logging
import numpy as np
import glvector
import fsl.fsleyes.gl as fslgl
......@@ -18,8 +21,11 @@ class GLTensor(glvector.GLVector):
def __init__(self, image, display):
glvector.GLVector.__init__(self, image, display)
glvector.GLVector.__init__(self,
image,
display,
prefilter=np.abs,
vectorImage=image.V1())
fslgl.gltensor_funcs.init(self)
......
......@@ -51,7 +51,7 @@ class GLVector(globject.GLImageObject):
"""
def __init__(self, image, display, prefilter=None):
def __init__(self, image, display, prefilter=None, vectorImage=None):
"""Create a ``GLVector`` object bound to the given image and display.
Initialises the OpenGL data required to render the given image.
......@@ -64,19 +64,29 @@ class GLVector(globject.GLImageObject):
instances, so the textures and geometry can be updated when
necessary.
:arg image: An :class:`.Image` object.
:arg image: An :class:`.Nifti1` object.
:arg display: A :class:`.Display` object which describes how the
image is to be displayed.
:arg prefilter: An optional function which filters the data before it
is stored as a 3D texture. See :class:`.ImageTexture`.
Whether or not this function is provided, the data is
transposed so that the fourth dimension is the fastest
changing.
:arg display: A :class:`.Display` object which describes how the
image is to be displayed.
:arg prefilter: An optional function which filters the data before it
is stored as a 3D texture. See
:class:`.ImageTexture`. Whether or not this function
is provided, the data is transposed so that the
fourth dimension is the fastest changing.
:arg vectorImage: Optional. If ``None``, the ``image`` is assumed to
be a 4D :class:`.Image` instance which contains
the vector data. If this is not the case, the
``vectorImage`` parameter can be used to specify
an ``Image`` instance which does contain the
vector data.
"""
if not image.is4DImage() or image.shape[3] != 3:
if vectorImage is None:
vectorImage = image
if len(vectorImage.shape) != 4 or vectorImage.shape[3] != 3:
raise ValueError('Image must be 4 dimensional, with 3 volumes '
'representing the XYZ vector angles')
......@@ -84,6 +94,7 @@ class GLVector(globject.GLImageObject):
name = self.name
self.vectorImage = vectorImage
self.xColourTexture = textures.ColourMapTexture('{}_x'.format(name))
self.yColourTexture = textures.ColourMapTexture('{}_y'.format(name))
self.zColourTexture = textures.ColourMapTexture('{}_z'.format(name))
......@@ -225,7 +236,8 @@ class GLVector(globject.GLImageObject):
opts = self.displayOpts
prefilter = self.prefilter
texName = '{}_{}'.format(type(self).__name__, id(self.image))
vecImage = self.vectorImage
texName = '{}_{}'.format(type(self).__name__, id(vecImage))
if self.imageTexture is not None:
glresources.delete(self.imageTexture.getTextureName())
......@@ -248,7 +260,7 @@ class GLVector(globject.GLImageObject):
texName,
textures.ImageTexture,
texName,
self.image,
vecImage,
nvals=3,
normalise=True,
prefilter=realPrefilter)
......
......@@ -186,6 +186,7 @@ class GLVolume(globject.GLImageObject):
display properties are changed.
"""
image = self.image
display = self.display
opts = self.displayOpts
lName = self.name
......@@ -234,6 +235,7 @@ class GLVolume(globject.GLImageObject):
opts .addListener('resolution', lName, imageUpdate, weak=False)
opts .addListener('interpolation', lName, imageUpdate, weak=False)
opts .addListener('transform', lName, update, weak=False)
image .addListener('data', lName, update, weak=False)
# Save a flag so the removeDisplayListeners
# method knows whether it needs to de-register
......@@ -258,6 +260,7 @@ class GLVolume(globject.GLImageObject):
were added by :meth:`addDisplayListeners`.
"""
image = self.image
display = self.display
opts = self.displayOpts
......@@ -276,6 +279,7 @@ class GLVolume(globject.GLImageObject):
opts .removeListener( 'resolution', lName)
opts .removeListener( 'interpolation', lName)
opts .removeListener( 'transform', lName)
image .removeListener( 'data', lName)
if self.__syncListenersRegistered:
opts.removeSyncChangeListener('volume', lName)
......
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