class Thing(props.HasProperties):

        myobject     = props.Object()
        mybool       = props.Boolean()
        myint        = props.Int()
        myreal       = props.Real()
        mypercentage = props.Percentage()
        mystring     = props.String()
        mychoice     = props.Choice(('1', '2', '3', '4', '5'))
        myfilepath   = props.FilePath()
        mylist       = props.List()
        mycolour     = props.Colour()
        mycolourmap  = props.ColourMap()
        mybounds     = props.Bounds(ndims=2)
        mypoint      = props.Point(ndims=2)
        myarray      = props.Array()
 class MyObj(props.HasProperties):
     val = props.String()
示例#3
0
class LutLabel(props.HasProperties):
    """This class represents a mapping from a value to a colour and name.
    ``LutLabel`` instances are created and managed by :class:`LookupTable`
    instances.

    Listeners may be registered on the :attr:`name`, :attr:`colour`, and
    :attr:`enabled` properties to be notified when they change.
    """

    name = props.String(default='Label')
    """The display name for this label. Internally (for comparison), the
    :meth:`internalName` is used, which is simply this name, converted to
    lower case.
    """

    colour = props.Colour(default=(0, 0, 0))
    """The colour for this label. """

    enabled = props.Boolean(default=True)
    """Whether this label is currently enabled or disabled. """
    def __init__(self, value, name=None, colour=None, enabled=None):
        """Create a ``LutLabel``.

        :arg value:   The label value.
        :arg name:    The label name.
        :arg colour:  The label colour.
        :arg enabled: Whether the label is enabled/disabled.
        """

        if value is None:
            raise ValueError('LutLabel value cannot be None')

        if name is None:
            name = LutLabel.getProp('name').getAttribute(None, 'default')

        if colour is None:
            colour = LutLabel.getProp('colour').getAttribute(None, 'default')

        if enabled is None:
            enabled = LutLabel.getProp('enabled').getAttribute(None, 'default')

        self.__value = value
        self.name = name
        self.colour = colour
        self.enabled = enabled

    @property
    def value(self):
        """Returns the value of this ``LutLabel``. """
        return self.__value

    @property
    def internalName(self):
        """Returns the *internal* name of this ``LutLabel``, which is just
        its :attr:`name`, converted to lower-case. This is used by
        :meth:`__eq__` and :meth:`__hash__`, and by the
        :class:`LookupTable` class.
        """
        return self.name.lower()

    def __eq__(self, other):
        """Equality operator - returns ``True`` if this ``LutLabel``
        has the same  value as the given one.
        """

        return self.value == other.value

    def __lt__(self, other):
        """Less-than operator - compares two ``LutLabel`` instances
        based on their value.
        """
        return self.value < other.value

    def __hash__(self):
        """The hash of a ``LutLabel`` is a combination of its
        value, name, and colour, but not its enabled state.
        """
        return (hash(self.value) ^ hash(self.internalName) ^ hash(self.colour))

    def __str__(self):
        """Returns a string representation of this ``LutLabel``."""
        return '{}: {} / {} ({})'.format(self.value, self.internalName,
                                         self.colour, self.enabled)

    def __repr__(self):
        """Returns a string representation of this ``LutLabel``."""
        return self.__str__()
