Beispiel #1
0
    def __propagateLocation(self, dest):
        """Called by the :meth:`__locationChanged` and
        :meth:`__worldLocationChanged` methods. The ``dest`` argument may be
        either ``'world'`` (the ``worldLocation`` is updated from the
        ``location``), or ``'display'`` (vice-versa).
        """

        if self.displaySpace == 'world':
            if dest == 'world':
                with props.skip(self, 'worldLocation', self.__name):
                    self.worldLocation = self.location
            else:
                with props.skip(self, 'location', self.__name):
                    self.location = self.worldLocation
            return

        ref = self.displaySpace
        opts = self.getOpts(ref)

        if dest == 'world':
            with props.skip(self, 'location', self.__name):
                self.worldLocation = opts.transformCoords(
                    self.location, 'display', 'world')
        else:
            with props.skip(self, 'worldLocation', self.__name):
                self.location = opts.transformCoords(self.worldLocation,
                                                     'world', 'display')
Beispiel #2
0
    def __setTransform(self, image):
        """Sets the :attr:`.NiftiOpts.transform` property associated with
        the given :class:`.Nifti` overlay to a sensible value, given the
        current value of the :attr:`.displaySpace` property.

        Called by the :meth:`__displaySpaceChanged` method, and by
        :meth:`__overlayListChanged` for any :class:`.Image` overlays which
        have been newly added to the :class:`.OverlayList`.

        :arg image: An :class:`.Image` overlay.
        """

        space = self.displaySpace
        opts = self.getOpts(image)

        # Disable notification of the bounds
        # property so the __overlayBoundsChanged
        # method does not get called. Use
        # ignoreInvalid, because this method might
        # get called before we have registered a
        # listener on the bounds property.
        with props.skip(opts, 'bounds', self.__name, ignoreInvalid=True):
            if space == 'world': opts.transform = 'affine'
            elif image is space: opts.transform = 'pixdim-flip'
            else: opts.transform = 'reference'
Beispiel #3
0
 def __onListRemove(self, ev):
     """Called when the user removes an item from the
     :class:`.EditableListBox`. Removes the corresponding
     :class:`.DataSeries` instance from the :attr:`.PlotPanel.dataSeries`
     list of the :class:`.OverlayPlotPanel`.
     """
     with props.skip(self.__plotPanel, 'dataSeries', self.name):
         self.__plotPanel.dataSeries.remove(ev.data)
Beispiel #4
0
    def __volumeChanged(self, *args, **kwargs):
        """Called when the :attr:`volume` property changes, and also by the
        :meth:`__init__` method.

        Re-calculates some things for the new overlay volume.
        """

        opts = self.__opts
        overlay = self.overlay

        # We cache the following for each volume
        # so they don't need to be recalculated:
        #  - finite data
        #  - non-zero data
        #  - finite minimum
        #  - finite maximum
        #
        # The cache size is restricted (see its
        # creation in __init__) so we don't blow
        # out RAM
        volkey = (opts.volumeDim, opts.volume)
        volprops = self.__volCache.get(volkey, None)

        if volprops is None:
            log.debug('Volume changed {} - extracting '
                      'finite/non-zero data'.format(volkey))
            finData = overlay[opts.index()]
            finData = finData[np.isfinite(finData)]
            nzData = finData[finData != 0]
            dmin = finData.min()
            dmax = finData.max()
            self.__volCache.put(volkey, (finData, nzData, dmin, dmax))
        else:
            log.debug('Volume changed {} - got finite/'
                      'non-zero data from cache'.format(volkey))
            finData, nzData, dmin, dmax = volprops

        dist = (dmax - dmin) / 10000.0

        with props.suppressAll(self):

            self.dataRange.xmin = dmin
            self.dataRange.xmax = dmax + dist
            self.dataRange.xlo = dmin
            self.dataRange.xhi = dmax + dist
            self.nbins = autoBin(nzData, self.dataRange.x)

            self.__finiteData = finData
            self.__nonZeroData = nzData

            self.__dataRangeChanged()

        with props.skip(self, 'dataRange', self.__name):
            self.propNotify('dataRange')
Beispiel #5
0
    def __onResize(self, ev):
        """Called on ``wx.EVT_SIZE`` events, when the canvas is resized. When
        the canvas is resized, we have to update the display bounds to preserve
        the aspect ratio.
        """
        ev.Skip()

        with props.skip(self.opts, 'displayBounds', self.name):
            centre = self.getDisplayCentre()
            self._updateDisplayBounds()
            self.centreDisplayAt(*centre)
