Esempio n. 1
0
    def __init__(self, image, overlayList, displayCtx, canvas, threedee):
        """Create a ``GLMask``.

        :arg image:       The :class:`.Image` instance.
        :arg overlayList: The :class:`.OverlayList`
        :arg displayCtx:  The :class:`.DisplayContext` managing the scene.
        :arg canvas:      The canvas doing the drawing.
        :arg threedee:    2D or 3D rendering
        """
        glimageobject.GLImageObject.__init__(self,
                                             image,
                                             overlayList,
                                             displayCtx,
                                             canvas,
                                             threedee)

        # The shader attribute will be created
        # by the glmask_funcs module
        self.shader        = None
        self.imageTexture  = None
        self.edgeFilter    = glfilter.Filter('edge', texture=1)
        self.renderTexture = textures.RenderTexture(
            self.name, interp=gl.GL_LINEAR, rttype='c')

        self.addDisplayListeners()
        self.refreshImageTexture()

        def init():
            fslgl.glmask_funcs.init(self)
            self.notify()

        idle.idleWhen(init, self.textureReady)
Esempio n. 2
0
    def __init__(self, image, displayCtx, canvas, threedee):
        """Create a ``GLLabel``.

        :arg image:      The :class:`.Image` instance.
        :arg displayCtx: The :class:`.DisplayContext` managing the scene.
        :arg canvas:     The canvas doing the drawing.
        :arg threedee:   2D or 3D rendering
        """

        glimageobject.GLImageObject.__init__(self,
                                             image,
                                             displayCtx,
                                             canvas,
                                             threedee)

        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.__lut = self.opts.lut

        self.addListeners()
        self.registerLut()
        self.refreshLutTexture()
        self.refreshImageTexture()

        def init():
            fslgl.gllabel_funcs.init(self)
            self.notify()

        idle.idleWhen(init, self.textureReady)
Esempio n. 3
0
    def __init__(self, image, overlayList, displayCtx, canvas, threedee):
        """Create a ``GLMIP``.

        :arg image:       An :class:`.Image` object.

        :arg overlayList: The :class:`.OverlayList`

        :arg displayCtx:  The :class:`.DisplayContext` object managing the
                          scene.

        :arg canvas:      The canvas doing the drawing.

        :arg threedee:    Set up for 2D or 3D rendering.
        """

        glimageobject.GLImageObject.__init__(self, image, overlayList,
                                             displayCtx, canvas, threedee)

        self.shader = None
        self.imageTexture = None
        self.cmapTexture = textures.ColourMapTexture(self.name)

        self.addDisplayListeners()
        self.refreshImageTexture()
        self.refreshCmapTextures()

        def init():
            fslgl.glmip_funcs.init(self)
            self.notify()

        idle.idleWhen(init, self.textureReady)
Esempio n. 4
0
    def registerAuxImage(self, which, image, onReady=None, **kwargs):
        """Registers the given auxillary image with the
        ``AuxImageTextureManager``.
        """

        self.auxmgr.texture(which).deregister(self.name)
        self.auxmgr.registerAuxImage(which, image, **kwargs)
        self.auxmgr.texture(which).register(self.name, self.__textureChanged)
        if onReady is not None:
            idle.idleWhen(onReady, self.auxmgr.texturesReady)
Esempio n. 5
0
    def captureFrame(ctx):
        try:
            realCaptureFrame(ctx)
            idle.idleWhen(captureFrame, ready, ctx, after=0.1)
            panel.doMovieUpdate(overlay, opts)

        except Finished:
            finalise(ctx)

        except (Cancelled, Exception) as e:
            ctx.cancelled = True
            finalise(ctx)
Esempio n. 6
0
    def updateShaderState(self, *args, **kwargs):
        """Calls :func:`.gl21.glmip_funcs.updateShaderState`, and
        :meth:`.Notifier.notify`. Uses :func:`.idle.idleWhen` to ensure that
        they don't get called until :meth:`ready` returns ``True``.
        """
        alwaysNotify = kwargs.pop('alwaysNotify', None)

        def func():
            if fslgl.glmip_funcs.updateShaderState(self) or alwaysNotify:
                self.notify()

        idle.idleWhen(func, self.ready, name=self.name, skipIfQueued=True)