示例#4
0
class DataSeries(props.HasProperties):
    """A ``DataSeries`` instance encapsulates some data to be plotted by
    a :class:`PlotPanel`, with the data extracted from an overlay in the
    :class:`.OverlayList`.

    Sub-class implementations must:

      - Accept an overlay object, :class:`.OverlayList`,
        :class:`.DisplayContext`, and :class:`.PlotPanel` in their
        ``__init__`` method, and pass these through to
        :meth:`.DataSeries.__init__`.
      - Override the :meth:`getData` method
      - Override the :meth:`redrawProperties` method if necessary


    The overlay is accessible as an instance attribute, confusingly called
    ``overlay``.


    .. note:: Some ``DataSeries`` instances may not be associated with
              an overlay (e.g. series imported loaded a text file). In
              this case, the ``overlay`` attribute will be ``None``.


    Each``DataSeries`` instance is plotted as a line, with the line
    style defined by properties on the ``DataSeries`` instance,
    such as :attr:`colour`, :attr:`lineWidth` etc.
    """

    colour = props.Colour()
    """Line colour. """

    enabled = props.Boolean(default=True)
    """Draw or not draw?"""

    alpha = props.Real(minval=0.0, maxval=1.0, default=1.0, clamped=True)
    """Line transparency."""

    label = props.String()
    """Line label (used in the plot legend)."""

    lineWidth = props.Choice((0.5, 1, 2, 3, 4, 5))
    """Line width. """

    lineStyle = props.Choice(
        ('-', '--', '-.', ':', (0, (5, 7)), (0, (1, 7)), (0, (4, 10, 1, 10)),
         (0, (4, 1, 1, 1, 1, 1)), (0, (4, 1, 4, 1, 1, 1))))
    """Line style. See
    https://matplotlib.org/gallery/lines_bars_and_markers/linestyles.html
    """
    def __init__(self, overlay, overlayList, displayCtx, plotPanel):
        """Create a ``DataSeries``.

        :arg overlay:     The overlay from which the data to be plotted is
                          retrieved.  May be ``None``.
        :arg overlayList: The :class:`.OverlayList` instance.
        :arg displayCtx:  The :class:`.DisplayContext` instance.
        :arg plotPanel:   The :class:`.PlotPanel` that owns this
                          ``DataSeries``.
        """

        self.__name = '{}_{}'.format(type(self).__name__, id(self))
        self.__overlay = overlay
        self.__overlayList = overlayList
        self.__displayCtx = displayCtx
        self.__plotPanel = plotPanel
        self.setData([], [])

        log.debug('{}.init ({})'.format(type(self).__name__, id(self)))

    def __del__(self):
        """Prints a log message. """
        if log:
            log.debug('{}.del ({})'.format(type(self).__name__, id(self)))

    def __hash__(self):
        """Returns a hash for this ``DataSeries`` instance."""
        return hash(id(self))

    @property
    def name(self):
        """Returns a unique name for this ``DataSeries`` instance. """
        return self.__name

    @property
    def overlay(self):
        """Returns the overlay associated with this ``DataSeries`` instance.
        """
        return self.__overlay

    @property
    def overlayList(self):
        """Returns the :class:`.OverlayList`.
        """
        return self.__overlayList

    @property
    def displayCtx(self):
        """Returns the :class:`.DisplayContext`.
        """
        return self.__displayCtx

    @property
    def plotPanel(self):
        """Returns the :class:`.PlotPanel` that owns this ``DataSeries``
        instance.
        """
        return self.__plotPanel

    def destroy(self):
        """This method must be called when this ``DataSeries`` instance is no
        longer needed. This implementation may be overridden by sub-classes
        which need to perform any clean-up operations. Sub-class
        implementations should call this implementation.
        """
        self.__overlay = None
        self.__overlayList = None
        self.__displayCtx = None
        self.__plotPanel = None

    def redrawProperties(self):
        """Returns a list of all properties which, when their values change,
        should result in this ``DataSeries`` being re-plotted. This method
        may be overridden by sub-classes.
        """

        return self.getAllProperties()[0]

    def extraSeries(self):
        """Some ``DataSeries`` types have additional ``DataSeries`` associated
        with them (see e.g. the :class:`.FEATTimeSeries` class). This method
        can be overridden to return a list of these extra ``DataSeries``
        instances. The default implementation returns an empty list.
        """
        return []

    def setData(self, xdata, ydata):
        """Set the data to be plotted. This method is irrelevant if a
        ``DataSeries`` sub-class has overridden :meth:`getData`.
        """
        self.__xdata = xdata
        self.__ydata = ydata

    def getData(self):
        """This method should be overridden by sub-classes. It must return
        the data to be plotted, as a tuple of the form:

            ``(xdata, ydata)``

        where ``xdata`` and ``ydata`` are sequences containing the x/y data
        to be plotted.

        The default implementation returns the data that has been set via the
        :meth:`setData` method.
        """
        return self.__xdata, self.__ydata