Beispiel #6
0
    def __colourChanged(self, *a):
        """Called when :attr:`.colour` changes. Updates :attr:`.Display.alpha`
        from the alpha component.
        """

        alpha = self.colour[3] * 100

        log.debug('Propagating MeshOpts.colour[3] to '
                  'Display.alpha [{}]'.format(alpha))

        with props.skip(self.display, 'alpha', self.name):
            self.display.alpha = alpha
    def __lbRemove(self, ev):
        """Called when an item is removed from the overlay listbox.
        Removes the corresponding overlay from the :class:`.OverlayList`.
        """

        overlay = self.displayCtx.overlayOrder[ev.idx]
        overlay = self.overlayList[overlay]

        with props.skip(self.overlayList, 'overlays',     self.name), \
             props.skip(self.displayCtx,  'overlayOrder', self.name):

            if not removeoverlay.removeOverlay(self.overlayList,
                                               self.displayCtx, overlay):
                ev.Veto()

            # The overlayListChanged method
            # must be called asynchronously,
            # otherwise it will corrupt the
            # EditableListBox state
            else:
                idle.idle(self.__overlayListChanged)
Beispiel #8
0
    def __alphaChanged(self, *a):
        """Called when :attr:`.Display.alpha` changes. Updates the alpha
        component of :attr:`.colour`.
        """

        alpha = self.display.alpha / 100.0
        r, g, b, _ = self.colour

        log.debug('Propagating Display.alpha to MeshOpts.'
                  'colour[3] [{}]'.format(alpha))

        with props.skip(self, 'colour', self.name):
            self.colour = r, g, b, alpha
Beispiel #9
0
    def __colourChanged(self, *a):
        """Called when :attr:`.colour` changes. Updates :attr:`.Display.alpha`
        from the alpha component.
        """

        # modulateAlpha may cause the
        # alpha property to be disabled
        if not self.display.propertyIsEnabled('alpha'):
            return

        alpha = self.colour[3] * 100

        log.debug('Propagating MeshOpts.colour[3] to '
                  'Display.alpha [{}]'.format(alpha))

        with props.skip(self.display, 'alpha', self.name):
            self.display.alpha = alpha
Beispiel #10
0
    def __onGridSelect(self, ev):
        """Called when a row is selected on the :class:`.WidgetGrid`. Makes
        sure that the 'new tag' control in the corresponding
        :class:`.TextTagPanel` is focused.
        """

        component = ev.row
        opts      = self.displayCtx.getOpts(self.__overlay)

        log.debug('Grid row selected (component {}) - updating '
                  'overlay volume'.format(component))

        with props.skip(opts, 'volume', self.name):
            opts.volume = component

        tags = self.__grid.GetWidget(ev.row, 1)
        tags.FocusNewTagCtrl()
Beispiel #11
0
    def __overlayListChanged(self, *a):
        """Called when the :class:`.OverlayList` changes.

        If a 3D mask overlay was being shown, and it has been removed from the
        ``OverlayList``, the :attr:`.HistogramSeries.showOverlay` property is
        updated accordingly.
        """

        if self.__histMask is None:
            return

        # If a 3D overlay was being shown, and it
        # has been removed from the overlay list
        # by the user, turn the showOverlay property
        # off
        if self.__histMask not in self.__overlayList:

            with props.skip(self.__histSeries, 'showOverlay', self.__name):
                self.__histSeries.showOverlay = False
                self.__showOverlayChanged()
Beispiel #12
0
    def __setLut(self, lut):
        """Updates this ``LookupTablePanel`` to display the labels for the
        given ``lut`` (assumed to be a :class:`.LookupTable` instance).

        If the currently selected overlay is associated with a
        :class:`.LabelOpts` instance, its :attr:`.LabelOpts.lut` property is
        set to the new ``LookupTable``.
        """

        if self.__selectedLut == lut:
            return

        log.debug('Selecting lut: {}'.format(lut))

        if self.__selectedLut is not None:
            self.__selectedLut.deregister(self.name, 'saved')
            self.__selectedLut.deregister(self.name, 'added')
            self.__selectedLut.deregister(self.name, 'removed')

        self.__selectedLut = lut

        if lut is not None:
            lut.register(self.name, self.__lutSaveStateChanged, 'saved')
            lut.register(self.name, self.__lutLabelAdded, 'added')
            lut.register(self.name, self.__lutLabelRemoved, 'removed')

        if lut is not None and self.__selectedOpts is not None:
            with props.skip(self.__selectedOpts, 'lut', self.name):
                self.__selectedOpts.lut = lut

        allLuts = fslcmaps.getLookupTables()

        self.__lutChoice.SetSelection(allLuts.index(lut))

        self.__lutSaveStateChanged()
        self.__createLabelList()