Esempio n. 7
0
    def updateShaderState(self, *args, **kwargs):
        """Calls :func:`.gl14.glvolume_funcs.updateShaderState` or
        :func:`.gl21.glvolume_funcs.updateShaderStatea`, then
        :meth:`.Notifier.notify`. Uses the :func:`.idle.idleWhen` function to
        make sure that it is not called until :meth:`ready` returns ``True``.

        :arg alwaysNotify: Must be passed as a keyword argument. If
                           ``False`` (the default), ``notify`` is only called
                           if ``glvolume_funcs.updateShaderState`` returns
                           ``True``. Otherwise, ``notify`` is always called.
        """

        alwaysNotify = kwargs.pop('alwaysNotify', None)

        # When alwaysNotify is True, we
        # set a flag on this GLVolume
        # instance to make sure that the
        # func() function below (which is
        # called asynchronously) gets
        # its value.
        #
        # We have to do this because this
        # updateShaderState method may be
        # called multiple times for a single
        # event, with different values of
        # alwaysNotify, and some of these
        # calls may be silently dropped
        # (see below).
        #
        # But if one of those calls needs
        # to force a notification, we want
        # that notification to happen.
        if alwaysNotify:
            self.__alwaysNotify = True

        def func():
            if fslgl.glvolume_funcs.updateShaderState(self) or \
               self.__alwaysNotify:
                self.notify()
                self.__alwaysNotify = False

        # Don't re-queue the update if it is
        # already queued on the idle loop.
        # As mentioned above, updateShaderState
        # may get called several times for a
        # single event, but in this situation
        # we only want to actually do the
        # update once.
        idle.idleWhen(func,
                      self.ready,
                      name=self.name,
                      skipIfQueued=True)
Esempio n. 8
0
    def asyncUpdateShaderState(self, *args, **kwargs):
        """Calls :meth:`updateShaderState` and then :meth:`.Notifier.notify`, using
        :func:`.idle.idleWhen` function to make sure that it is only called
        when :meth:`ready` returns ``True``.
        """

        alwaysNotify = kwargs.pop('alwaysNotify', None)

        def func():
            if self.updateShaderState() or alwaysNotify:
                self.notify()

        idle.idleWhen(func, self.ready, name=self.name, skipIfQueued=True)
Esempio n. 9
0
    def registerAuxImage(self, which, image, onReady=None):
        """Calls :meth:`.AuxImageTextureManager.registerAuxImage`, making
        sure that the texture interpolation is set appropriately.
        """

        if self.opts.interpolation == 'none': interp = gl.GL_NEAREST
        else:                                 interp = gl.GL_LINEAR

        self.auxmgr.texture(which).deregister(self.name)
        self.auxmgr.registerAuxImage(which, image, interp=interp)
        self.auxmgr.texture(which).register(self.name, self.__texturesChanged)
        if onReady is not None:
            idle.idleWhen(onReady, self.auxmgr.texturesReady)
Esempio n. 10
0
    def __colourImageChanged(self, *a):
        """Called when the :attr:`.VectorOpts.colourImage` changes. Registers
        with the new image, and refreshes textures as needed.
        """
        self.deregisterAuxImage('colour')
        self.registerAuxImage('colour')

        def onRefresh():
            self.compileShaders()
            self.refreshColourMapTexture()
            self.asyncUpdateShaderState(alwaysNotify=True)

        self.refreshAuxTexture('colour')
        idle.idleWhen(onRefresh, self.texturesReady)
Esempio n. 11
0
    def __init__(self, image, overlayList, displayCtx, canvas, threedee):
        """Create a ``GLLabel``.

        :arg image:       The :class:`.Image` instance.
        :arg overlayList: The :class:`.OverlayList`
        :arg displayCtx:  The :class:`.DisplayContext` managing the scene.
        :arg canvas:      The canvas doing the drawing.
        :arg threedee:    2D or 3D rendering
        """

        glimageobject.GLImageObject.__init__(self,
                                             image,
                                             overlayList,
                                             displayCtx,
                                             canvas,
                                             threedee)

        # The shader attribute will be created
        # by the gllabel_funcs module, and the
        # imageTexture by the refreshTexture
        # method
        self.shader       = None
        self.imageTexture = None
        self.lutTexture   = textures.LookupTableTexture(
            '{}_lut'.format(self.name))
        self.edgeFilter   = glfilter.Filter('edge', texture=2)
        self.renderTexture = textures.RenderTexture(
            self.name, gl.GL_LINEAR, rttype='c')

        self.__lut = self.opts.lut

        self.addListeners()
        self.registerLut()
        self.refreshLutTexture()
        self.refreshImageTexture()

        def init():
            fslgl.gllabel_funcs.init(self)
            self.notify()

        idle.idleWhen(init, self.textureReady)