示例#5
0
class DataSeries(props.HasProperties):
    """A ``DataSeries`` instance encapsulates some data to be plotted by
    a :class:`PlotPanel`, with the data extracted from an overlay in the
    :class:`.OverlayList`.

    Sub-class implementations must:

      - Accept an overlay object in their ``__init__`` method
      - Pass this overlay to meth:`.DataSeries.__init__`
      - Override the :meth:`getData` method
      - Override the :meth:`redrawProperties` method if necessary


    The overlay is accessible as an instance attribute, confusingly called
    ``overlay``.


    .. note:: Some ``DataSeries`` instances may not be associated with
              an overlay (e.g. series imported loaded a text file). In
              this case, the ``overlay`` attribute will be ``None``.


    Each``DataSeries`` instance is plotted as a line, with the line
    style defined by properties on the ``DataSeries`` instance,
    such as :attr:`colour`, :attr:`lineWidth` etc.
    """

    colour = props.Colour()
    """Line colour. """

    enabled = props.Boolean(default=True)
    """Draw or not draw?"""

    alpha = props.Real(minval=0.0, maxval=1.0, default=1.0, clamped=True)
    """Line transparency."""

    label = props.String()
    """Line label (used in the plot legend)."""

    lineWidth = props.Choice((0.5, 1, 2, 3, 4, 5))
    """Line width. """

    lineStyle = props.Choice(('-', '--', '-.', ':'))
    """Line style. """
    def __init__(self, overlay):
        """Create a ``DataSeries``.

        :arg overlay: The overlay from which the data to be plotted is
                      retrieved.  May be ``None``.
        """

        self.__overlay = overlay
        self.setData([], [])

        log.debug('{}.init ({})'.format(type(self).__name__, id(self)))

    def __del__(self):
        """Prints a log message. """
        if log:
            log.debug('{}.del ({})'.format(type(self).__name__, id(self)))

    def __hash__(self):
        """Returns a hash for this ``DataSeries`` instance."""
        return hash(id(self))

    @property
    def overlay(self):
        """Returns the overlay associated with this ``DataSeries`` instance.
        """
        return self.__overlay

    def destroy(self):
        """This method must be called when this ``DataSeries`` instance is no
        longer needed. This implementation does nothing, but it should be
        overridden by sub-classes which need to perform any clean-up
        operations.
        """
        pass

    def redrawProperties(self):
        """Returns a list of all properties which, when their values change,
        should result in this ``DataSeries`` being re-plotted. This method
        may be overridden by sub-classes.
        """

        return self.getAllProperties()[0]

    def setData(self, xdata, ydata):
        """Set the data to be plotted. This method is irrelevant if a
        ``DataSeries`` sub-class has overridden :meth:`getData`.
        """
        self.__xdata = xdata
        self.__ydata = ydata

    def getData(self):
        """This method should be overridden by sub-classes. It must return
        the data to be plotted, as a tuple of the form:

            ``(xdata, ydata)``

        where ``xdata`` and ``ydata`` are sequences containing the x/y data
        to be plotted.

        The default implementation returns the data that has been set via the
        :meth:`setData` method.
        """
        return self.__xdata, self.__ydata