Beispiel #13
0
        def applyLabels(labelFile, overlay, allLabels, newOverlay):
            # labelFile:  Path to the loaded label file
            # overlay:    Overlay to apply them to
            # allLabels:  Loaded labels (list of (component, [label]) tuples)
            # newOverlay: True if the selected overlay has changed, False
            #             otherwise

            lut       = self.__lut
            volLabels = self.overlayList.getData(overlay, 'VolumeLabels')

            ncomps  = volLabels.numComponents()
            nlabels = len(allLabels)

            # Error: number of labels in the
            # file is greater than the number
            # of components in the overlay.
            if ncomps < nlabels:
                msg   = strings.messages[self, 'wrongNComps'].format(
                    labelFile, overlay.dataSource)
                title = strings.titles[  self, 'loadError']
                wx.MessageBox(msg, title, wx.ICON_ERROR | wx.OK)
                return

            # Number of labels in the file is
            # less than number of components
            # in the overlay - we pad the
            # labels with 'Unknown'
            elif ncomps > nlabels:
                for i in range(nlabels, ncomps):
                    allLabels.append(['Unknown'])

            # Disable notification while applying
            # labels so the component/label grids
            # don't confuse themselves.
            with volLabels.skip(self.__componentGrid.name), \
                 volLabels.skip(self.__labelGrid    .name):

                volLabels.clear()

                for comp, lbls in enumerate(allLabels):
                    for lbl in lbls:
                        volLabels.addLabel(comp, lbl)

                # Make sure a colour in the melodic
                # lookup table exists for all labels
                for label in volLabels.getAllLabels():

                    label    = volLabels.getDisplayLabel(label)
                    lutLabel = lut.getByName(label)

                    if lutLabel is None:
                        log.debug('New melodic classification '
                                  'label: {}'.format(label))
                        lut.new(label, colour=fslcm.randomBrightColour())

            # New overlay was loaded
            if newOverlay:

                # Make sure the new image is selected.
                with props.skip(self.displayCtx,
                                'selectedOverlay',
                                self.name):
                    self.displayCtx.selectOverlay(overlay)

                self.__componentGrid.setOverlay(overlay)
                self.__labelGrid    .setOverlay(overlay)

            # Labels were applied to
            # already selected overlay.
            else:
                self.__componentGrid.refreshTags()
                self.__labelGrid    .refreshTags()