Esempio n. 12
0
    def __onSelect(self, ev):
        """Called when the user selects a row. Calls the
        :meth:`.FileTreeManager.Show` method.
        """

        overlayList = self.__ftpanel.overlayList
        group = self.__mgr.filegroups[ev.row]

        # Disable events on the grid while
        # the overlays are being swapped,
        # otherwise FSLeyes will explode.
        freezeTime = time.time()
        freezeMsg = strings.messages[self, 'loading']

        def freeze():
            self.__grid.SetEvtHandlerEnabled(False)
            idle.idle(fwoverlay.textOverlay, self.__grid, freezeMsg)
            self.__mgr.show(group)

        def thaw():
            self.__grid.SetEvtHandlerEnabled(True)
            self.Refresh()
            self.Update()

        # Wait until every overlay is in the
        # overlay list, or 5 seconds have
        # elapsed, before re-enabling the grid.
        def thawWhen():

            thawTime = time.time()
            files = [f for f in group.files if f is not None]
            allin = all([overlayList.find(f) is not None for f in files])

            return allin or ((thawTime - freezeTime) >= 5)

        try:
            freeze()
        finally:
            idle.idleWhen(thaw, thawWhen)
Esempio n. 13
0
    def __init__(self, image, overlayList, displayCtx, canvas, threedee):
        """Create a ``GLRGBVolume``.

        :arg image:       The :class:`.Image` instance.
        :arg overlayList: The :class:`.OverlayList`
        :arg displayCtx:  The :class:`.DisplayContext` managing the scene.
        :arg canvas:      The canvas doing the drawing.
        :arg threedee:    2D or 3D rendering
        """
        glimageobject.GLImageObject.__init__(self, image, overlayList,
                                             displayCtx, canvas, threedee)

        self.shader = None
        self.imageTexture = None

        self.addListeners()
        self.refreshImageTexture()

        def init():
            fslgl.glrgbvolume_funcs.init(self)
            self.notify()

        idle.idleWhen(init, self.textureReady)
Esempio n. 14
0
    def __movieFrame(self):
        """Called by :meth:`__movieLoop`.

        If the currently selected overlay (see
        :attr:`.DisplayContext.selectedOverlay`) is a 4D :class:`.Image` being
        displayed as a ``volume`` (see the :class:`.VolumeOpts` class), the
        :attr:`.NiftiOpts.volume` property is incremented and all
        GL canvases in this ``CanvasPanel`` are refreshed.

        :returns: ``True`` if the movie loop was started, ``False`` otherwise.
        """

        from . import scene3dpanel

        if self.destroyed: return False
        if not self.movieMode: return False

        overlay = self.displayCtx.getSelectedOverlay()
        canvases = self.getGLCanvases()

        if overlay is None:
            return False

        opts = self.displayCtx.getOpts(overlay)

        if not self.canRunMovie(overlay, opts):
            return False

        # We want the canvas refreshes to be
        # synchronised. So we 'freeze' them
        # while changing the image volume, and
        # then refresh them all afterwards.
        for c in canvases:
            c.FreezeDraw()
            c.FreezeSwapBuffers()

        self.doMovieUpdate(overlay, opts)

        # Now we get refs to *all* GLObjects managed
        # by every canvas - we have to wait until
        # they are all ready to be drawn before we
        # can refresh the canvases.  Note that this
        # is only necessary when the movie axis == 3
        globjs = [c.getGLObject(o) for c in canvases for o in self.overlayList]
        globjs = [g for g in globjs if g is not None]

        def allReady():
            return all([g.ready() for g in globjs])

        # Figure out the movie rate - the
        # number of seconds to wait until
        # triggering the next frame.
        rate = self.movieRate
        rateMin = self.getAttribute('movieRate', 'minval')
        rateMax = self.getAttribute('movieRate', 'maxval')

        # Special case/hack - if this is a Scene3DPanel,
        # and the movie axis is X/Y/Z, we always
        # use a fast rate. Instead, the Scene3dPanel
        # will increase/decrease the rotation angle
        # to speed up/slow down the movie instead.
        if isinstance(self, scene3dpanel.Scene3DPanel) and self.movieAxis < 3:
            rate = rateMax

        rate = (rateMin + (rateMax - rate)) / 1000.0

        def update():
            self.movieSync()
            idle.idle(self.__movieLoop, after=rate)

        # Refresh the canvases when all
        # GLObjects are ready to be drawn.
        idle.idleWhen(update, allReady, pollTime=rate / 10)

        return True
