Esempio n. 1
0
    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()
Esempio n. 2
0
    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))
Esempio n. 3
0
    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()