def refresh(self, refreshData=True, notify=True, callback=None): """(Re-)configures the OpenGL texture. :arg refreshData: If ``True`` (the default), the texture data is refreshed. :arg notify: If ``True`` (the default), a notification is triggered via the :class:`.Notifier` base-class, when this ``Texture3D`` has been refreshed, and is ready to use. Otherwise, the notification is suppressed. :arg callback: Optional function which will be called (via :func:`.idle.idle`) when the texture has been refreshed. Only called if ``refresh`` is ``True``, and a setting has changed. .. note:: The texture data may be generated on a separate thread, using the :func:`.idle.run` function. This is controlled by the ``threaded`` parameter, passed to :meth:`__init__`. """ # Don't bother if data # or shape/type hasn't # been set data = self.__data shape = self.__shape dtype = self.__dtype # We either need some data, or # we need a shape and data type. if data is None and (shape is None or dtype is None): return refreshData = refreshData and (data is not None) self.__ready = False # This can take a long time for big # data, so we do it in a separate # thread using the idle module. def genData(): # Another genData function is # already queued - don't run. # The TaskThreadVeto error # will stop the TaskThread from # calling configTexture as well. if self.__taskThread is not None and \ self.__taskThread.isQueued(self.__taskName): raise idle.TaskThreadVeto() self.__determineTextureType() if refreshData: self.__prepareTextureData() # Once genData is finished, we pass the # result (see __prepareTextureData) to # the sub-class doRefresh method. def doRefresh(): self.doRefresh() self.__ready = True if notify: self.notify() if callback is not None: callback() # Wrap the above functions in a report # decorator in case an error occurs title = strings.messages[self, 'dataError'] msg = strings.messages[self, 'dataError'] genData = status.reportErrorDecorator(title, msg)(genData) doRefresh = status.reportErrorDecorator(title, msg)(doRefresh) # Run asynchronously if we are # threaded, and we have data to # prepare - if we don't have # data, we run genData on the # current thread, because it # shouldn't do anything if self.__threaded and (data is not None): # TODO the task is already queued, # but a callback function has been # specified, should you queue the # callback function? # Don't queue the texture # refresh task twice if not self.__taskThread.isQueued(self.__taskName): self.__taskThread.enqueue(genData, taskName=self.__taskName, onFinish=doRefresh) else: genData() doRefresh()
def __doScreenshot(self): """Capture a screenshot. Prompts the user to select a file to save the screenshot to, and then calls the :func:`screenshot` function. """ lastDirSetting = 'fsleyes.actions.screenshot.lastDir' # Ask the user where they want # the screenshot to be saved fromDir = fslsettings.read(lastDirSetting, os.getcwd()) # We can get a list of supported output # types via a matplotlib figure object fig = plt.figure() fmts = fig.canvas.get_supported_filetypes() # Default to png if # it is available if 'png' in fmts: fmts = [('png', fmts['png'])] + \ [(k, v) for k, v in fmts.items() if k != 'png'] else: fmts = list(fmts.items()) wildcard = [ '[*.{}] {}|*.{}'.format(fmt, desc, fmt) for fmt, desc in fmts ] wildcard = '|'.join(wildcard) filename = 'screenshot' dlg = wx.FileDialog(self.__panel, message=strings.messages[self, 'screenshot'], wildcard=wildcard, defaultDir=fromDir, defaultFile=filename, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) if dlg.ShowModal() != wx.ID_OK: return filename = dlg.GetPath() # Make the dialog go away before # the screenshot gets taken dlg.Close() dlg.Destroy() wx.GetApp().Yield() # Show an error if the screenshot # function raises an error doScreenshot = status.reportErrorDecorator( strings.titles[self, 'error'], strings.messages[self, 'error'])(screenshot) # We do the screenshot asynchronously, # to make sure it is performed on # the main thread, during idle time idle.idle(doScreenshot, self.__panel, filename) status.update(strings.messages[self, 'pleaseWait'].format(filename)) fslsettings.write(lastDirSetting, op.dirname(filename))
def __refresh(self, *args, **kwargs): """(Re-)configures the OpenGL texture. :arg refreshData: If ``True`` (the default), the texture data is refreshed. :arg notify: If ``True`` (the default), a notification is triggered via the :class:`.Notifier` base-class, when this ``Texture3D`` has been refreshed, and is ready to use. Otherwise, the notification is suppressed. :arg callback: Optional function which will be called (via :func:`.idle.idle`) when the texture has been refreshed. Only called if ``refresh`` is ``True``, and a setting has changed. This method sets an attribute ``__textureShape`` on this ``Texture3D`` instance, containing the shape of the texture data. .. note:: The texture data is generated on a separate thread, using the :func:`.idle.run` function. """ # Don't bother if data # hasn't been set if self.__data is None: return refreshData = kwargs.get('refreshData', True) notify = kwargs.get('notify', True) callback = kwargs.get('callback', None) self.__ready = False bound = self.isBound() # This can take a long time for big # data, so we do it in a separate # thread using the idle module. def genData(): # Another genData function is # already queued - don't run. # The TaskThreadVeto error # will stop the TaskThread from # calling configTexture as well. if self.__taskThread is not None and \ self.__taskThread.isQueued(self.__taskName): raise idle.TaskThreadVeto() if refreshData: self.__determineTextureType() self.__prepareTextureData() # Once the genData function has finished, # we'll configure the texture back on the # main thread - OpenGL doesn't play nicely # with multi-threading. def configTexture(): data = self.__preparedData # If destroy() is called, the # preparedData will be blanked # out. if data is None: return # It is assumed that, for textures with more than one # value per voxel (e.g. RGB textures), the data is # arranged accordingly, i.e. with the voxel value # dimension the fastest changing if len(data.shape) == 4: self.__textureShape = data.shape[1:] else: self.__textureShape = data.shape log.debug('Configuring 3D texture (id {}) for ' '{} (data shape: {})'.format(self.getTextureHandle(), self.getTextureName(), self.__textureShape)) # The image data is flattened, with fortran dimension # ordering, so the data, as stored on the GPU, has its # first dimension as the fastest changing. data = np.ascontiguousarray(data.reshape(-1, order='F')) # PyOpenGL needs the data array # to be writeable, as it uses # PyArray_ISCARRAY to check # for contiguousness. but if the # data has come from a nibabel # ArrayProxy, the writeable flag # will be set to False for some # reason. # # TODO: If this turns out to be a problem # then you will have to bite the # bullet and copy the data. data.flags.writeable = True if not bound: self.bindTexture() # Enable storage of tightly packed data of any size (i.e. # our texture shape does not have to be divisible by 4). gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1) gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1) # set interpolation routine interp = self.__interp if interp is None: interp = gl.GL_NEAREST gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_MAG_FILTER, interp) gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_MIN_FILTER, interp) # Clamp texture borders to the edge # values - it is the responsibility # of the rendering logic to not draw # anything outside of the image space gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE) gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE) gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_WRAP_R, gl.GL_CLAMP_TO_EDGE) # create the texture according to # the format determined by the # _determineTextureType method. # # note: The ancient Chromium driver (still # in use by VirtualBox) will improperly # create 3D textures without two calls # (to glTexImage3D and glTexSubImage3D). # If I specify the texture size and set # the data in a single call, it seems to # expect that the data or texture # dimensions always have even size - odd # sized images will be displayed # incorrectly. gl.glTexImage3D(gl.GL_TEXTURE_3D, 0, self.__texIntFmt, self.__textureShape[0], self.__textureShape[1], self.__textureShape[2], 0, self.__texFmt, self.__texDtype, None) gl.glTexSubImage3D(gl.GL_TEXTURE_3D, 0, 0, 0, 0, self.__textureShape[0], self.__textureShape[1], self.__textureShape[2], self.__texFmt, self.__texDtype, data) if not bound: self.unbindTexture() log.debug('{}({}) is ready to use'.format( type(self).__name__, self.getTextureName())) self.__ready = True if notify: self.notify() if callback is not None: callback() # Wrap the above functions in a report # decorator in case an error occurs title = strings.messages[self, 'dataError'] msg = strings.messages[self, 'dataError'] genData = status.reportErrorDecorator(title, msg)(genData) configTexture = status.reportErrorDecorator(title, msg)(configTexture) if self.__threaded: # Don't queue the texture # refresh task twice if not self.__taskThread.isQueued(self.__taskName): self.__taskThread.enqueue(genData, taskName=self.__taskName, onFinish=configTexture) # TODO the task is already queued, # but a callback function has been # specified, should you queue the # callback function? else: genData() configTexture()