Esempio n. 15
0
def makeGif(overlayList,
            displayCtx,
            panel,
            filename,
            progfunc=None,
            onfinish=None):
    """Save an animated gif of the currently selected overlay, according to the
    current movie mode settings.

    .. note:: This function will return immediately, as the animated GIF is
              generated on the ``wx`` :mod:`.idle` loop

    :arg overlayList: The :class:`.OverlayList`
    :arg displayCtx:  The :class:`.DisplayContext`
    :arg panel:       The :class:`.CanvasPanel`.
    :arg filename:    Name of file to save the movie to
    :arg progfunc:    Function which will be called after each frame is saved.
    :arg onfinish:    Function which will be called after all frames have been
                      saved.
    """

    def defaultProgFunc(frame):
        return True

    if progfunc is None:
        progfunc = defaultProgFunc

    overlay = displayCtx.getSelectedOverlay()
    opts    = displayCtx.getOpts(overlay)
    tempdir = tempfile.mkdtemp()
    is3d    = isinstance(panel, scene3dpanel.Scene3DPanel) and \
              panel.movieAxis != 3

    class Context(object):
        pass

    ctx           = Context()
    ctx.cancelled = False
    ctx.images    = []
    ctx.frames    = []

    class Finished(Exception):
        pass

    class Cancelled(Exception):
        pass

    def finalise(ctx):
        if not ctx.cancelled and len(ctx.images) > 0:
            ctx.images[0].save(filename,
                               format='gif',
                               save_all=True,
                               append_images=ctx.images[1:])
        shutil.rmtree(tempdir)

        if onfinish is not None:
            onfinish()

    def ready():
        globjs = [c.getGLObject(o)
                  for c in panel.getGLCanvases()
                  for o in overlayList]
        globjs = [g for g in globjs if g is not None]
        return all([g.ready() for g in globjs])

    def captureFrame(ctx):
        try:
            realCaptureFrame(ctx)
            idle.idleWhen(captureFrame, ready, ctx, after=0.1)
            panel.doMovieUpdate(overlay, opts)

        except Finished:
            finalise(ctx)

        except (Cancelled, Exception) as e:
            ctx.cancelled = True
            finalise(ctx)

    def realCaptureFrame(ctx):

        idx   = len(ctx.frames)
        fname = op.join(tempdir, '{}.gif'.format(idx))

        frame = panel.getMovieFrame(overlay, opts)

        if not progfunc(idx):
            raise Cancelled()

        # The 3D X/Y/Z movie mode performs
        # rotations, rather than moving the
        # display location through the X/Y/Z
        # axes. The "frame" returned by
        # getMovieFrame is a rotation matrix.
        # We convert these rotation matrices
        # into rms-deviations (average
        # deviation of the current frame from
        # the starting frame), which has an
        # inverted "V"-shaped wave form as the
        # scene is rotated 360 degrees. So
        # we continue capturing frames until
        # the rmsdev of the current frame is:
        #
        #   - close to 0 (i.e. very similar to
        #     the rotation matrix of the starting
        #     frame), and
        #
        #   - less than the most recent frame (i.e.
        #     has rotated past 180 degrees, and is
        #     rotating back twoards the starting
        #     point)
        if is3d:

            if len(ctx.frames) == 0:
                ctx.startFrame = frame

            # normalise the rotmat for this
            # frame to the rms difference
            # from the starting rotmat
            frame = transform.rmsdev(ctx.startFrame, frame)

            # Keep capturing frames until we
            # have performed a full 360 degree
            # rotation (rmsdev of current
            # frame is decreasing towards 0)
            if len(ctx.frames) > 1    and \
               frame < ctx.frames[-1] and \
               abs(frame) < 0.1:
                raise Finished()

        # All other movie frames have a range
        # (fmin, fmax) and start at some arbitrary
        # point within this range. We capture frames
        # until a full loop through this range has
        # been completed.
        else:

            # Have we looped back to fmin?
            ctx.looped = getattr(ctx, 'looped', False)
            if not ctx.looped          and \
               len(ctx.frames) > 1 and \
               frame < ctx.frames[-1]:
                ctx.looped = True

            # We have done one full loop, and
            # have reached the starting frame.
            if ctx.looped          and \
               len(ctx.frames) > 1 and \
               frame >= ctx.frames[0]:
                raise Finished()

        screenshot.screenshot(panel, fname)
        ctx.images.append(PIL.Image.open(fname))
        ctx.frames.append(frame)

    idle.idleWhen(captureFrame, ready, ctx, after=0.1)