示例#6
0
class Display(props.SyncableHasProperties):
    """The ``Display`` class contains display settings which are common to
    all overlay types.

    A ``Display`` instance is also responsible for managing a single
    :class:`DisplayOpts` instance, which contains overlay type specific
    display options. Whenever the :attr:`overlayType` property of a
    ``Display`` instance changes, the old ``DisplayOpts`` instance (if any)
    is destroyed, and a new one, of the correct type, created.
    """


    name = props.String()
    """The overlay name. """


    overlayType = props.Choice()
    """This property defines the overlay type - how the data is to be
    displayed.

    The options for this property are populated in the :meth:`__init__`
    method, from the :attr:`.displaycontext.OVERLAY_TYPES` dictionary. A
    :class:`DisplayOpts` sub-class exists for every possible value that this
    property may take.

    """

    enabled = props.Boolean(default=True)
    """Should this overlay be displayed at all? """


    alpha = props.Percentage(default=100.0)
    """Opacity - 100% is fully opaque, and 0% is fully transparent."""


    brightness = props.Percentage()
    """Brightness - 50% is normal brightness."""


    contrast   = props.Percentage()
    """Contrast - 50% is normal contrast."""


    def __init__(self,
                 overlay,
                 overlayList,
                 displayCtx,
                 parent=None,
                 overlayType=None):
        """Create a :class:`Display` for the specified overlay.

        :arg overlay:     The overlay object.

        :arg overlayList: The :class:`.OverlayList` instance which contains
                          all overlays.

        :arg displayCtx:  A :class:`.DisplayContext` instance describing how
                          the overlays are to be displayed.

        :arg parent:      A parent ``Display`` instance - see
                          :mod:`props.syncable`.

        :arg overlayType: Initial overlay type - see the :attr:`overlayType`
                          property.
        """

        self.__overlay     = overlay
        self.__overlayList = overlayList
        self.__displayCtx  = displayCtx
        self.name          = overlay.name

        # Populate the possible choices
        # for the overlayType property
        from . import getOverlayTypes

        ovlTypes    = getOverlayTypes(overlay)
        ovlTypeProp = self.getProp('overlayType')

        log.debug('Enabling overlay types for {}: '.format(overlay, ovlTypes))
        ovlTypeProp.setChoices(ovlTypes, instance=self)

        # Override the default overlay
        # type if it has been specified
        if overlayType is not None:
            self.overlayType = overlayType

        # Call the super constructor after our own
        # initialisation, in case the provided parent
        # has different property values to our own,
        # and our values need to be updated
        props.SyncableHasProperties.__init__(
            self,
            parent=parent,

            # These properties cannot be unbound, as
            # they affect the OpenGL representation.
            # The name can't be unbound either,
            # because it would be silly to allow
            # different names for the same overlay.
            nounbind=['overlayType', 'name'],

            # Initial sync state between this
            # Display and the parent Display
            # (if this Display has a parent)
            state=displayCtx.syncOverlayDisplay)

        # When the overlay type changes, the property
        # values of the DisplayOpts instance for the
        # old overlay type are stored in this dict.
        # If the overlay is later changed back to the
        # old type, its previous values are restored.
        #
        # The structure of the dictionary is:
        #
        #   { (type(DisplayOpts), propName) : propValue }
        #
        # This also applies to the case where the
        # overlay type is changed from one type to
        # a related type (e.g. from VolumeOpts to
        # LabelOpts) - the values of all common
        # properties are copied to the new
        # DisplayOpts instance.
        self.__oldOptValues = td.TypeDict()

        # Set up listeners after caling Syncable.__init__,
        # so the callbacks don't get called during
        # synchronisation
        self.addListener(
            'overlayType',
            'Display_{}'.format(id(self)),
            self.__overlayTypeChanged)

        # The __overlayTypeChanged method creates
        # a new DisplayOpts instance - for this,
        # it needs to be able to access this
        # Display instance's parent (so it can
        # subsequently access a parent for the
        # new DisplayOpts instance). Therefore,
        # we do this after calling Syncable.__init__.
        self.__displayOpts = None
        self.__overlayTypeChanged()

        log.debug('{}.init ({})'.format(type(self).__name__, id(self)))


    def __del__(self):
        """Prints a log message."""
        if log:
            log.debug('{}.del ({})'.format(type(self).__name__, id(self)))


    def destroy(self):
        """This method must be called when this ``Display`` instance
        is no longer needed.

        When a ``Display`` instance is destroyed, the corresponding
        :class:`DisplayOpts` instance is also destroyed.
        """

        if self.__displayOpts is not None:
            self.__displayOpts.destroy()

        self.removeListener('overlayType', 'Display_{}'.format(id(self)))

        self.detachAllFromParent()

        self.__displayOpts = None
        self.__overlayList = None
        self.__displayCtx  = None
        self.__overlay     = None


    @deprecated.deprecated('0.14.3', '1.0.0', 'Use overlay instead')
    def getOverlay(self):
        """Deprecated - use :meth:`overlay` instead."""
        return self.__overlay


    @property
    def overlay(self):
        """Returns the overlay associated with this ``Display`` instance."""
        return self.__overlay


    @property
    def opts(self):
        """Return the :class:`.DisplayOpts` instance associated with this
        ``Display``, which contains overlay type specific display settings.

        If a ``DisplayOpts`` instance has not yet been created, or the
        :attr:`overlayType` property no longer matches the type of the
        existing ``DisplayOpts`` instance, a new ``DisplayOpts`` instance
        is created (and the old one destroyed if necessary).

        See the :meth:`__makeDisplayOpts` method.
        """

        if (self.__displayOpts             is None) or \
           (self.__displayOpts.overlayType != self.overlayType):

            if self.__displayOpts is not None:
                self.__displayOpts.destroy()

            self.__displayOpts = self.__makeDisplayOpts()

        return self.__displayOpts


    @deprecated.deprecated('0.16.0', '1.0.0', 'Use opts instead')
    def getDisplayOpts(self):
        """Return the :class:`.DisplayOpts` instance associated with this
        ``Display``, which contains overlay type specific display settings.

        If a ``DisplayOpts`` instance has not yet been created, or the
        :attr:`overlayType` property no longer matches the type of the
        existing ``DisplayOpts`` instance, a new ``DisplayOpts`` instance
        is created (and the old one destroyed if necessary).

        See the :meth:`__makeDisplayOpts` method.
        """

        if (self.__displayOpts             is None) or \
           (self.__displayOpts.overlayType != self.overlayType):

            if self.__displayOpts is not None:
                self.__displayOpts.destroy()

            self.__displayOpts = self.__makeDisplayOpts()

        return self.__displayOpts


    def __makeDisplayOpts(self):
        """Creates a new :class:`DisplayOpts` instance. The specific
        ``DisplayOpts`` sub-class that is created is dictated by the current
        value of the :attr:`overlayType` property.

        The :data:`.displaycontext.DISPLAY_OPTS_MAP` dictionary defines the
        mapping between overlay types and :attr:`overlayType` values, and
        ``DisplayOpts`` sub-class types.
        """

        if self.getParent() is None:
            oParent = None
        else:
            oParent = self.getParent().opts

        from . import DISPLAY_OPTS_MAP

        optType = DISPLAY_OPTS_MAP[self.__overlay, self.overlayType]

        log.debug('Creating {} instance (synced: {}) for overlay '
                  '{} ({})'.format(optType.__name__,
                                   self.__displayCtx.syncOverlayDisplay,
                                   self.__overlay, self.overlayType))

        volProps  = optType.getVolumeProps()
        allProps  = optType.getAllProperties()[0]
        initState = {}

        for p in allProps:
            if p in volProps:
                initState[p] = self.__displayCtx.syncOverlayVolume
            else:
                initState[p] = self.__displayCtx.syncOverlayDisplay

        return optType(self.__overlay,
                       self,
                       self.__overlayList,
                       self.__displayCtx,
                       parent=oParent,
                       state=initState)


    def __findOptBaseType(self, optType, optName):
        """Finds the class, in the hierarchy of the given ``optType`` (a
        :class:`.DisplayOpts` sub-class) in which the given ``optName``
        is defined.

        This method is used by the :meth:`__saveOldDisplayOpts` method, and
        is an annoying necessity caused by the way that the :class:`.TypeDict`
        class works. A ``TypeDict`` does not allow types to be used as keys -
        they must be strings containing the type names.

        Furthermore, in order for the property values of a common
        ``DisplayOpts`` base type to be shared across sub types (e.g. copying
        the :attr:`.NiftiOpts.transform` property between :class:`.VolumeOpts`
        and :class:`.LabelOpts` instances), we need to store the name of the
        common base type in the dictionary.
        """

        for base in inspect.getmro(optType):
            if optName in base.__dict__:
                return base

        return None


    def __saveOldDisplayOpts(self):
        """Saves the value of every property on the current
        :class:`DisplayOpts` instance, so they can be restored later if
        needed.
        """

        opts = self.__displayOpts

        if opts is None:
            return

        for propName in opts.getAllProperties()[0]:
            base = self.__findOptBaseType(type(opts), propName)
            base = base.__name__
            val  = getattr(opts, propName)

            log.debug('Saving {}.{} = {} [{} {}]'.format(
                base, propName, val, type(opts).__name__, id(self)))

            self.__oldOptValues[base, propName] = val


    def __restoreOldDisplayOpts(self):
        """Restores any cached values for all of the properties on the
        current :class:`DisplayOpts` instance.
        """
        opts = self.__displayOpts

        if opts is None:
            return

        for propName in opts.getAllProperties()[0]:

            try:
                value = self.__oldOptValues[opts, propName]

                if not hasattr(opts, propName):
                    continue

                log.debug('Restoring {}.{} = {} [{}]'.format(
                    type(opts).__name__, propName, value, id(self)))

                setattr(opts, propName, value)

            except KeyError:
                pass


    def __overlayTypeChanged(self, *a):
        """Called when the :attr:`overlayType` property changes. Makes sure
        that the :class:`DisplayOpts` instance is of the correct type.
        """
        self.__saveOldDisplayOpts()
        self.opts
        self.__restoreOldDisplayOpts()