Beispiel #14
0
    def __onLoadButton(self, ev):
        """Called when the *Load labels* button is pushed.  Prompts the user
        to select a label file to load, then does the following:

        1. If the selected label file refers to the currently selected
           melodic_IC overlay, the labels are applied to the overlay.

        2. If the selected label file refers to a different melodic_IC
           overlay, the user is asked whether they want to load the
           different melodic_IC file (the default), or whether they
           want the labels applied to the existing overlay.

        3. If the selected label file does not refer to any overlay
           (it only contains the bad component list), the user is asked
           whether they want the labels applied to the current melodic_IC
           overlay.

        If the number of labels in the file is less than the number of
        melodic_IC components, the remaining components are labelled
        as unknown. If the number of labels in the file is greater than
        the number of melodic_IC components, an error is shown, and
        nothing is done.
        """

        # The aim of the code beneath the
        # applyLabels function is to load
        # a set of component labels, and
        # to figure out which overlay
        # they should be added to.

        # When it has done this, it calls
        # applyLabels, which applies the
        # loaded labels to the overlay.
        def applyLabels(labelFile, overlay, allLabels, newOverlay):
            # labelFile:  Path to the loaded label file
            # overlay:    Overlay to apply them to
            # allLabels:  Loaded labels (list of (component, [label]) tuples)
            # newOverlay: True if the selected overlay has changed, False
            #             otherwise

            lut       = self.__lut
            volLabels = self.overlayList.getData(overlay, 'VolumeLabels')

            ncomps  = volLabels.numComponents()
            nlabels = len(allLabels)

            # Error: number of labels in the
            # file is greater than the number
            # of components in the overlay.
            if ncomps < nlabels:
                msg   = strings.messages[self, 'wrongNComps'].format(
                    labelFile, overlay.dataSource)
                title = strings.titles[  self, 'loadError']
                wx.MessageBox(msg, title, wx.ICON_ERROR | wx.OK)
                return

            # Number of labels in the file is
            # less than number of components
            # in the overlay - we pad the
            # labels with 'Unknown'
            elif ncomps > nlabels:
                for i in range(nlabels, ncomps):
                    allLabels.append(['Unknown'])

            # Disable notification while applying
            # labels so the component/label grids
            # don't confuse themselves.
            with volLabels.skip(self.__componentGrid.name), \
                 volLabels.skip(self.__labelGrid    .name):

                volLabels.clear()

                for comp, lbls in enumerate(allLabels):
                    for lbl in lbls:
                        volLabels.addLabel(comp, lbl)

                # Make sure a colour in the melodic
                # lookup table exists for all labels
                for label in volLabels.getAllLabels():

                    label    = volLabels.getDisplayLabel(label)
                    lutLabel = lut.getByName(label)

                    if lutLabel is None:
                        log.debug('New melodic classification '
                                  'label: {}'.format(label))
                        lut.new(label, colour=fslcm.randomBrightColour())

            # New overlay was loaded
            if newOverlay:

                # Make sure the new image is selected.
                with props.skip(self.displayCtx,
                                'selectedOverlay',
                                self.name):
                    self.displayCtx.selectOverlay(overlay)

                self.__componentGrid.setOverlay(overlay)
                self.__labelGrid    .setOverlay(overlay)

            # Labels were applied to
            # already selected overlay.
            else:
                self.__componentGrid.refreshTags()
                self.__labelGrid    .refreshTags()

        # If the current overlay is a compatible
        # Image, the open file dialog starting
        # point will be its directory.
        overlay = self.__overlay

        if overlay is not None and overlay.dataSource is not None:
            loadDir = op.dirname(overlay.dataSource)

        # Otherwise it will be the most
        # recent overlay load directory.
        else:
            loadDir = fslsettings.read('loadSaveOverlayDir', os.getcwd())

        # Ask the user to select a label file
        dlg = wx.FileDialog(
            self,
            message=strings.titles[self, 'loadDialog'],
            defaultDir=loadDir,
            style=wx.FD_OPEN)

        # User cancelled the dialog
        if dlg.ShowModal() != wx.ID_OK:
            return

        # Load the specified label file
        filename = dlg.GetPath()
        emsg     = strings.messages[self, 'loadError'].format(filename)
        etitle   = strings.titles[  self, 'loadError']
        try:
            with status.reportIfError(msg=emsg, title=etitle):
                melDir, allLabels = fixlabels.loadLabelFile(filename)
        except Exception:
            return

        # Ok we've got the labels, now
        # we need to figure out which
        # overlay to add them to.

        # If the label file does not refer
        # to a Melodic directory, and the
        # current overlay is a compatible
        # image, apply the labels to the
        # image.
        if overlay is not None and melDir is None:
            applyLabels(filename, overlay, allLabels, False)
            return

        # If the label file refers to a
        # Melodic directory, and the
        # current overlay is a compatible
        # image.
        if overlay is not None and melDir is not None:

            if isinstance(overlay, fslmelimage.MelodicImage):
                overlayDir = overlay.getMelodicDir()
            elif overlay.dataSource is not None:
                overlayDir = op.dirname(overlay.dataSource)
            else:
                overlayDir = 'none'

            # And both the current overlay and
            # the label file refer to the same
            # directory, then we apply the
            # labels to the curent overlay.
            if op.abspath(melDir) == op.abspath(overlayDir):

                applyLabels(filename, overlay, allLabels, False)
                return

            # Otherwise, if the overlay and the
            # label file refer to different
            # directories...

            # Ask the user whether they want to load
            # the image specified in the label file,
            # or apply the labels to the currently
            # selected image.
            dlg = wx.MessageDialog(
                self,
                strings.messages[self, 'diffMelDir'].format(
                    melDir, overlayDir),
                style=wx.ICON_QUESTION | wx.YES_NO | wx.CANCEL)
            dlg.SetYesNoLabels(
                strings.messages[self, 'diffMelDir.labels'],
                strings.messages[self, 'diffMelDir.overlay'])

            response = dlg.ShowModal()

            # User cancelled the dialog
            if response == wx.ID_CANCEL:
                return

            # User chose to load the melodic
            # image specified in the label
            # file. We'll carry on with this
            # processing below.
            elif response == wx.ID_YES:
                pass

            # Apply the labels to the current
            # overlay, even though they are
            # from different analyses.
            else:
                applyLabels(filename, overlay, allLabels, False)
                return

        # If we've reached this far, we are
        # going to attempt to identify the
        # image associated with the label
        # file, load that image, and then
        # apply the labels.

        # The label file does not
        # specify a melodic directory
        if melDir is None:

            msg   = strings.messages[self, 'noMelDir'].format(filename)
            title = strings.titles[  self, 'loadError']
            wx.MessageBox(msg, title, wx.ICON_ERROR | wx.OK)
            return

        # Try loading the melodic_IC image
        # specified in the label file.
        try:
            overlay = fslmelimage.MelodicImage(melDir)

            log.debug('Adding {} to overlay list'.format(overlay))

            with props.skip(self.overlayList, 'overlays',        self.name),\
                 props.skip(self.displayCtx,  'selectedOverlay', self.name):

                self.overlayList.append(overlay)

            if self.displayCtx.autoDisplay:
                autodisplay.autoDisplay(overlay,
                                        self.overlayList,
                                        self.displayCtx)

            fslsettings.write('loadSaveOverlayDir', op.abspath(melDir))

        except Exception as e:

            e     = str(e)
            msg   = strings.messages[self, 'loadError'].format(filename, e)
            title = strings.titles[  self, 'loadError']
            log.debug('Error loading classification file '
                      '({}), ({})'.format(filename, e), exc_info=True)
            wx.MessageBox(msg, title, wx.ICON_ERROR | wx.OK)

        # Apply the loaded labels
        # to the loaded overlay.
        applyLabels(filename, overlay, allLabels, True)