Esempio n. 16
0
    def __movieFrame(self):
        """Called by :meth:`__movieLoop`.

        If the currently selected overlay (see
        :attr:`.DisplayContext.selectedOverlay`) is a 4D :class:`.Image` being
        displayed as a ``volume`` (see the :class:`.VolumeOpts` class), the
        :attr:`.NiftiOpts.volume` property is incremented and all
        GL canvases in this ``CanvasPanel`` are refreshed.

        :returns: ``True`` if the movie loop was started, ``False`` otherwise.
        """

        from . import scene3dpanel

        if self.destroyed(): return False
        if not self.movieMode: return False

        overlay = self.displayCtx.getSelectedOverlay()
        canvases = self.getGLCanvases()

        if overlay is None:
            return False

        opts = self.displayCtx.getOpts(overlay)

        if not self.canRunMovie(overlay, opts):
            return False

        # We want the canvas refreshes to be
        # synchronised. So we 'freeze' them
        # while changing the image volume, and
        # then refresh them all afterwards.
        for c in canvases:
            c.FreezeDraw()
            c.FreezeSwapBuffers()

        self.doMovieUpdate(overlay, opts)

        # Now we get refs to *all* GLObjects managed
        # by every canvas - we have to wait until
        # they are all ready to be drawn before we
        # can refresh the canvases.  Note that this
        # is only necessary when the movie axis == 3
        globjs = [c.getGLObject(o) for c in canvases for o in self.overlayList]
        globjs = [g for g in globjs if g is not None]

        def allReady():
            return all([g.ready() for g in globjs])

        # Figure out the movie rate - the
        # number of seconds to wait until
        # triggering the next frame.
        rate = self.movieRate
        rateMin = self.getAttribute('movieRate', 'minval')
        rateMax = self.getAttribute('movieRate', 'maxval')

        # Special case/hack - if this is a Scene3DPanel,
        # and the movie axis is X/Y/Z, we always
        # use a fast rate. Instead, the Scene3dPanel
        # will increase/decrease the rotation angle
        # to speed up/slow down the movie instead.
        if isinstance(self, scene3dpanel.Scene3DPanel) and self.movieAxis < 3:
            rate = rateMax

        rate = (rateMin + (rateMax - rate)) / 1000.0

        # The canvas refreshes are performed by the
        # __syncMovieRefresh or __unsyncMovieRefresh
        # methods. Gallium seems to have a problem
        # with separate renders/buffer swaps, so we
        # have to use a shitty unsynchronised update
        # routine.
        #
        # TODO Ideally, figure out a refresh
        #      regime that works across all
        #      drivers. Failing this, make
        #      this switch user controllable.
        renderer = fslplatform.glRenderer.lower()
        unsyncRenderers = ['gallium', 'mesa dri intel(r)']
        useSync = not any([r in renderer for r in unsyncRenderers])

        if useSync: update = self.__syncMovieRefresh
        else: update = self.__unsyncMovieRefresh

        # Refresh the canvases when all
        # GLObjects are ready to be drawn.
        idle.idleWhen(update, allReady, canvases, rate, pollTime=rate / 10)

        return True