示例#7
0
class MyObj(props.HasProperties):
    myint  = props.Int()
    mybool = props.Boolean(default=False)
    mystr  = props.String()
示例#8
0
class ColourBarCanvas(props.HasProperties):
    """Contains logic to render a colour bar as an OpenGL texture.
    """

    cmap = props.ColourMap()
    """The :mod:`matplotlib` colour map to use."""

    negativeCmap = props.ColourMap()
    """Negative colour map to use, if :attr:`useNegativeCmap` is ``True``."""

    useNegativeCmap = props.Boolean(default=False)
    """Whether or not to use the :attr:`negativeCmap`.
    """

    cmapResolution = props.Int(minval=2, maxval=1024, default=256)
    """Number of discrete colours to use in the colour bar. """

    invert = props.Boolean(default=False)
    """Invert the colour map(s). """

    vrange = props.Bounds(ndims=1)
    """The minimum/maximum values to display."""

    label = props.String()
    """A label to display under the centre of the colour bar."""

    orientation = props.Choice(('horizontal', 'vertical'))
    """Whether the colour bar should be vertical or horizontal. """

    labelSide = props.Choice(('top-left', 'bottom-right'))
    """Whether the colour bar labels should be on the top/left, or bottom/right
    of the colour bar (depending upon whether the colour bar orientation is
    horizontal/vertical).
    """

    textColour = props.Colour(default=(1, 1, 1, 1))
    """Colour to use for the colour bar label. """

    bgColour = props.Colour(default=(0, 0, 0, 1))
    """Colour to use for the background. """
    def __init__(self):
        """Adds a few listeners to the properties of this object, to update
        the colour bar when they change.
        """

        self._tex = None
        self._name = '{}_{}'.format(self.__class__.__name__, id(self))

        self.addGlobalListener(self._name, self.__updateTexture)

    def __updateTexture(self, *a):
        self._genColourBarTexture()
        self.Refresh()

    def _initGL(self):
        """Called automatically by the OpenGL canvas target superclass (see the
        :class:`.WXGLCanvasTarget` and :class:`.OSMesaCanvasTarget` for
        details).

        Generates the colour bar texture.
        """
        self._genColourBarTexture()

    def destroy(self):
        """Should be called when this ``ColourBarCanvas`` is no longer needed.
        Destroys the :class:`.Texture2D` instance used to render the colour
        bar.
        """
        self.removeGlobalListener(self._name)
        self._tex.destroy()
        self._tex = None

    def _genColourBarTexture(self):
        """Generates a texture containing an image of the colour bar,
        according to the current property values.
        """

        if not self._setGLContext():
            return

        w, h = self.GetSize()

        if w < 50 or h < 50:
            return

        if self.orientation == 'horizontal':
            if self.labelSide == 'top-left': labelSide = 'top'
            else: labelSide = 'bottom'
        else:
            if self.labelSide == 'top-left': labelSide = 'left'
            else: labelSide = 'right'

        if self.cmap is None:
            bitmap = np.zeros((w, h, 4), dtype=np.uint8)
        else:

            if self.useNegativeCmap:
                negCmap = self.negativeCmap
                ticks = [0.0, 0.49, 0.51, 1.0]
                ticklabels = [
                    '{:0.2f}'.format(-self.vrange.xhi),
                    '{:0.2f}'.format(-self.vrange.xlo),
                    '{:0.2f}'.format(self.vrange.xlo),
                    '{:0.2f}'.format(self.vrange.xhi)
                ]
                tickalign = ['left', 'right', 'left', 'right']
            else:
                negCmap = None
                ticks = [0.0, 1.0]
                tickalign = ['left', 'right']
                ticklabels = [
                    '{:0.2f}'.format(self.vrange.xlo),
                    '{:0.2f}'.format(self.vrange.xhi)
                ]

            bitmap = cbarbmp.colourBarBitmap(
                cmap=self.cmap,
                negCmap=negCmap,
                invert=self.invert,
                ticks=ticks,
                ticklabels=ticklabels,
                tickalign=tickalign,
                width=w,
                height=h,
                label=self.label,
                orientation=self.orientation,
                labelside=labelSide,
                textColour=self.textColour,
                cmapResolution=self.cmapResolution)

        if self._tex is None:
            self._tex = textures.Texture2D(
                '{}_{}'.format(type(self).__name__, id(self)), gl.GL_LINEAR)

        # The bitmap has shape W*H*4, but the
        # Texture2D instance needs it in shape
        # 4*W*H
        bitmap = np.fliplr(bitmap).transpose([2, 0, 1])

        self._tex.setData(bitmap)
        self._tex.refresh()

    def _draw(self):
        """Renders the colour bar texture using all available canvas space."""

        if self._tex is None or not self._setGLContext():
            return

        width, height = self.GetSize()

        # viewport
        gl.glViewport(0, 0, width, height)
        gl.glMatrixMode(gl.GL_PROJECTION)
        gl.glLoadIdentity()
        gl.glOrtho(0, 1, 0, 1, -1, 1)
        gl.glMatrixMode(gl.GL_MODELVIEW)
        gl.glLoadIdentity()

        gl.glClearColor(*self.bgColour)
        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
        gl.glEnable(gl.GL_BLEND)
        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
        gl.glShadeModel(gl.GL_FLAT)

        self._tex.drawOnBounds(0, 0, 1, 0, 1, 0, 1)