Beispiel #15
0
    def __histPropsChanged(self, *a):
        """Called internally, and when any histogram settings change.
        Re-calculates the histogram data.
        """

        log.debug('Calculating histogram for '
                  'overlay {}'.format(self.overlay.name))

        status.update('Calculating histogram for '
                      'overlay {}'.format(self.overlay.name))

        if np.isclose(self.dataRange.xhi, self.dataRange.xlo):
            self.__xdata = np.array([])
            self.__ydata = np.array([])
            self.__nvals = 0
            return

        if self.ignoreZeros:
            if self.includeOutliers: data = self.__nonZeroData
            else: data = self.__clippedNonZeroData
        else:
            if self.includeOutliers: data = self.__finiteData
            else: data = self.__clippedFiniteData

        # Figure out the number of bins to use
        if self.autoBin: nbins = autoBin(data, self.dataRange.x)
        else: nbins = self.nbins

        # nbins is unclamped, but
        # we don't allow < 10
        if nbins < 10:
            nbins = 10

        # Update the nbins property
        with props.skip(self, 'nbins', self.__name):
            self.nbins = nbins

        # We cache calculated bins and counts
        # for each combination of parameters,
        # as histogram calculation can take
        # time.
        hrange = (self.dataRange.xlo, self.dataRange.xhi)
        drange = (self.dataRange.xmin, self.dataRange.xmax)
        histkey = ((self.__opts.volumeDim, self.__opts.volume),
                   self.includeOutliers, hrange, drange, self.nbins)
        cached = self.__histCache.get(histkey, None)

        if cached is not None:
            histX, histY, nvals = cached
        else:
            histX, histY, nvals = histogram(data, self.nbins, hrange, drange,
                                            self.includeOutliers, True)
            self.__histCache.put(histkey, (histX, histY, nvals))

        self.__xdata = histX
        self.__ydata = histY
        self.__nvals = nvals

        status.update('Histogram for {} calculated.'.format(self.overlay.name))

        log.debug('Calculated histogram for overlay '
                  '{} (number of values: {}, number '
                  'of bins: {})'.format(self.overlay.name, self.__nvals,
                                        self.nbins))
