From 28df70c496b6c9e013fe0042c51dbf479a30db3a Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Mon, 29 Feb 2016 10:56:54 +0000 Subject: [PATCH] Xax/yax are passed to all GLObject constructors, so SliceCanvas does not need to call setAxes after GLObject creation (as this can cause issues due to asynchronous ImageTexture initialisation). Also, related bugfix to GLLabel - ready() checks to make sure that shader object has been created --- fsl/fsleyes/gl/annotations.py | 64 +++++++++++++++++++++++++--------- fsl/fsleyes/gl/gllabel.py | 17 ++++++--- fsl/fsleyes/gl/gllinevector.py | 7 ++-- fsl/fsleyes/gl/glmodel.py | 8 +++-- fsl/fsleyes/gl/globject.py | 43 +++++++++++++++-------- fsl/fsleyes/gl/glrgbvector.py | 6 +++- fsl/fsleyes/gl/gltensor.py | 8 ++++- fsl/fsleyes/gl/glvector.py | 8 ++++- fsl/fsleyes/gl/glvolume.py | 8 +++-- fsl/fsleyes/gl/slicecanvas.py | 6 ++-- 10 files changed, 129 insertions(+), 46 deletions(-) diff --git a/fsl/fsleyes/gl/annotations.py b/fsl/fsleyes/gl/annotations.py index 06b9a42b8..e73348e82 100644 --- a/fsl/fsleyes/gl/annotations.py +++ b/fsl/fsleyes/gl/annotations.py @@ -95,21 +95,27 @@ class Annotations(object): def line(self, *args, **kwargs): """Queues a line for drawing - see the :class:`Line` class. """ hold = kwargs.pop('hold', False) - return self.obj(Line(*args, **kwargs), hold) + obj = Line(self.__xax, self.__yax, *args, **kwargs) + + return self.obj(obj, hold) def rect(self, *args, **kwargs): """Queues a rectangle for drawing - see the :class:`Rectangle` class. """ hold = kwargs.pop('hold', False) - return self.obj(Rect(*args, **kwargs), hold) + obj = Rect(self.__xax, self.__yax, *args, **kwargs) + + return self.obj(obj, hold) def grid(self, *args, **kwargs): """Queues a voxel grid for drawing - see the :class:`VoxelGrid` class. """ hold = kwargs.pop('hold', False) - return self.obj(VoxelGrid(*args, **kwargs), hold) + obj = VoxelGrid(self.__xax, self.__yax, *args, **kwargs) + + return self.obj(obj, hold) def selection(self, *args, **kwargs): @@ -117,7 +123,9 @@ class Annotations(object): class. """ hold = kwargs.pop('hold', False) - return self.obj(VoxelSelection(*args, **kwargs), hold) + obj = VoxelSelection(self.__xax, self.__yax, *args, **kwargs) + + return self.obj(obj, hold) def obj(self, obj, hold=False): @@ -236,9 +244,13 @@ class AnnotationObject(globject.GLSimpleObject): :meth:`globject.GLObject.draw` method. """ - def __init__(self, xform=None, colour=None, width=None): + def __init__(self, xax, yax, xform=None, colour=None, width=None): """Create an ``AnnotationObject``. + :arg xax: Initial display X axis + + :arg yax: Initial display Y axis + :arg xform: Transformation matrix which will be applied to all vertex coordinates. @@ -246,7 +258,7 @@ class AnnotationObject(globject.GLSimpleObject): :arg width: Line width to use for the annotation. """ - globject.GLSimpleObject.__init__(self) + globject.GLSimpleObject.__init__(self, xax, yax) self.colour = colour self.width = width @@ -269,13 +281,17 @@ class Line(AnnotationObject): 2D line. """ - def __init__(self, xy1, xy2, *args, **kwargs): + def __init__(self, xax, yax, xy1, xy2, *args, **kwargs): """Create a ``Line`` annotation. The ``xy1`` and ``xy2`` coordinate tuples should be in relation to the axes which map to the horizontal/vertical screen axes on the target canvas. + :arg xax: Initial display X axis + + :arg yax: Initial display Y axis + :arg xy1: Tuple containing the (x, y) coordinates of one endpoint. :arg xy2: Tuple containing the (x, y) coordinates of the second @@ -284,7 +300,7 @@ class Line(AnnotationObject): All other arguments are passed through to :meth:`AnnotationObject.__init__`. """ - AnnotationObject.__init__(self, *args, **kwargs) + AnnotationObject.__init__(self, xax, yax, *args, **kwargs) self.xy1 = xy1 self.xy2 = xy2 @@ -310,18 +326,22 @@ class Rect(AnnotationObject): 2D rectangle. """ - def __init__(self, xy, w, h, *args, **kwargs): + def __init__(self, xax, yax, xy, w, h, *args, **kwargs): """Create a :class:`Rect` annotation. - :arg xy: Tuple specifying bottom left of the rectangle, in the display - coordinate system. - :arg w: Rectangle width. - :arg h: Rectangle height. + :arg xax: Initial display X axis + + :arg yax: Initial display Y axis + + :arg xy: Tuple specifying bottom left of the rectangle, in the display + coordinate system. + :arg w: Rectangle width. + :arg h: Rectangle height. All other arguments are passed through to :meth:`AnnotationObject.__init__`. """ - AnnotationObject.__init__(self, *args, **kwargs) + AnnotationObject.__init__(self, xax, yax, *args, **kwargs) self.xy = xy self.w = w self.h = h @@ -371,6 +391,8 @@ class VoxelGrid(AnnotationObject): def __init__(self, + xax, + yax, selectMask, displayToVoxMat, voxToDisplayMat, @@ -379,6 +401,10 @@ class VoxelGrid(AnnotationObject): **kwargs): """Create a ``VoxelGrid`` annotation. + :arg xax: Initial display X axis + + :arg yax: Initial display Y axis + :arg selectMask: A 3D numpy array, the same shape as the image being annotated (or a sub-space of the image - see the ``offsets`` argument), which is @@ -403,7 +429,7 @@ class VoxelGrid(AnnotationObject): """ kwargs['xform'] = voxToDisplayMat - AnnotationObject.__init__(self, *args, **kwargs) + AnnotationObject.__init__(self, xax, yax, *args, **kwargs) if offsets is None: offsets = [0, 0, 0] @@ -453,6 +479,8 @@ class VoxelSelection(AnnotationObject): def __init__(self, + xax, + yax, selection, displayToVoxMat, voxToDisplayMat, @@ -461,6 +489,10 @@ class VoxelSelection(AnnotationObject): **kwargs): """Create a ``VoxelSelection`` annotation. + :arg xax: Initial display X axis + + :arg yax: Initial display Y axis + :arg selection: A :class:`.Selection` instance which defines the voxels to be highlighted. @@ -484,7 +516,7 @@ class VoxelSelection(AnnotationObject): :meth:`AnnotationObject.__init__` method. """ - AnnotationObject.__init__(self, *args, **kwargs) + AnnotationObject.__init__(self, xax, yax, *args, **kwargs) if offsets is None: offsets = [0, 0, 0] diff --git a/fsl/fsleyes/gl/gllabel.py b/fsl/fsleyes/gl/gllabel.py index b01e27ad3..a33ecb0a1 100644 --- a/fsl/fsleyes/gl/gllabel.py +++ b/fsl/fsleyes/gl/gllabel.py @@ -43,20 +43,25 @@ class GLLabel(globject.GLImageObject): """ - def __init__(self, image, display): + def __init__(self, image, display, xax, yax): """Create a ``GLLabel``. :arg image: The :class:`.Image` instance. :arg display: The associated :class:`.Display` instance. + :arg xax: Initial display X axis + :arg yax: Initial display Y axis """ - globject.GLImageObject.__init__(self, image, display) - - lutTexName = '{}_lut'.format(self.name) + globject.GLImageObject.__init__(self, image, display, xax, yax) + lutTexName = '{}_lut'.format(self.name) self.lutTexture = textures.LookupTableTexture(lutTexName) self.imageTexture = None + # The shader attribute will be created + # by the gllabel_funcs module + self.shader = None + self.addListeners() self.refreshLutTexture() @@ -86,7 +91,9 @@ class GLLabel(globject.GLImageObject): """Returns ``True`` if this ``GLLabel`` is ready to be drawn, ``False`` otherwise. """ - return self.imageTexture is not None and self.imageTexture.ready() + return self.shader is not None and \ + self.imageTexture is not None and \ + self.imageTexture.ready() def addListeners(self): diff --git a/fsl/fsleyes/gl/gllinevector.py b/fsl/fsleyes/gl/gllinevector.py index 1549f9bdd..fce6f809f 100644 --- a/fsl/fsleyes/gl/gllinevector.py +++ b/fsl/fsleyes/gl/gllinevector.py @@ -53,12 +53,13 @@ class GLLineVector(glvector.GLVector): """ - def __init__(self, image, display): + def __init__(self, image, display, xax, yax): """Create a ``GLLineVector`` instance. :arg image: An :class:`.Image` or :class:`.TensorImage` instance. - :arg display: The associated :class:`.Display` instance. + :arg xax: Initial display X axis + :arg yax: Initial display Y axis """ # If the overlay is a TensorImage, use the @@ -70,6 +71,8 @@ class GLLineVector(glvector.GLVector): glvector.GLVector.__init__(self, image, display, + xax, + yax, vectorImage=vecImage, init=lambda: fslgl.gllinevector_funcs.init( self)) diff --git a/fsl/fsleyes/gl/glmodel.py b/fsl/fsleyes/gl/glmodel.py index 0dd61055d..0e348889e 100644 --- a/fsl/fsleyes/gl/glmodel.py +++ b/fsl/fsleyes/gl/glmodel.py @@ -65,16 +65,20 @@ class GLModel(globject.GLObject): """ - def __init__(self, overlay, display): + def __init__(self, overlay, display, xax, yax): """Create a ``GLModel``. :arg overlay: A :class:`.Model` overlay. :arg display: A :class:`.Display` instance defining how the ``overlay`` is to be displayed. + + :arg xax: Initial display X axis + + :arg yax: Initial display Y axis """ - globject.GLObject.__init__(self) + globject.GLObject.__init__(self, xax, yax) self.shader = None self.overlay = overlay diff --git a/fsl/fsleyes/gl/globject.py b/fsl/fsleyes/gl/globject.py index 3c03d030e..72fbc67e0 100644 --- a/fsl/fsleyes/gl/globject.py +++ b/fsl/fsleyes/gl/globject.py @@ -50,7 +50,7 @@ def getGLObjectType(overlayType): return typeMap.get(overlayType, None) -def createGLObject(overlay, display): +def createGLObject(overlay, display, xax, yax): """Create :class:`GLObject` instance for the given overlay, as specified by the :attr:`.Display.overlayType` property. @@ -58,10 +58,14 @@ def createGLObject(overlay, display): :arg display: A :class:`.Display` instance describing how the overlay should be displayed. + + :arg xax: Initial display X axis + + :arg yax: Initial display Y axis """ ctr = getGLObjectType(display.overlayType) - if ctr is not None: return ctr(overlay, display) + if ctr is not None: return ctr(overlay, display, xax, yax) else: return None @@ -104,7 +108,11 @@ class GLObject(notifier.Notifier): Sub-class implementations must do the following: - - Call :meth:`__init__`. + - Call :meth:`__init__`. A ``GLObject.__init__`` sub-class method must + have the following signature:: + + def __init__(self, overlay, display, xax, yax) + - Call :meth:`notify` whenever its OpenGL representation changes. @@ -132,23 +140,26 @@ class GLObject(notifier.Notifier): """ - def __init__(self): - """Create a :class:`GLObject`. The constructor adds one attribute + def __init__(self, xax, yax): + """Create a :class:`GLObject`. The constructor adds one attribute to this instance, ``name``, which is simply a unique name for this - instance, and gives default values to the ``xax``, ``yax``, and - ``zax`` attributes. + instance, and gives values to the ``xax``, ``yax``, and ``zax`` + attributes. Subclass implementations must call this method, and should also perform any necessary OpenGL initialisation, such as creating textures. + + :arg xax: Initial display X axis + :arg yax: Initial display Y axis """ # Give this instance a name, and set # initial values for the display axes self.name = '{}_{}'.format(type(self).__name__, id(self)) - self.xax = 0 - self.yax = 1 - self.zax = 2 + self.xax = xax + self.yax = yax + self.zax = 3 - xax - yax log.memory('{}.init ({})'.format(type(self).__name__, id(self))) @@ -285,9 +296,9 @@ class GLSimpleObject(GLObject): be called. """ - def __init__(self): + def __init__(self, xax, yax): """Create a ``GLSimpleObject``. """ - GLObject.__init__(self) + GLObject.__init__(self, xax, yax) def ready(self): @@ -315,7 +326,7 @@ class GLImageObject(GLObject): of :class:`.Nifti1` instances. """ - def __init__(self, image, display): + def __init__(self, image, display, xax, yax): """Create a ``GLImageObject``. This constructor adds the following attributes to this instance: @@ -333,9 +344,13 @@ class GLImageObject(GLObject): :arg image: The :class:`.Nifti1` instance :arg display: An associated :class:`.Display` instance. + + :arg xax: Initial display X axis + + :arg yax: Initial display Y axis """ - GLObject.__init__(self) + GLObject.__init__(self, xax, yax) self.image = image self.display = display self.displayOpts = display.getDisplayOpts() diff --git a/fsl/fsleyes/gl/glrgbvector.py b/fsl/fsleyes/gl/glrgbvector.py index 5d290dd50..4c25bd4f5 100644 --- a/fsl/fsleyes/gl/glrgbvector.py +++ b/fsl/fsleyes/gl/glrgbvector.py @@ -59,11 +59,13 @@ class GLRGBVector(glvector.GLVector): """ - def __init__(self, image, display): + def __init__(self, image, display, xax, yax): """Create a ``GLRGBVector``. :arg image: An :class:`.Image` or :class:`.TensorImage` instance. :arg display: The associated :class:`.Display` instance. + :arg xax: Initial display X axis + :arg yax: Initial display Y axis """ # If the overlay is a TensorImage, use the @@ -75,6 +77,8 @@ class GLRGBVector(glvector.GLVector): glvector.GLVector.__init__(self, image, display, + xax, + yax, prefilter=np.abs, vectorImage=vecImage, init=lambda: fslgl.glrgbvector_funcs.init( diff --git a/fsl/fsleyes/gl/gltensor.py b/fsl/fsleyes/gl/gltensor.py index 96f8e7d68..064adc63d 100644 --- a/fsl/fsleyes/gl/gltensor.py +++ b/fsl/fsleyes/gl/gltensor.py @@ -28,7 +28,7 @@ class GLTensor(glvector.GLVector): """ - def __init__(self, image, display): + def __init__(self, image, display, xax, yax): """Create a ``GLTensor``. Calls the :func:`.gl21.gltensor_funcs.init` function. @@ -36,10 +36,16 @@ class GLTensor(glvector.GLVector): :arg display: The :class:`.Display` instance associated with the ``image``. + + :arg xax: Initial display X axis + + :arg yax: Initial display Y axis """ glvector.GLVector.__init__(self, image, display, + xax, + yax, prefilter=np.abs, vectorImage=image.V1(), init=lambda: fslgl.gltensor_funcs.init( diff --git a/fsl/fsleyes/gl/glvector.py b/fsl/fsleyes/gl/glvector.py index a05935eff..f47b6fc7a 100644 --- a/fsl/fsleyes/gl/glvector.py +++ b/fsl/fsleyes/gl/glvector.py @@ -96,6 +96,8 @@ class GLVector(globject.GLImageObject): def __init__(self, image, display, + xax, + yax, prefilter=None, vectorImage=None, init=None): @@ -116,6 +118,10 @@ class GLVector(globject.GLImageObject): :arg display: A :class:`.Display` object which describes how the image is to be displayed. + :arg xax: Initial display X axis + + :arg yax: Initial display Y axis + :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 @@ -145,7 +151,7 @@ class GLVector(globject.GLImageObject): raise ValueError('Image must be 4 dimensional, with 3 volumes ' 'representing the XYZ vector angles') - globject.GLImageObject.__init__(self, image, display) + globject.GLImageObject.__init__(self, image, display, xax, yax) name = self.name diff --git a/fsl/fsleyes/gl/glvolume.py b/fsl/fsleyes/gl/glvolume.py index e04fa19dd..3eb8125ac 100644 --- a/fsl/fsleyes/gl/glvolume.py +++ b/fsl/fsleyes/gl/glvolume.py @@ -142,16 +142,20 @@ class GLVolume(globject.GLImageObject): """ - def __init__(self, image, display): + def __init__(self, image, display, xax, yax): """Create a ``GLVolume`` object. :arg image: An :class:`.Image` object. :arg display: A :class:`.Display` object which describes how the image is to be displayed. + + :arg xax: Initial display X axis + + :arg yax: Initial display Y axis """ - globject.GLImageObject.__init__(self, image, display) + globject.GLImageObject.__init__(self, image, display, xax, yax) # Add listeners to this image so the view can be # updated when its display properties are changed diff --git a/fsl/fsleyes/gl/slicecanvas.py b/fsl/fsleyes/gl/slicecanvas.py index 0b3d700d1..5ce14d3df 100644 --- a/fsl/fsleyes/gl/slicecanvas.py +++ b/fsl/fsleyes/gl/slicecanvas.py @@ -802,10 +802,12 @@ class SliceCanvas(props.HasProperties): self._glObjects.pop(overlay) return - globj = globject.createGLObject(overlay, display) + globj = globject.createGLObject(overlay, + display, + self.xax, + self.yax) if globj is not None: - globj.setAxes(self.xax, self.yax) globj.register(self.name, self._refresh) self._glObjects[overlay] = globj -- GitLab