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)
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)
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)
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)
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 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)
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)
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)
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)
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)
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)
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)
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)
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
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)
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
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)
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)