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

Documentation for gl/annotations, gl/resources, gl/routines, and gl/shaders.

parent 5b0797e8
No related branches found
No related tags found
No related merge requests found
...@@ -7,45 +7,60 @@ ...@@ -7,45 +7,60 @@
"""This module provides the :class:`Annotations` class, which implements """This module provides the :class:`Annotations` class, which implements
functionality to draw 2D OpenGL annotations on a canvas functionality to draw 2D OpenGL annotations on a canvas
The :class:`Annotations` class is used by the :class:`.SliceCanvas` class, and
users of that class, to annotate the canvas. The :class:`Annotations` class is used by the :class:`.SliceCanvas` and
:class:`.LightBoxCanvas` classes, and users of those class, to annotate the
canvas.
All annotations derive from the :class:`AnnotationObject` base class. The
following annotation types are defined:
.. autosummary::
:nosignatures:
Line
Rect
VoxelGrid
VoxelSelection
""" """
import logging
log = logging.getLogger(__name__)
import logging
import numpy as np import numpy as np
import OpenGL.GL as gl import OpenGL.GL as gl
import fsl.fsleyes.gl.globject as globject import fsl.fsleyes.gl.globject as globject
import fsl.fsleyes.gl.routines as glroutines import fsl.fsleyes.gl.routines as glroutines
import fsl.fsleyes.gl.textures as textures import fsl.fsleyes.gl.textures as textures
import fsl.utils.transform as transform import fsl.utils.transform as transform
log = logging.getLogger(__name__)
class Annotations(object): class Annotations(object):
"""An :class:`Annotations` object provides functionality to draw 2D """An :class:`Annotations` object provides functionality to draw 2D
annotations on a 3D OpenGL canvas. Annotations may be enqueued via any annotations on a :class:`.SliceCanvas`. Annotations may be enqueued via
of the :meth:`line`, :meth:`rect`, :meth:`selection` or :meth:`obj`, any of the :meth:`line`, :meth:`rect`, :meth:`grid`, :meth:`selection` or
methods. :meth:`obj`, methods.
A call to :meth:`draw` will then draw each of the queued annotations,
and clear the queue. A call to :meth:`draw` will then draw each of the queued annotations on
the canvas, and clear the queue.
If an annotation is to be persistent, it can be enqueued, as above, but
If an annotation is to be persisted, it can be enqueued, as above, but
passing ``hold=True`` to the queueing method. The annotation will then passing ``hold=True`` to the queueing method. The annotation will then
remain in the queue until it is removed via :meth:`dequeue`, or the remain in the queue until it is removed via :meth:`dequeue`, or the
entire annotations queue is cleared via :meth:`clear`. entire annotations queue is cleared via :meth:`clear`.
Annotations can be queued by one of the helper methods on the Annotations can be queued by one of the helper methods on the
:class:`Annotations` object (e.g. :meth:`line`, :meth:`rect` or :class:`Annotations` object (e.g. :meth:`line` or :meth:`rect`), or by
:meth:`selection`), or by manually creating an :class:`AnnotationObject` manually creating an :class:`AnnotationObject` and passing it to the
and passing it to the :meth:`obj` method. :meth:`obj` method.
The :class:`AnnotationObject` defines a set of parameters which are
shared by all annotations (e.g. colour and linewidth).
""" """
...@@ -59,10 +74,10 @@ class Annotations(object): ...@@ -59,10 +74,10 @@ class Annotations(object):
to the horizontal screen axis. to the horizontal screen axis.
""" """
self._q = [] self.__q = []
self._holdq = [] self.__holdq = []
self._xax = xax self.__xax = xax
self._yax = yax self.__yax = yax
def setAxes(self, xax, yax): def setAxes(self, xax, yax):
...@@ -70,11 +85,11 @@ class Annotations(object): ...@@ -70,11 +85,11 @@ class Annotations(object):
:meth:`__init__`. :meth:`__init__`.
""" """
self._xax = xax self.__xax = xax
self._yax = yax self.__yax = yax
for obj in self._q: obj.setAxes(xax, yax) for obj in self.__q: obj.setAxes(xax, yax)
for obj in self._holdq: obj.setAxes(xax, yax) for obj in self.__holdq: obj.setAxes(xax, yax)
def line(self, *args, **kwargs): def line(self, *args, **kwargs):
...@@ -91,15 +106,14 @@ class Annotations(object): ...@@ -91,15 +106,14 @@ class Annotations(object):
def grid(self, *args, **kwargs): def grid(self, *args, **kwargs):
"""Queues a selection for drawing - see the :class:`VoxelSelection` """Queues a voxel grid for drawing - see the :class:`VoxelGrid` class.
class.
""" """
hold = kwargs.pop('hold', False) hold = kwargs.pop('hold', False)
return self.obj(VoxelGrid(*args, **kwargs), hold) return self.obj(VoxelGrid(*args, **kwargs), hold)
def selection(self, *args, **kwargs): def selection(self, *args, **kwargs):
"""Queues a mask for drawing - see the :class:`VoxelMask` """Queues a selection for drawing - see the :class:`VoxelSelection`
class. class.
""" """
hold = kwargs.pop('hold', False) hold = kwargs.pop('hold', False)
...@@ -107,42 +121,47 @@ class Annotations(object): ...@@ -107,42 +121,47 @@ class Annotations(object):
def obj(self, obj, hold=False): def obj(self, obj, hold=False):
"""Queues the given :class:`AnnotationObject` for drawing.""" """Queues the given :class:`AnnotationObject` for drawing.
:arg hold: If ``True``, the given ``AnnotationObject`` will be kept in
the queue until it is explicitly removed. Otherwise (the
default), the object will be removed from the queue after
it has been drawn.
"""
if hold: self._holdq.append(obj) if hold: self.__holdq.append(obj)
else: self._q .append(obj) else: self.__q .append(obj)
obj.setAxes(self._xax, self._yax) obj.setAxes(self.__xax, self.__yax)
return obj return obj
def dequeue(self, obj, hold=False): def dequeue(self, obj, hold=False):
"""Removes the given :class:`AnnotationObject` from the queue, but does """Removes the given :class:`AnnotationObject` from the queue, but
not call its :meth:`.GLObject.destroy` method - this is the does not call its :meth:`.GLObject.destroy` method - this is the
responsibility of the caller. responsibility of the caller.
""" """
if hold: if hold:
try: self._holdq.remove(obj) try: self.__holdq.remove(obj)
except: pass except: pass
else: else:
try: self._q.remove(obj) try: self.__q.remove(obj)
except: pass except: pass
def clear(self): def clear(self):
"""Clears both the normal queue and the persistent (a.k.a. ``hold``) """Clears both the normal queue and the persistent (a.k.a. ``hold``)
queue, and calls the :meth:`.GLObject.destroy` method of all objects queue, and calls the :meth:`.GLObject.destroy` method on every object
in the queue. in the queue.
""" """
for obj in self._q: obj.destroy() for obj in self.__q: obj.destroy()
for obj in self._holdq: obj.destroy() for obj in self.__holdq: obj.destroy()
self._q = [] self.__q = []
self._holdq = [] self.__holdq = []
def draw(self, zpos, xform=None, skipHold=False): def draw(self, zpos, xform=None, skipHold=False):
...@@ -158,8 +177,8 @@ class Annotations(object): ...@@ -158,8 +177,8 @@ class Annotations(object):
items. items.
""" """
if not skipHold: objs = self._holdq + self._q if not skipHold: objs = self.__holdq + self.__q
else: objs = self._q else: objs = self.__q
if xform is not None: if xform is not None:
gl.glMatrixMode(gl.GL_MODELVIEW) gl.glMatrixMode(gl.GL_MODELVIEW)
...@@ -168,7 +187,7 @@ class Annotations(object): ...@@ -168,7 +187,7 @@ class Annotations(object):
for obj in objs: for obj in objs:
obj.setAxes(self._xax, self._yax) obj.setAxes(self.__xax, self.__yax)
if obj.xform is not None: if obj.xform is not None:
gl.glMatrixMode(gl.GL_MODELVIEW) gl.glMatrixMode(gl.GL_MODELVIEW)
...@@ -198,12 +217,23 @@ class Annotations(object): ...@@ -198,12 +217,23 @@ class Annotations(object):
gl.glMatrixMode(gl.GL_MODELVIEW) gl.glMatrixMode(gl.GL_MODELVIEW)
gl.glPopMatrix() gl.glPopMatrix()
self._q = [] # Clear the regular queue after each draw
self.__q = []
class AnnotationObject(globject.GLSimpleObject): class AnnotationObject(globject.GLSimpleObject):
"""Superclass for all annotation objects. Subclasses must, at the very """Base class for all annotation objects. An ``AnnotationObject`` is drawn
least override, the :meth:`globject.GLObject.draw` method. by an :class:`Annotations` instance. The ``AnnotationObject`` contains some
attributes which are common to all annotation types:
========== =============================================================
``colour`` Annotation colour
``width`` Annotation line width (if the annotation is made up of lines)
``xform`` Custom transformation matrix to apply to annotation vertices.
========== =============================================================
Subclasses must, at the very least, override the
:meth:`globject.GLObject.draw` method.
""" """
def __init__(self, xform=None, colour=None, width=None): def __init__(self, xform=None, colour=None, width=None):
...@@ -212,7 +242,7 @@ class AnnotationObject(globject.GLSimpleObject): ...@@ -212,7 +242,7 @@ class AnnotationObject(globject.GLSimpleObject):
:arg xform: Transformation matrix which will be applied to all :arg xform: Transformation matrix which will be applied to all
vertex coordinates. vertex coordinates.
:arg colour: RGB/RGBA tuple specifying the annotation. :arg colour: RGB/RGBA tuple specifying the annotation colour.
:arg width: Line width to use for the annotation. :arg width: Line width to use for the annotation.
""" """
...@@ -224,27 +254,34 @@ class AnnotationObject(globject.GLSimpleObject): ...@@ -224,27 +254,34 @@ class AnnotationObject(globject.GLSimpleObject):
if self.xform is not None: if self.xform is not None:
self.xform = np.array(self.xform, dtype=np.float32) self.xform = np.array(self.xform, dtype=np.float32)
def preDraw(self): def preDraw(self):
gl.glEnableClientState(gl.GL_VERTEX_ARRAY) gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
def postDraw(self): def postDraw(self):
gl.glDisableClientState(gl.GL_VERTEX_ARRAY) gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
class Line(AnnotationObject): class Line(AnnotationObject):
"""Annotation object which represents a 2D line. """The ``Line`` class is an :class:`AnnotationObject` which represents a
2D line.
""" """
def __init__(self, xy1, xy2, *args, **kwargs): def __init__(self, xy1, xy2, *args, **kwargs):
"""Create a :class:`Line`. The (x, y) coordinate tuples should be in """Create a ``Line`` annotation.
relation to the axes which map to the horizontal/vertical screen axes
on the target canvas. 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 xy1: Tuple containing the (x, y) coordinates of one endpoint. :arg xy1: Tuple containing the (x, y) coordinates of one endpoint.
:arg xy2: Tuple containing the (x, y) coordinates of the second :arg xy2: Tuple containing the (x, y) coordinates of the second
endpoint. endpoint.
All other arguments are passed through to
:meth:`AnnotationObject.__init__`.
""" """
AnnotationObject.__init__(self, *args, **kwargs) AnnotationObject.__init__(self, *args, **kwargs)
self.xy1 = xy1 self.xy1 = xy1
...@@ -252,6 +289,7 @@ class Line(AnnotationObject): ...@@ -252,6 +289,7 @@ class Line(AnnotationObject):
def draw(self, zpos): def draw(self, zpos):
"""Draws this ``Line`` annotation. """
xax = self.xax xax = self.xax
yax = self.yax yax = self.yax
zax = self.zax zax = self.zax
...@@ -267,13 +305,20 @@ class Line(AnnotationObject): ...@@ -267,13 +305,20 @@ class Line(AnnotationObject):
class Rect(AnnotationObject): class Rect(AnnotationObject):
"""Annotation object which represents a 2D rectangle.""" """The ``Rect`` class is an :class:`AnnotationObject` which represents a
2D rectangle.
"""
def __init__(self, xy, w, h, *args, **kwargs): def __init__(self, xy, w, h, *args, **kwargs):
"""Create a :class:`Rect` annotation. The `xy` parameter should """Create a :class:`Rect` annotation.
be a tuple specifying the bottom left of the rectangle, and the `w`
and `h` parameters specifying the rectangle width and height :arg xy: Tuple specifying bottom left of the rectangle, in the display
respectively. 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, *args, **kwargs)
self.xy = xy self.xy = xy
...@@ -282,7 +327,8 @@ class Rect(AnnotationObject): ...@@ -282,7 +327,8 @@ class Rect(AnnotationObject):
def draw(self, zpos): def draw(self, zpos):
"""Draws this ``Rectangle`` annotation. """
if self.w == 0 or self.h == 0: if self.w == 0 or self.h == 0:
return return
...@@ -315,7 +361,9 @@ class Rect(AnnotationObject): ...@@ -315,7 +361,9 @@ class Rect(AnnotationObject):
class VoxelGrid(AnnotationObject): class VoxelGrid(AnnotationObject):
"""Annotation object which represents a collection of 'selected' voxels. """The ``VoxelGrid`` is an :class:`AnnotationObject` which represents a
collection of selected voxels. See also the :class:`VoxelSelection`
annotation.
Each selected voxel is highlighted with a rectangle around its border. Each selected voxel is highlighted with a rectangle around its border.
""" """
...@@ -328,7 +376,7 @@ class VoxelGrid(AnnotationObject): ...@@ -328,7 +376,7 @@ class VoxelGrid(AnnotationObject):
offsets=None, offsets=None,
*args, *args,
**kwargs): **kwargs):
"""Create a :class:`VoxelSelection` object. """Create a ``VoxelGrid`` annotation.
:arg selectMask: A 3D numpy array, the same shape as the image :arg selectMask: A 3D numpy array, the same shape as the image
being annotated (or a sub-space of the image - being annotated (or a sub-space of the image -
...@@ -365,6 +413,7 @@ class VoxelGrid(AnnotationObject): ...@@ -365,6 +413,7 @@ class VoxelGrid(AnnotationObject):
def draw(self, zpos): def draw(self, zpos):
"""Draws this ``VoxelGrid`` annotation. """
xax = self.xax xax = self.xax
yax = self.yax yax = self.yax
...@@ -395,11 +444,13 @@ class VoxelGrid(AnnotationObject): ...@@ -395,11 +444,13 @@ class VoxelGrid(AnnotationObject):
gl.glDrawElements(gl.GL_LINES, len(idxs), gl.GL_UNSIGNED_INT, idxs) gl.glDrawElements(gl.GL_LINES, len(idxs), gl.GL_UNSIGNED_INT, idxs)
class VoxelSelection(AnnotationObject): class VoxelSelection(AnnotationObject):
""" """A ``VoxelSelection`` is an :class:`AnnotationObject` which draws
selected voxels from a :class:`.Selection` instance. A
:class:`.SelectionTexture` is used to draw the selected voxels.
""" """
def __init__(self, def __init__(self,
selection, selection,
displayToVoxMat, displayToVoxMat,
...@@ -407,6 +458,30 @@ class VoxelSelection(AnnotationObject): ...@@ -407,6 +458,30 @@ class VoxelSelection(AnnotationObject):
offsets=None, offsets=None,
*args, *args,
**kwargs): **kwargs):
"""Create a ``VoxelSelection`` annotation.
:arg selection: A :class:`.Selection` instance which defines
the voxels to be highlighted.
:arg displayToVoxMat: A transformation matrix which transforms from
display space coordinates into voxel space
coordinates.
:arg voxToDisplayMat: A transformation matrix which transforms from
voxel coordinates into display space
coordinates.
:arg offsets: If ``None`` (the default), the ``selection``
must have the same shape as the image data
being annotated. Alternately, you may set
``offsets`` to a sequence of three values,
which are used as offsets for the xyz voxel
values. This is to allow for a sub-space of
the full image space to be annotated.
All other arguments are passed through to the
:meth:`AnnotationObject.__init__` method.
"""
AnnotationObject.__init__(self, *args, **kwargs) AnnotationObject.__init__(self, *args, **kwargs)
...@@ -422,12 +497,17 @@ class VoxelSelection(AnnotationObject): ...@@ -422,12 +497,17 @@ class VoxelSelection(AnnotationObject):
'{}_{}'.format(type(self).__name__, id(selection)), '{}_{}'.format(type(self).__name__, id(selection)),
selection) selection)
def destroy(self): def destroy(self):
"""Must be called when this ``VoxelSelection`` is no longer needed.
Destroys the :class:`.SelectionTexture`.
"""
self.texture.destroy() self.texture.destroy()
self.texture = None self.texture = None
def draw(self, zpos): def draw(self, zpos):
"""Draws this ``VoxelSelection``."""
xax = self.xax xax = self.xax
yax = self.yax yax = self.yax
...@@ -448,18 +528,12 @@ class VoxelSelection(AnnotationObject): ...@@ -448,18 +528,12 @@ class VoxelSelection(AnnotationObject):
gl.glTexEnvf(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_MODULATE) gl.glTexEnvf(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_MODULATE)
# gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
gl.glEnableClientState(gl.GL_TEXTURE_COORD_ARRAY) gl.glEnableClientState(gl.GL_TEXTURE_COORD_ARRAY)
gl.glVertexPointer( 3, gl.GL_FLOAT, 0, verts) gl.glVertexPointer( 3, gl.GL_FLOAT, 0, verts)
gl.glTexCoordPointer(3, gl.GL_FLOAT, 0, texs) gl.glTexCoordPointer(3, gl.GL_FLOAT, 0, texs)
gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6) gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6)
# gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
gl.glDisableClientState(gl.GL_TEXTURE_COORD_ARRAY) gl.glDisableClientState(gl.GL_TEXTURE_COORD_ARRAY)
self.texture.unbindTexture() self.texture.unbindTexture()
# class Text(AnnotationObject) ?
# class Circle(AnnotationObject) ?
...@@ -4,26 +4,95 @@ ...@@ -4,26 +4,95 @@
# #
# Author: Paul McCarthy <pauldmccarthy@gmail.com> # Author: Paul McCarthy <pauldmccarthy@gmail.com>
# #
"""This module implements a simple API for managing shared OpenGL resources.
Some OpenGL resources (e.g. textures) take up a lot of memory so it makes
sense to share these resources where possible, instead of creating and
maintaining multiple copies. The API defined in this module consists of the
following functions:
import logging
log = logging.getLogger(__name__)
.. autosummary::
:nosignatures:
class _Resource(object): exists
get
set
delete
def __init__(self, key, resource):
self.key = key
self.resource = resource
self.refcount = 0
_resources = {} On creation, resources must be given a unique name, referred to as a
``key``. Subsequent accesses to the resource are performed by specifying this
key. As an example, let's say that we have a :class:`.Image` called
``myImage``::
import fsl.fsleyes.gl.resources as glresources
import fsl.fsleyes.gl.textures as gltextures
import fsl.data.image as fslimage
image = fslimage.Image('data.nii.gz')
We wish to create an :class:`.ImageTexture` which can be shared by multiple
users. All users of this texture can use the :func:`get` function to access
the texture. The first call to :func:`get` will result in the texture being
created, whereas subsequent calls will return a reference to the existing
texture, and will increase its reference count::
texture = glresources.get(
'myTexture',
gltextures.ImageTexture,
'myTexture',
image,
interp=gl.GL_LINEAR)
.. note:: Here, we have used ``'myTexture'`` as the resource key in practice,
you will need to use something that is guaranteed to be unique
throughout your application.
When a user of the texture no longer needs the texture, it must call the
:func:`delete` method. Calls to :func:`delete` will decrement the reference
count; when this count reaches zero, the texture will be destroyed::
glresources.delete('myTexture')
.. note:: This module was written for managing OpenGL :class:`.Texture`
objects, but can actually be used with any type - the only
requirement is that the type defines a method called ``destroy``,
which performs any required clean-up operations.
"""
import logging
log = logging.getLogger(__name__)
def exists(key): def exists(key):
"""Returns ``True`` if a resource with the specified key exists, ``False``
otherwise.
"""
return key in _resources return key in _resources
def get(key, createFunc=None, *args, **kwargs): def get(key, createFunc=None, *args, **kwargs):
"""Return a reference to the resource wiuh the specified key.
If no resource with the given key exists, and ``createFunc`` is not
``None``, the resource is created, registered, and returned. If
the resource does not exist, and ``createFunc`` is ``None``, a
:exc:`KeyError` is raised.
:arg key: Unique resource identifier.
:arg createFunc: If the resource does not exist, and this argument
is provided, it will be called to create the resource.
All other positional and keyword arguments will be passed through to
the ``createFunc``.
"""
r = _resources.get(key, None) r = _resources.get(key, None)
...@@ -43,6 +112,18 @@ def get(key, createFunc=None, *args, **kwargs): ...@@ -43,6 +112,18 @@ def get(key, createFunc=None, *args, **kwargs):
def set(key, resource, overwrite=False): def set(key, resource, overwrite=False):
"""Create a new resource, or update an existing one.
:arg key: Unique resource identifier.
:arg resource: The resource itself.
:arg overwrite: If ``False`` (the default), and a resource with
the specified ``key`` already exists, a :exc:`KeyError`
is raised. Otherwise, it is assumed that a resource with
the specified ``key`` exists - the existing resource is
replaced with the specified ``resource``.
"""
if (not overwrite) and (key in _resources): if (not overwrite) and (key in _resources):
raise KeyError('Resource {} already exists'.format(str(key))) raise KeyError('Resource {} already exists'.format(str(key)))
...@@ -66,6 +147,12 @@ def set(key, resource, overwrite=False): ...@@ -66,6 +147,12 @@ def set(key, resource, overwrite=False):
def delete(key): def delete(key):
"""Decrements the reference count of the resource with the specified key.
When the resource reference count reaches ``0``, the ``destroy`` method
is called on the resource.
:arg key: Unique resource identifier.
"""
r = _resources[key] r = _resources[key]
r.refcount -= 1 r.refcount -= 1
...@@ -79,3 +166,32 @@ def delete(key): ...@@ -79,3 +166,32 @@ def delete(key):
_resources.pop(key) _resources.pop(key)
r.resource.destroy() r.resource.destroy()
class _Resource(object):
"""Internal type which is used to encapsulate a resource, and the
number of active references to that resources. The following attributes
are available on a ``_Resource``:
============ ============================================================
``key`` The unique resource key.
``resource`` The resource itself.
``refcount`` Number of references to the resource (initialised to ``0``).
============ ============================================================
"""
def __init__(self, key, resource):
"""Create a ``_Resource``.
:arg key: The unique resource key.
:arg resource: The resource itself.
"""
self.key = key
self.resource = resource
self.refcount = 0
_resources = {}
"""A dictionary containing ``{key : _Resource}`` mappings for all resources
that exist.
"""
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
# #
# Author: Paul McCarthy <pauldmccarthy@gmail.com> # Author: Paul McCarthy <pauldmccarthy@gmail.com>
# #
"""This module contains a collection of miscellaneous OpenGL routines. """
import logging import logging
...@@ -19,6 +21,19 @@ log = logging.getLogger(__name__) ...@@ -19,6 +21,19 @@ log = logging.getLogger(__name__)
def show2D(xax, yax, width, height, lo, hi): def show2D(xax, yax, width, height, lo, hi):
"""Configures the OpenGL viewport for 2D othorgraphic display.
:arg xax: Index (into ``lo`` and ``hi``) of the axis which
corresponds to the horizontal screen axis.
:arg yax: Index (into ``lo`` and ``hi``) of the axis which
corresponds to the vertical screen axis.
:arg width: Canvas width in pixels.
:arg height: Canvas height in pixels.
:arg lo: Tuple containing the mininum ``(x, y, z)`` display
coordinates.
:arg hi: Tuple containing the maxinum ``(x, y, z)`` display
coordinates.
"""
zax = 3 - xax - yax zax = 3 - xax - yax
...@@ -61,7 +76,7 @@ def calculateSamplePoints(shape, resolution, xform, xax, yax, origin='centre'): ...@@ -61,7 +76,7 @@ def calculateSamplePoints(shape, resolution, xform, xax, yax, origin='centre'):
This function returns a tuple containing: This function returns a tuple containing:
- a numpy array of shape `(N, 3)`, containing the coordinates of the - a numpy array of shape ``(N, 3)``, containing the coordinates of the
centre of every sampling point in the display coordinate system. centre of every sampling point in the display coordinate system.
- the horizontal distance (along xax) between adjacent points - the horizontal distance (along xax) between adjacent points
...@@ -136,8 +151,8 @@ def samplePointsToTriangleStrip(coords, ...@@ -136,8 +151,8 @@ def samplePointsToTriangleStrip(coords,
(for example, that generated by the :func:`calculateSamplePoints` function (for example, that generated by the :func:`calculateSamplePoints` function
above), converts those points into an OpenGL vertex triangle strip. above), converts those points into an OpenGL vertex triangle strip.
A grid of M*N points is represented by M*2*(N + 1) vertices. For example, A grid of ``M*N`` points is represented by ``M*2*(N + 1)`` vertices. For
this image represents a 4*3 grid, with periods representing vertex example, this image represents a 4*3 grid, with periods representing vertex
locations:: locations::
.___.___.___.___. .___.___.___.___.
...@@ -180,7 +195,7 @@ def samplePointsToTriangleStrip(coords, ...@@ -180,7 +195,7 @@ def samplePointsToTriangleStrip(coords,
rows, between every column we add a couple of 'dummy' vertices, which will rows, between every column we add a couple of 'dummy' vertices, which will
then be interpreted by OpenGL as 'degenerate triangles', and will not be then be interpreted by OpenGL as 'degenerate triangles', and will not be
drawn. So in reality, a 4*3 slice would be drawn as follows (with vertices drawn. So in reality, a 4*3 slice would be drawn as follows (with vertices
labelled from [a-z0-9]: labelled from ``[a-z0-9]``::
v x z 1 33 v x z 1 33
|\ |\ |\ |\ | |\ |\ |\ |\ |
...@@ -204,15 +219,15 @@ def samplePointsToTriangleStrip(coords, ...@@ -204,15 +219,15 @@ def samplePointsToTriangleStrip(coords,
A tuple is returned containing: A tuple is returned containing:
- A 2D `numpy.float32` array of shape `(2 * (xlen + 1) * ylen), 3)`, - A 2D ``numpy.float32`` array of shape ``(2 * (xlen + 1) * ylen), 3)``,
containing the coordinates of all triangle strip vertices which containing the coordinates of all triangle strip vertices which
represent the entire grid of sample points. represent the entire grid of sample points.
- A 2D `numpy.float32` array of shape `(2 * (xlen + 1) * ylen), 3)`, - A 2D ``numpy.float32`` array of shape ``(2 * (xlen + 1) * ylen), 3)``,
containing the centre of every grid, to be used for texture containing the centre of every grid, to be used for texture
coordinates/colour lookup. coordinates/colour lookup.
- A 1D `numpy.uint32` array of size `ylen * (2 * (xlen + 1) + 2) - 2` - A 1D ``numpy.uint32`` array of size ``ylen * (2 * (xlen + 1) + 2) - 2``
containing indices into the first array, defining the order in which containing indices into the first array, defining the order in which
the vertices need to be rendered. There are more indices than vertex the vertices need to be rendered. There are more indices than vertex
coordinates due to the inclusion of repeated/degenerate vertices. coordinates due to the inclusion of repeated/degenerate vertices.
...@@ -354,7 +369,7 @@ def slice2D(dataShape, ...@@ -354,7 +369,7 @@ def slice2D(dataShape,
defined by the given ``voxToDisplayMat``. defined by the given ``voxToDisplayMat``.
If ``geometry`` is ``triangles`` (the default), six vertices are returned, If ``geometry`` is ``triangles`` (the default), six vertices are returned,
arranged as follows: arranged as follows::
4---5 4---5
1 \ | 1 \ |
...@@ -364,7 +379,7 @@ def slice2D(dataShape, ...@@ -364,7 +379,7 @@ def slice2D(dataShape,
0---2 0---2
Otherwise, if geometry is ``square``, four vertices are returned, arranged Otherwise, if geometry is ``square``, four vertices are returned, arranged
as follows: as follows::
3---2 3---2
...@@ -374,12 +389,17 @@ def slice2D(dataShape, ...@@ -374,12 +389,17 @@ def slice2D(dataShape,
0---1 0---1
If ``origin`` is set to ``centre`` (the default), it is assumed that If ``origin`` is set to ``centre`` (the default), it is assumed that
a voxel at location ``(x, y, z)`` is located in the space a voxel at location ``(x, y, z)`` is located in the space::
``(x - 0.5 : x + 0.5, y - 0.5 : y + 0.5, z - 0.5 : z + 0.5). Otherwise,
if ``origin`` is set to ``corner``, a voxel at location ``(x, y, z)`` (x - 0.5 : x + 0.5, y - 0.5 : y + 0.5, z - 0.5 : z + 0.5)
is assumed to be located in the space
``(x : x + 1, y : y + 1, z : z + 1)``.
Otherwise, if ``origin`` is set to ``corner``, a voxel at location ``(x,
y, z)`` is assumed to be located in the space::
(x : x + 1, y : y + 1, z : z + 1)
:arg dataShape: Number of elements along each dimension in the :arg dataShape: Number of elements along each dimension in the
image data. image data.
...@@ -575,11 +595,12 @@ def planeEquation(xyz1, xyz2, xyz3): ...@@ -575,11 +595,12 @@ def planeEquation(xyz1, xyz2, xyz3):
Returns a tuple containing four values, the coefficients of the Returns a tuple containing four values, the coefficients of the
equation: equation:
a * x + b * y + c * z = d :math:`a\\times x + b\\times y + c \\times z = d`
for any point (x, y, z) that lies on the plane. for any point ``(x, y, z)`` that lies on the plane.
See http://paulbourke.net/geometry/pointlineplane/ See http://paulbourke.net/geometry/pointlineplane/ for details on plane
equations.
""" """
x1, y1, z1 = xyz1 x1, y1, z1 = xyz1
x2, y2, z2 = xyz2 x2, y2, z2 = xyz2
......
...@@ -4,22 +4,55 @@ ...@@ -4,22 +4,55 @@
# #
# Author: Paul McCarthy <pauldmccarthy@gmail.com> # Author: Paul McCarthy <pauldmccarthy@gmail.com>
# #
"""Convenience for managing vertex and fragment shader source code. """This module defines convenience functions for managing vertex and fragment
shader source code.
The rendering code for some :class:`.GLObject` types use vertex and fragment
shader programs. These programs can be accessed and compiled with the
following functions:
.. autosummary::
:nosignatures:
getVertexShader
getFragmentShader
compilePrograms
compileShaders
Some functions are also provided for use with OpenGL 1.4, which make setting
program variables a bit less painful:
.. autosummary::
:nosignatures:
setVertexProgramVector
setVertexProgramMatrix
setFragmentProgramVector
setFragmentProgramMatrix
**Shader program file locations**
The :mod:`shaders` module provides convenience functions for accessing the
vertex and fragment shader source files used to render different types of GL
objects.
All shader programs and associated files are assumed to be located in one of All shader programs and associated files are assumed to be located in one of
the OpenGL version specific packages, i.e. :mod:`.gl14` the OpenGL version specific packages, i.e. :mod:`.gl14`
(ARB_vertex_program/ARB_fragment_program shaders) or :mod:`.gl21` (GLSL (``ARB_vertex_program``/``ARB_fragment_program`` shaders) or :mod:`.gl21`
shaders). (GLSL shaders).
**Preprocessing**
When a shader file is loaded, a simple preprocessor is applied to the source - When a shader file is loaded, a simple preprocessor is applied to the source -
any lines of the form '#pragma include filename', will be replaced with the any lines of the form '#pragma include filename', will be replaced with the
contents of the specified file. contents of the specified file.
""" """
import logging import logging
import os.path as op import os.path as op
...@@ -31,60 +64,6 @@ import fsl.utils.typedict as td ...@@ -31,60 +64,6 @@ import fsl.utils.typedict as td
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
_shaderTypePrefixMap = td.TypeDict({
('GLVolume', 'vert', False) : 'glvolume',
('GLVolume', 'vert', True) : 'glvolume_sw',
('GLVolume', 'frag', False) : 'glvolume',
('GLVolume', 'frag', True) : 'glvolume_sw',
('GLLabel', 'vert', False) : 'glvolume',
('GLLabel', 'vert', True) : 'glvolume_sw',
('GLLabel', 'frag', False) : 'gllabel',
('GLLabel', 'frag', True) : 'gllabel_sw',
('GLRGBVector', 'vert', False) : 'glvolume',
('GLRGBVector', 'vert', True) : 'glvolume_sw',
('GLRGBVector', 'frag', False) : 'glvector',
('GLRGBVector', 'frag', True) : 'glvector_sw',
('GLLineVector', 'vert', False) : 'gllinevector',
('GLLineVector', 'vert', True) : 'gllinevector_sw',
('GLLineVector', 'frag', False) : 'glvector',
('GLLineVector', 'frag', True) : 'glvector_sw',
('GLModel', 'vert', False) : 'glmodel',
('GLModel', 'vert', True) : 'glmodel',
('GLModel', 'frag', False) : 'glmodel',
('GLModel', 'frag', True) : 'glmodel',
})
"""This dictionary provides a mapping between :class:`.GLObject` types,
and file name prefixes, identifying the shader programs to use.
"""
def getShaderPrefix(globj, shaderType, sw):
"""Returns the prefix identifying the vertex/fragment shader programs to use
for the given :class:`.GLObject` instance. If ``globj`` is a string, it is
returned unchanged.
"""
if isinstance(globj, str):
return globj
return _shaderTypePrefixMap[globj, shaderType, sw]
def setShaderPrefix(globj, shaderType, sw, prefix):
"""Updates the prefix identifying the vertex/fragment shader programs to use
for the given :class:`.GLObject` type or instance.
"""
_shaderTypePrefixMap[globj, shaderType, sw] = prefix
def setVertexProgramVector(index, vector): def setVertexProgramVector(index, vector):
"""Convenience function which sets the vertex program local parameter """Convenience function which sets the vertex program local parameter
at the given index to the given 4 component vector. at the given index to the given 4 component vector.
...@@ -134,8 +113,8 @@ def setFragmentProgramMatrix(index, matrix): ...@@ -134,8 +113,8 @@ def setFragmentProgramMatrix(index, matrix):
def compilePrograms(vertexProgramSrc, fragmentProgramSrc): def compilePrograms(vertexProgramSrc, fragmentProgramSrc):
"""Compiles the given vertex and fragment programs (written according """Compiles the given vertex and fragment programs (written according
to the ARB_vertex_program and ARB_fragment_program extensions), and to the ``ARB_vertex_program`` and ``ARB_fragment_program`` extensions),
returns references to the compiled programs. and returns references to the compiled programs.
""" """
import OpenGL.GL as gl import OpenGL.GL as gl
...@@ -193,10 +172,10 @@ def compileShaders(vertShaderSrc, fragShaderSrc): ...@@ -193,10 +172,10 @@ def compileShaders(vertShaderSrc, fragShaderSrc):
programs, and returns a reference to the resulting program. Raises programs, and returns a reference to the resulting program. Raises
an error if compilation/linking fails. an error if compilation/linking fails.
I'm explicitly not using the PyOpenGL .. note:: I'm explicitly not using the PyOpenGL
:func:`OpenGL.GL.shaders.compileProgram` function, because it attempts :func:`OpenGL.GL.shaders.compileProgram` function, because it
to validate the program after compilation, which fails due to texture attempts to validate the program after compilation, which fails
data not being bound at the time of validation. due to texture data not being bound at the time of validation.
""" """
import OpenGL.GL as gl import OpenGL.GL as gl
...@@ -246,6 +225,61 @@ def getFragmentShader(globj, sw=False): ...@@ -246,6 +225,61 @@ def getFragmentShader(globj, sw=False):
return _getShader(globj, 'frag', sw) return _getShader(globj, 'frag', sw)
_shaderTypePrefixMap = td.TypeDict({
('GLVolume', 'vert', False) : 'glvolume',
('GLVolume', 'vert', True) : 'glvolume_sw',
('GLVolume', 'frag', False) : 'glvolume',
('GLVolume', 'frag', True) : 'glvolume_sw',
('GLLabel', 'vert', False) : 'glvolume',
('GLLabel', 'vert', True) : 'glvolume_sw',
('GLLabel', 'frag', False) : 'gllabel',
('GLLabel', 'frag', True) : 'gllabel_sw',
('GLRGBVector', 'vert', False) : 'glvolume',
('GLRGBVector', 'vert', True) : 'glvolume_sw',
('GLRGBVector', 'frag', False) : 'glvector',
('GLRGBVector', 'frag', True) : 'glvector_sw',
('GLLineVector', 'vert', False) : 'gllinevector',
('GLLineVector', 'vert', True) : 'gllinevector_sw',
('GLLineVector', 'frag', False) : 'glvector',
('GLLineVector', 'frag', True) : 'glvector_sw',
('GLModel', 'vert', False) : 'glmodel',
('GLModel', 'vert', True) : 'glmodel',
('GLModel', 'frag', False) : 'glmodel',
('GLModel', 'frag', True) : 'glmodel',
})
"""This dictionary provides a mapping between :class:`.GLObject` types,
and file name prefixes, identifying the shader programs to use.
"""
def _getShaderPrefix(globj, shaderType, sw):
"""Returns the prefix identifying the vertex/fragment shader programs to use
for the given :class:`.GLObject` instance. If ``globj`` is a string, it is
returned unchanged.
"""
if isinstance(globj, str):
return globj
return _shaderTypePrefixMap[globj, shaderType, sw]
def _setShaderPrefix(globj, shaderType, sw, prefix):
"""Updates the prefix identifying the vertex/fragment shader programs to use
for the given :class:`.GLObject` type or instance.
"""
_shaderTypePrefixMap[globj, shaderType, sw] = prefix
def _getShader(globj, shaderType, sw): def _getShader(globj, shaderType, sw):
"""Returns the shader source for the given GL object and the given """Returns the shader source for the given GL object and the given
shader type ('vert' or 'frag'). shader type ('vert' or 'frag').
...@@ -271,7 +305,7 @@ def _getFileName(globj, shaderType, sw): ...@@ -271,7 +305,7 @@ def _getFileName(globj, shaderType, sw):
if shaderType not in ('vert', 'frag'): if shaderType not in ('vert', 'frag'):
raise RuntimeError('Invalid shader type: {}'.format(shaderType)) raise RuntimeError('Invalid shader type: {}'.format(shaderType))
prefix = getShaderPrefix(globj, shaderType, sw) prefix = _getShaderPrefix(globj, shaderType, sw)
return op.join(op.dirname(__file__), subdir, '{}_{}.{}'.format( return op.join(op.dirname(__file__), subdir, '{}_{}.{}'.format(
prefix, shaderType, suffix)) prefix, shaderType, suffix))
......
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