Beispiel #16
0
    def __overlayListChanged(self, *a):
        """Called when the :attr:`.OverlayList.overlays` property
        changes.

        Ensures that a :class:`.Display` and :class:`.DisplayOpts` object
        exists for every image, updates the :attr:`bounds` property, makes
        sure that the :attr:`overlayOrder` property is consistent, and updates
        constraints on the :attr:`selectedOverlay` property.
        """

        # Discard all Display instances
        # which refer to overlays that
        # are no longer in the list
        for overlay in list(self.__displays.keys()):
            if overlay not in self.__overlayList:

                display = self.__displays.pop(overlay)
                opts = display.opts

                display.removeListener('overlayType', self.__name)
                opts.removeListener('bounds', self.__name)

                # The display instance will destroy the
                # opts instance, so we don't do it here
                display.destroy()

        # Ensure that a Display object exists
        # for every overlay in the list
        for overlay in self.__overlayList:

            ovlType = self.__overlayList.initOverlayType(overlay)

            # The getDisplay method
            # will create a Display object
            # if one does not already exist
            display = self.getDisplay(overlay, ovlType)
            opts = display.opts

            # Register a listener on the overlay type,
            # because when it changes, the DisplayOpts
            # instance will change, and we will need
            # to re-register the DisplayOpts.bounds
            # listener (see the next statement)
            display.addListener('overlayType',
                                self.__name,
                                self.__overlayListChanged,
                                overwrite=True)

            # Register a listener on the DisplayOpts.bounds
            # property for every overlay - if the display
            # bounds for any overlay changes, we need to
            # update our own bounds property. This is only
            # done on child DCs, as the parent DC bounds
            # only gets used for synchronisation
            if self.__child:
                opts.addListener('bounds',
                                 self.__name,
                                 self.__overlayBoundsChanged,
                                 overwrite=True)

                # If detachDisplaySpace has been called,
                # make sure the opts bounds (and related)
                # properties are also detached
                if not self.canBeSyncedToParent('displaySpace'):
                    opts.detachFromParent('bounds')
                    if isinstance(overlay, fslimage.Nifti):
                        opts.detachFromParent('transform')

        # Ensure that the displaySpace
        # property options are in sync
        # with the overlay list.
        self.__updateDisplaySpaceOptions()

        # Stuff which only needs to
        # be done on the parent DC
        if not self.__child:

            # Limit the selectedOverlay property
            # so it cannot take a value greater
            # than len(overlayList)-1. selectedOverlay
            # is always synchronised, so we only
            # need to do this on the parent DC.
            nOverlays = len(self.__overlayList)
            if nOverlays > 0:
                self.setAttribute('selectedOverlay', 'maxval', nOverlays - 1)
            else:
                self.setAttribute('selectedOverlay', 'maxval', 0)

            return

        # Ensure that the overlayOrder
        # property is valid
        self.__syncOverlayOrder()

        # If the overlay list was empty,
        # and is now non-empty, we need
        # to initialise the display space
        # and the display location
        initDS        = self.__initDS                      and \
                        np.all(np.isclose(self.bounds, 0)) and \
                        len(self.__overlayList) > 0
        self.__initDS = len(self.__overlayList) == 0

        # Initialise the display space. We
        # have to do this before updating
        # image transforms, and updating
        # the display bounds
        if initDS:

            displaySpace = 'world'

            if self.defaultDisplaySpace == 'ref':
                for overlay in self.__overlayList:
                    if isinstance(overlay, fslimage.Nifti):
                        displaySpace = overlay
                        break

            with props.skip(self, 'displaySpace', self.__name):
                self.displaySpace = displaySpace

        # Initialise the transform property
        # of any Image overlays which have
        # just been added to the list,
        oldList = self.__overlayList.getLastValue('overlays')[:]
        for overlay in self.__overlayList:
            if isinstance(overlay, fslimage.Nifti) and \
               (overlay not in oldList):
                self.__setTransform(overlay)

        # Ensure that the bounds
        # property is accurate
        self.__updateBounds()

        # Initialise the display location to
        # the centre of the display bounds
        if initDS:
            b = self.bounds
            self.location.xyz = [
                b.xlo + b.xlen / 2.0, b.ylo + b.ylen / 2.0,
                b.zlo + b.zlen / 2.0
            ]
            self.__propagateLocation('world')
        else:
            self.__propagateLocation('display')
