From bcb54d606379b29c1e9828488d7d90148d12b815 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Thu, 5 Feb 2015 16:35:57 +0000 Subject: [PATCH] All Annotation objects now derive from GLObject --- fsl/fslview/gl/annotations.py | 185 ++++++++++++++++++------------- fsl/fslview/gl/globject.py | 41 ++++++- fsl/fslview/gl/lightboxcanvas.py | 2 +- fsl/fslview/gl/slicecanvas.py | 9 +- 4 files changed, 150 insertions(+), 87 deletions(-) diff --git a/fsl/fslview/gl/annotations.py b/fsl/fslview/gl/annotations.py index dc10eebe5..4a1c23476 100644 --- a/fsl/fslview/gl/annotations.py +++ b/fsl/fslview/gl/annotations.py @@ -48,17 +48,32 @@ class Annotations(object): """ - def __init__(self): - """Creates an :class:`Annotations` object.""" + def __init__(self, xax, yax): + """Creates an :class:`Annotations` object. + + :arg xax: Index of the display coordinate system axis that corresponds + to the horizontal screen axis. + + :arg yax: Index of the display coordinate system axis that corresponds + to the horizontal screen axis. + """ self._q = [] self._holdq = [] + self._xax = xax + self._yax = yax - def _adjustColour(self, colour): - """Turns RGB colour tuples into RGBA tuples, if necessary.""" - if len(colour) == 3: return (colour[0], colour[1], colour[2], 1.0) - else: return colour + def setAxes(self, xax, yax): + """This method must be called if the display orientation changes. See + :meth:`__init__`. + """ + + self._xax = xax + self._yax = yax + + for obj in self._q: obj.setAxes(xax, yax) + for obj in self._holdq: obj.setAxes(xax, yax) def line(self, *args, **kwargs): @@ -89,11 +104,15 @@ class Annotations(object): if hold: self._holdq.append(obj) else: self._q .append(obj) + obj.setAxes(self._xax, self._yax) + return obj def dequeue(self, obj, hold=False): - """Removes the given :class:`AnnotationObject` from the queue. + """Removes the given :class:`AnnotationObject` from the queue, but + does not call its :meth:`~fsl.fslview.gl.globject.GLObject.destroy` + method - this is the responsibility of the caller. """ if hold: @@ -106,68 +125,76 @@ class Annotations(object): def clear(self): """Clears both the normal queue and the persistent (a.k.a. ``hold``) - queue. + queue, and calls the :meth:`~fsl.fslview.gl.globject.GLObject.destroy` + method of all objects in the queue. """ + + for obj in self._q: obj.destroy() + for obj in self._holdq: obj.destroy() + self._q = [] self._holdq = [] - def draw(self, xax, yax, zax, zpos): + def draw(self, zpos, xform=None): """Draws all enqueued annotations. - :arg xax: Data axis which corresponds to the horizontal screen axis. - - :arg yax: Data axis which corresponds to the vertical screen axis. - - :arg zax: Data axis which corresponds to the depth screen axis. - - :arg zpos: Position along the Z axis, above which all annotations - should be drawn. - """ + :arg zpos: Position along the Z axis, above which all annotations + should be drawn. - gl.glEnableClientState(gl.GL_VERTEX_ARRAY) + :arg xform: Transformation matrix which should be applied to all + objects. + """ objs = self._holdq + self._q - for obj in objs: - - verts, indices = obj.vertices(xax, yax, zax, zpos) + if xform is not None: + gl.glMatrixMode(gl.GL_MODELVIEW) + gl.glPushMatrix() + gl.glMultMatrixf(xform.ravel('C')) - verts[:, zax] = zpos + for obj in objs: - verts = np.array(verts, dtype=np.float32).ravel('C') - indices = np.array(indices, dtype=np.uint32) + if not obj.ready(): + continue + + obj.setAxes(self._xax, self._yax) if obj.xform is not None: gl.glMatrixMode(gl.GL_MODELVIEW) gl.glPushMatrix() gl.glMultMatrixf(obj.xform.ravel('C')) - gl.glColor4f(*self._adjustColour(obj.colour)) - gl.glLineWidth(obj.width) + if obj.colour is not None: + + if len(obj.colour) == 3: colour = list(obj.colour) + [1.0] + else: colour = list(obj.colour) + gl.glColor4f(*colour) - gl.glVertexPointer(3, gl.GL_FLOAT, 0, verts) + if obj.width is not None: + gl.glLineWidth(obj.width) - gl.glDrawElements(gl.GL_LINES, - len(indices), - gl.GL_UNSIGNED_INT, - indices) + obj.preDraw() + obj.draw(zpos) + obj.postDraw() if obj.xform is not None: - gl.glPopMatrix() - - gl.glDisableClientState(gl.GL_VERTEX_ARRAY) + gl.glPopMatrix() + + if xform is not None: + gl.glMatrixMode(gl.GL_MODELVIEW) + gl.glPopMatrix() self._q = [] -class AnnotationObject(object): - """Superclass for all annotation objects. Subclasses must override the - :meth:`vertices` method. +class AnnotationObject(globject.GLSimpleObject): + """Superclass for all annotation objects. Subclasses must, at the very + least override, the :meth:`globject.GLObject.draw` method. """ def __init__(self, xform=None, colour=None, width=None): - """Create an AnnotationObject. + """Create an ``AnnotationObject``. :arg xform: Transformation matrix which will be applied to all vertex coordinates. @@ -177,37 +204,15 @@ class AnnotationObject(object): :arg width: Line width to use for the annotation. """ - if colour is None: colour = (1, 1, 1, 1) - if width is None: width = 1 - self.colour = colour self.width = width self.xform = xform - - def vertices(self, xax, yax, zax, zpos): - """Generate/return vertices to render this :class:`AnnotationObject`. - - This method must be overridden by subclasses, and must return two - values: - - - A 2D ``float32`` numpy array of shape ``(N, 3)`` (where ``N`` is - the number of vertices), containing the xyz coordinates of every - vertex. + def preDraw(self): + gl.glEnableClientState(gl.GL_VERTEX_ARRAY) - - A 1D ``uint32`` numpy array containing the indices of all - vertices to be rendered. - - :arg xax: The axis which corresponds to the horizontal screen axis. - - :arg yax: The axis which corresponds to the horizontal screen axis. - - :arg zax: The axis which corresponds to the depth screen axis. - - :arg zpos: The position along the depth axis. - """ - raise NotImplementedError('Subclasses must implement ' - 'the vertices method') + def postDraw(self): + gl.glDisableClientState(gl.GL_VERTEX_ARRAY) class Line(AnnotationObject): @@ -226,16 +231,23 @@ class Line(AnnotationObject): """ AnnotationObject.__init__(self, *args, **kwargs) - self.xy1 = xy1 - self.xy2 = xy2 + self.xy1 = xy1 + self.xy2 = xy2 - - def vertices(self, xax, yax, zax, zpos): - verts = np.zeros((2, 3)) + + def draw(self, zpos): + xax = self.xax + yax = self.yax + zax = self.zax + + idxs = np.arange(2, dtype=np.uint32) + verts = np.zeros((2, 3), dtype=np.float32) verts[0, [xax, yax]] = self.xy1 verts[1, [xax, yax]] = self.xy2 - - return verts, np.arange(2) + verts[:, zax] = zpos + + gl.glVertexPointer(3, gl.GL_FLOAT, 0, verts.ravel('C')) + gl.glDrawElements(gl.GL_LINES, len(idxs), gl.GL_UNSIGNED_INT, idxs) class Rect(AnnotationObject): @@ -253,18 +265,22 @@ class Rect(AnnotationObject): self.h = h - def vertices(self, xax, yax, zax, zpos): + def draw(self, zpos): - xy = self.xy - w = self.w - h = self.h + xax = self.xax + yax = self.yax + zax = self.zax + xy = self.xy + w = self.w + h = self.h bl = [xy[0], xy[1]] br = [xy[0] + w, xy[1]] tl = [xy[0], xy[1] + h] tr = [xy[0] + w, xy[1] + h] - verts = np.zeros((8, 3)) + idxs = np.arange(8, dtype=np.uint32) + verts = np.zeros((8, 3), dtype=np.float32) verts[0, [xax, yax]] = bl verts[1, [xax, yax]] = br verts[2, [xax, yax]] = tl @@ -273,8 +289,10 @@ class Rect(AnnotationObject): verts[5, [xax, yax]] = tl verts[6, [xax, yax]] = br verts[7, [xax, yax]] = tr + verts[:, zax] = zpos - return verts, np.arange(8) + gl.glVertexPointer(3, gl.GL_FLOAT, 0, verts.ravel('C')) + gl.glDrawElements(gl.GL_LINES, len(idxs), gl.GL_UNSIGNED_INT, idxs) class VoxelSelection(AnnotationObject): @@ -327,7 +345,11 @@ class VoxelSelection(AnnotationObject): self.offsets = offsets - def vertices(self, xax, yax, zax, zpos): + def draw(self, zpos): + + xax = self.xax + yax = self.yax + zax = self.zax dispLoc = [0] * 3 dispLoc[zax] = zpos @@ -348,7 +370,10 @@ class VoxelSelection(AnnotationObject): off = 0 voxels[:, ax] += off + self.offsets[ax] - return globject.voxelGrid(voxels, xax, yax, 1, 1) + verts, idxs = globject.voxelGrid(voxels, xax, yax, 1, 1) + + gl.glVertexPointer(3, gl.GL_FLOAT, 0, verts.ravel('C')) + gl.glDrawElements(gl.GL_LINES, len(idxs), gl.GL_UNSIGNED_INT, idxs) # class Text(AnnotationObject) ? # class Circle(AnnotationObject) ? diff --git a/fsl/fslview/gl/globject.py b/fsl/fslview/gl/globject.py index ded626cc3..7ada3f91a 100644 --- a/fsl/fslview/gl/globject.py +++ b/fsl/fslview/gl/globject.py @@ -128,6 +128,42 @@ class GLObject(object): raise NotImplementedError() +class GLSimpleObject(GLObject): + """The ``GLSimpleObject`` class is a convenience superclass for simple + rendering tasks (probably fixed-function) which require no setup or + initialisation/management of GL memory or state. All subclasses need to + do is implement the :meth:`GLObject.draw` method. + + Subclasses should not assume that any of the other methods will ever + be called. + + On calls to :meth:`draw`, the following attributes will be available on + ``GLSimpleObject`` instances: + + - ``xax``: Index of the display coordinate system axis that corresponds + to the horizontal screen axis. + - ``yax``: Index of the display coordinate system axis that corresponds + to the vertical screen axis. + """ + + def __init__(self): + GLObject.__init__(self) + self.__ready = False + + def init( self): pass + def destroy(self): pass + def ready( self): return self.__ready + + def setAxes(self, xax, yax): + self.xax = xax + self.yax = yax + self.zax = 3 - xax - yax + self.__ready = True + + def preDraw( self): pass + def postDraw(self): pass + + class GLImageObject(GLObject): """The ``GLImageObject` class is the superclass for all GL representations of :class:`~fsl.data.image.Image` instances. @@ -249,7 +285,7 @@ def calculateSamplePoints(image, display, xax, yax): worldX, worldY = np.meshgrid(worldX, worldY) - coords = np.zeros((worldX.size, 3)) + coords = np.zeros((worldX.size, 3), dtype=np.float32) coords[:, xax] = worldX.flatten() coords[:, yax] = worldY.flatten() @@ -465,7 +501,8 @@ def voxelGrid(points, xax, yax, xpixdim, ypixdim): indices = np.array([0, 1, 0, 2, 1, 3, 2, 3], dtype=np.uint32) indices = np.tile(indices, npoints) - indices = (indices.T + np.repeat(np.arange(0, npoints * 4, 4), 8)).T + indices = (indices.T + + np.repeat(np.arange(0, npoints * 4, 4, dtype=np.uint32), 8)).T return vertices, indices diff --git a/fsl/fslview/gl/lightboxcanvas.py b/fsl/fslview/gl/lightboxcanvas.py index 185c1ec05..524502f34 100644 --- a/fsl/fslview/gl/lightboxcanvas.py +++ b/fsl/fslview/gl/lightboxcanvas.py @@ -654,6 +654,6 @@ class LightBoxCanvas(slicecanvas.SliceCanvas): if self.showGridLines: self._drawGridLines() if self.highlightSlice: self._drawSliceHighlight() - self.getAnnotations().draw(self.xax, self.yax, self.zax, self.pos.z) + self.getAnnotations().draw(self.pos.z) self._postDraw() diff --git a/fsl/fslview/gl/slicecanvas.py b/fsl/fslview/gl/slicecanvas.py index c2f8e3ade..1ba2bb1be 100644 --- a/fsl/fslview/gl/slicecanvas.py +++ b/fsl/fslview/gl/slicecanvas.py @@ -271,9 +271,8 @@ class SliceCanvas(props.HasProperties): self.xax = (zax + 1) % 3 self.yax = (zax + 2) % 3 - self._annotations = annotations.Annotations() - - self._zAxisChanged() + self._annotations = annotations.Annotations(self.xax, self.yax) + self._zAxisChanged() # when any of the properties of this # canvas change, we need to redraw @@ -332,6 +331,8 @@ class SliceCanvas(props.HasProperties): self.xax = dims[0] self.yax = dims[1] + self._annotations.setAxes(self.xax, self.yax) + for image in self.imageList: try: glData = image.getAttribute(self.name) @@ -719,5 +720,5 @@ class SliceCanvas(props.HasProperties): if self.showCursor: self._drawCursor() - self._annotations.draw(self.xax, self.yax, self.zax, self.pos.z) + self._annotations.draw(self.pos.z) self._postDraw() -- GitLab