Esempio n. 17
0
    def __init__(self, image, overlayList, displayCtx, canvas, threedee):
        """Create a ``GLVolume`` object.

        :arg image:       An :class:`.Image` object.

        :arg overlayList: The :class:`.OverlayList`

        :arg displayCtx:  The :class:`.DisplayContext` object managing the
                          scene.

        :arg canvas:      The canvas doing the drawing.

        :arg threedee:    Set up for 2D or 3D rendering.
        """

        glimageobject.GLImageObject.__init__(self, image, overlayList,
                                             displayCtx, canvas, threedee)

        # Add listeners to this image so the view can be
        # updated when its display properties are changed
        self.addDisplayListeners()

        # Create an image texture, clip texture, and a colour map texture
        #
        # We use the gl.resources module to manage texture
        # creation, because ImageTexture instances can
        # potentially be shared between GLVolumes. So all
        # GLVolumes use the same name, defined here, to
        # refer to the ImageTexture for a given image.
        self.texName = '{}_{}'.format(type(self).__name__, id(self.image))

        # Ref to an OpenGL shader program -
        # the glvolume_funcs module will
        # create this for us.
        self.shader = None

        # References to the clip image and
        # associated DisplayOpts instance,
        # if it is set.
        self.clipImage = None
        self.clipOpts = None

        # Refs to all of the texture objects.
        self.imageTexture = None
        self.clipTexture = None
        self.colourTexture = textures.ColourMapTexture(self.texName)
        self.negColourTexture = textures.ColourMapTexture('{}_neg'.format(
            self.texName))

        if self.threedee:

            self.smoothFilter = glfilter.Filter('smooth', texture=0)
            self.smoothFilter.set(kernSize=self.opts.smoothing * 2)

            self.renderTexture1 = textures.RenderTexture(self.name,
                                                         gl.GL_LINEAR,
                                                         rttype='cd')
            self.renderTexture2 = textures.RenderTexture(self.name,
                                                         gl.GL_LINEAR,
                                                         rttype='cd')

        # This attribute is used by the
        # updateShaderState method to
        # make sure that the Notifier.notify()
        # method gets called when needed.
        # See that method for details.
        self.__alwaysNotify = False

        # If the VolumeOpts instance has
        # inherited a clipImage value,
        # make sure we're registered with it.
        if self.opts.clipImage is not None:
            self.registerClipImage()

        self.refreshColourTextures()
        self.refreshImageTexture()
        self.refreshClipTexture()

        # Call glvolume_funcs.init when the image
        # and clip textures are ready to be used.
        def init():
            fslgl.glvolume_funcs.init(self)
            self.notify()

        idle.idleWhen(init, self.texturesReady)
Esempio n. 18
0
    def __init__(self,
                 overlay,
                 displayCtx,
                 canvas,
                 threedee,
                 init=None,
                 preinit=None):
        """Create a ``GLVectorBase`` object bound to the given overlay and
        display.

        Initialises the OpenGL data required to render the given vector
        overlay. This method does the following:

          - Creates the modulate, clipping and colour image textures.

          - Adds listeners to the :class:`.Display` and :class:`.VectorOpts`
            instances, so the textures and geometry can be updated when
            necessary.

        :arg overlay:        A :class:`.Nifti` object.

        :arg displayCtx:     A :class:`.DisplayContext` object which describes
                             how the overlay is to be displayed.

        :arg canvas:         The canvas doing the drawing.

        :arg threedee:       2D or 3D rendering.

        :arg init:           An optional function to be called when all of the
                             :class:`.ImageTexture` instances associated with
                             this ``GLVectorBase`` have been initialised.

        :arg preinit:        An optional functiono be called after this
                             ``GLVectorBase`` has configured itself, but
                             *before* ``init`` is called. Used by
                             :class:`GLVector`.
        """

        glimageobject.GLImageObject.__init__(self, overlay, displayCtx, canvas,
                                             threedee)

        name = self.name

        self.cmapTexture = textures.ColourMapTexture('{}_cm'.format(name))

        self.shader = None
        self.modulateImage = None
        self.clipImage = None
        self.colourImage = None
        self.modulateOpts = None
        self.clipOpts = None
        self.colourOpts = None
        self.modulateTexture = None
        self.clipTexture = None
        self.colourTexture = None

        # Make sure we are registered with the
        # auxillary images if any of them are set.
        opts = self.opts

        if opts.colourImage is not None: self.registerAuxImage('colour')
        if opts.modulateImage is not None: self.registerAuxImage('modulate')
        if opts.clipImage is not None: self.registerAuxImage('clip')

        self.addListeners()

        def initWrapper():
            if init is not None:
                init()
            self.notify()

        self.refreshColourMapTexture()
        self.refreshAuxTexture('modulate')
        self.refreshAuxTexture('clip')
        self.refreshAuxTexture('colour')

        if preinit is not None:
            preinit()

        idle.idleWhen(initWrapper, self.texturesReady)