Beispiel #17
0
    def __updateShowOverlayRange(self, datax, which=False):
        """Called by the ``overlayRange`` mouse event handlers.  Updates the
        :attr:`.HistogramSeries.showOverlayRange`.

        :arg datax: X data coordinate corresponding to the mouse position.

        :arg which: Used to keep track of which value in the
                    ``showOverlayRange`` property the user is currently
                    modifying. On mouse down events, this method figures out
                    which range should be modified, and returns either
                    ``'lo'`` or ``'hi'``. On subsequent calls to this method
                    (on mouse drag and mouse up events), that return value
                    should be passed back into this method so that the same
                    value continues to get modified.
        """

        hs           = self.__currentHs
        rangePolygon = self.__rangePolygons.get(hs, None)

        if hs           is None: return
        if rangePolygon is None: return

        rangelo, rangehi = hs.showOverlayRange

        if   which == 'lo': newRange = [datax,   rangehi]
        elif which == 'hi': newRange = [rangelo, datax]

        else:
            # Less than low range
            if datax < rangelo:
                which    = 'lo'
                newRange =  (datax, rangehi)

            # Less than high range
            elif datax > rangehi:
                which    = 'hi'
                newRange =  (rangelo, datax)

            # In between low/high ranges -
            # is the mouse location closer
            # to the low or high range?
            else:

                lodist = abs(datax - rangelo)
                hidist = abs(datax - rangehi)

                if lodist < hidist:
                    which    = 'lo'
                    newRange = [datax, rangehi]
                else:
                    which    = 'hi'
                    newRange = [rangelo, datax]

        if newRange[1] < newRange[0]:
            if   which == 'lo': newRange[0] = newRange[1]
            elif which == 'hi': newRange[1] = newRange[0]

        # The range polygon will automatically update itself
        # when any HistogramSeries properties change. But the
        # canvas draw is faster if we do it manually. Hence
        # the listener skip.
        with props.skip(hs, 'showOverlayRange', rangePolygon._rp_name):
            hs.showOverlayRange = newRange

        # Manually refresh the histogram range polygon.
        rangePolygon.updatePolygon()

        return which
Beispiel #18
0
    def setHistogramData(self, data, key):
        """Must be called by sub-classes whenever the underlying histogram data
        changes.

        :arg data: A ``numpy`` array containing the data that the histogram is
                   to be calculated on. Pass in ``None``  to indicate that
                   there is currently no histogram data.

        :arg key:  Something which identifies the ``data``, and can be used as
                   a ``dict`` key.
        """

        if data is None:
            self.__nvals              = 0
            self.__dataKey            = None
            self.__xdata              = np.array([])
            self.__ydata              = np.array([])
            self.__finiteData         = np.array([])
            self.__nonZeroData        = np.array([])
            self.__clippedFiniteData  = np.array([])
            self.__clippedNonZeroData = np.array([])

            # force the panel to refresh
            with props.skip(self, 'dataRange', self.name):
                self.propNotify('dataRange')
            return

        # We cache the following data, based
        # on the provided key, so they don't
        # need to be recalculated:
        #  - finite data
        #  - non-zero data
        #  - finite minimum
        #  - finite maximum
        #
        # The cache size is restricted (see its
        # creation in __init__) so we don't blow
        # out RAM
        cached = self.__dataCache.get(key, None)

        if cached is None:

            log.debug('New histogram data {} - extracting '
                      'finite/non-zero data'.format(key))

            finData = data[np.isfinite(data)]
            nzData  = finData[finData != 0]
            dmin    = finData.min()
            dmax    = finData.max()

            self.__dataCache.put(key, (finData, nzData, dmin, dmax))
        else:
            log.debug('Got histogram data {} from cache'.format(key))
            finData, nzData, dmin, dmax = cached

        # The upper bound on the dataRange
        # is exclusive, so we initialise it
        # to a bit more than the data max.
        dist = (dmax - dmin) / 10000.0

        with props.suppressAll(self):

            self.dataRange.xmin = dmin
            self.dataRange.xmax = dmax + dist
            self.dataRange.xlo  = dmin
            self.dataRange.xhi  = dmax + dist
            self.nbins          = autoBin(nzData, self.dataRange.x)

            self.__dataKey     = key
            self.__finiteData  = finData
            self.__nonZeroData = nzData

            self.__dataRangeChanged()

        with props.skip(self, 'dataRange', self.name):
            self.propNotify('dataRange')