diff --git a/doc/images/orthoedittoolbar.png b/doc/images/orthoedittoolbar.png new file mode 100644 index 0000000000000000000000000000000000000000..86d54ece8fa45fcb5ed4274de2460966fa4b78b8 Binary files /dev/null and b/doc/images/orthoedittoolbar.png differ diff --git a/doc/images/overlaydisplaytoolbar.png b/doc/images/overlaydisplaytoolbar.png new file mode 100644 index 0000000000000000000000000000000000000000..5c40d9003b76537eb1a009e5e9c24c9a0623ce79 Binary files /dev/null and b/doc/images/overlaydisplaytoolbar.png differ diff --git a/fsl/fsleyes/controls/__init__.py b/fsl/fsleyes/controls/__init__.py index dda85bec4e75777e00c5f0665b7d839a10df0a14..0251c3acc76e2d2ba52d4e08c7ba0545a1e4bf62 100644 --- a/fsl/fsleyes/controls/__init__.py +++ b/fsl/fsleyes/controls/__init__.py @@ -30,6 +30,7 @@ The following control panels currently exist: ~fsl.fsleyes.controls.orthoedittoolbar.OrthoEditToolBar ~fsl.fsleyes.controls.orthotoolbar.OrthoToolBar ~fsl.fsleyes.controls.overlaydisplaypanel.OverlayDisplayPanel + ~fsl.fsleyes.controls.overlaydisplaytoolbar.OverlayDisplayToolBar ~fsl.fsleyes.controls.overlayinfopanel.OverlayInfoPanel ~fsl.fsleyes.controls.overlaylistpanel.OverlayListPanel ~fsl.fsleyes.controls.shellpanel.ShellPanel diff --git a/fsl/fsleyes/controls/orthoedittoolbar.py b/fsl/fsleyes/controls/orthoedittoolbar.py index 7f9ff6e5a3374fc2cd39eaed54db9454010172b2..dfbddb7a36d7f2afe2b88a2d0a6f959592efb685 100644 --- a/fsl/fsleyes/controls/orthoedittoolbar.py +++ b/fsl/fsleyes/controls/orthoedittoolbar.py @@ -1,9 +1,14 @@ #!/usr/bin/env python # -# orthoedittoolbar.py - +# orthoedittoolbar.py - The OrthoEditToolBar # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""This module provides the :class:`OrthoEditToolBar`, a +:class:`.FSLEyesToolBar` which displays controls for editing :class:`.Image` +instances in an :class:`.OrthoPanel`. +""" + import logging @@ -20,7 +25,144 @@ from fsl.fsleyes.profiles.orthoeditprofile import OrthoEditProfile log = logging.getLogger(__name__) -# Some of the toolbar widgets are labelled + +class OrthoEditToolBar(fsltoolbar.FSLEyesToolBar): + """The ``OrthoEditToolBar`` is a :class:`.FSLEyesToolBar` which displays + controls for editing :class:`.Image` instances in an :class:`.OrthoPanel`. + + An ``OrthoEditToolBar`` looks something like this: + + + .. image:: images/orthoedittoolbar.png + :scale: 50% + :align: center + + + The ``OrthoEditToolBar`` exposes properties and actions which are defined + on the :class:`.OrthoEditProfile` class, and allows the user to: + + - Change the :class:`.OrthoPanel` profile between ``view`` and ``edit`` + mode (see the :attr:`.ViewPanel.profile` property). When in ``view`` + mode, all of the other controls are hidden. + + - Undo/redo changes to the selection and to :class:`.Image` instances. + + - Clear and fill the current selection. + + - Switch between a 2D and 3D selection cursor. + + - Change the selection cursor size. + + - Create a new mask/ROI :class:`.Image` from the current selection. + + - Switch between regular *select* mode, and *select by intensity* mode, + and adjust the select by intensity mode settings. + + + All of the controls shown on an ``OrthoEditToolBar`` instance are defined + in the :attr:`_TOOLBAR_SPECS` dictionary. + """ + + + selint = props.Boolean(default=False) + """This property allows the user to change the :class:`.OrthoEditProfile` + between ``sel`` mode, and ``selint`` mode. + """ + + + def __init__(self, parent, overlayList, displayCtx, ortho): + """Create an ``OrthoEditToolBar``. + + :arg parent: The :mod:`wx` parent object. + :arg overlayList: The :class:`.OverlayList` instance. + :arg displayCtx: The :class:`.DisplayContext` instance. + :arg ortho: The :class:`.OrthoPanel` instance. + """ + fsltoolbar.FSLEyesToolBar.__init__(self, + parent, + overlayList, + displayCtx, + 24) + + self.__orthoPanel = ortho + + self .addListener('selint', self._name, self.__selintChanged) + ortho.addListener('profile', self._name, self.__profileChanged) + + self.__profileTool = props.buildGUI( + self, + ortho, + _TOOLBAR_SPECS['profile']) + + self.AddTool(self.__profileTool) + + self.__profileChanged() + + + def destroy(self): + """Must be called when this ``OrthoEditToolBar`` is no longer + needed. Removes property listeners, and calls the + :meth:`.FSLEyesToolBar.destroy` method. + """ + self.__orthoPanel.removeListener('profile', self._name) + fsltoolbar.FSLEyesToolBar.destroy(self) + + + def __selintChanged(self, *a): + """Called when the :attr:`selint` property changes. If the + :class:`OrthoPanel` is currently in ``edit`` mode, toggles the + associated :class:`.OrthoEditProfile` instance between ``sel`` + and ``selint`` modes. + """ + + ortho = self.__orthoPanel + + if ortho.profile != 'edit': + return + + profile = ortho.getCurrentProfile() + + if self.selint: profile.mode = 'selint' + else: profile.mode = 'sel' + + + def __profileChanged(self, *a): + """Called when the :attr:`.ViewPanel.profile` property of the + :class:`.OrthoPanel` changes. Shows/hides edit controls accordingly. + """ + + # We don't want to remove the profile tool + # created in __init__, so we skip the first + # tool + self.ClearTools(startIdx=1, destroy=True, postevent=False) + + ortho = self.__orthoPanel + profile = ortho.profile + profileObj = ortho.getCurrentProfile() + + if profile == 'edit': + self.disableNotification('selint') + self.selint = profileObj.mode == 'selint' + self.enableNotification('selint') + + specs = _TOOLBAR_SPECS[profile] + + tools = [] + + for spec in specs: + + if spec.key == 'selint': target = self + else: target = profileObj + + widget = props.buildGUI(self, target, spec) + if spec.label is not None: + widget = self.MakeLabelledTool(widget, spec.label) + + tools.append(widget) + + self.InsertTools(tools, 1) + + _LABELS = { 'selectionCursorColour' : strings.properties[OrthoEditProfile, @@ -36,22 +178,30 @@ _LABELS = { 'fillValue' : strings.properties[OrthoEditProfile, 'fillValue'], } +"""This dictionary contains labels for some :class:`OrthoEditToolBar` +controls. It is referenced in the :attr:`_TOOLBAR_SPECS` dictionary. +""" + _ICONS = { - 'view' : fslicons.findImageFile('eye24'), - 'edit' : fslicons.findImageFile('pencil24'), + 'view' : fslicons.findImageFile('eye24'), + 'edit' : fslicons.findImageFile('pencil24'), 'selectionIs3D' : [fslicons.findImageFile('selection3D24'), fslicons.findImageFile('selection2D24')], - 'clearSelection' : fslicons.findImageFile('clear24'), - 'undo' : fslicons.findImageFile('undo24'), - 'redo' : fslicons.findImageFile('redo24'), - 'fillSelection' : fslicons.findImageFile('fill24'), - 'createMaskFromSelection' : fslicons.findImageFile('createMask24'), - 'createROIFromSelection' : fslicons.findImageFile('createROI24'), - 'limitToRadius' : fslicons.findImageFile('radius24'), - 'localFill' : fslicons.findImageFile('localsearch24'), - 'selint' : fslicons.findImageFile('selectByIntensity24'), + 'clearSelection' : fslicons.findImageFile('clear24'), + 'undo' : fslicons.findImageFile('undo24'), + 'redo' : fslicons.findImageFile('redo24'), + 'fillSelection' : fslicons.findImageFile('fill24'), + 'createMaskFromSelection' : fslicons.findImageFile('createMask24'), + 'createROIFromSelection' : fslicons.findImageFile('createROI24'), + 'limitToRadius' : fslicons.findImageFile('radius24'), + 'localFill' : fslicons.findImageFile('localsearch24'), + 'selint' : fslicons.findImageFile('selectByIntensity24'), } +"""This dictionary contains icons for some :class:`OrthoEditToolBar` +controls. It is referenced in the :attr:`_TOOLBAR_SPECS` dictionary. +""" + _TOOLTIPS = { 'profile' : fsltooltips.properties['OrthoPanel.profile'], @@ -90,6 +240,10 @@ _TOOLTIPS = { 'intensityThres' : fsltooltips.properties['OrthoEditProfile.' 'intensityThres'], } +"""This dictionary contains tooltips for some :class:`OrthoEditToolBar` +controls. It is referenced in the :attr:`_TOOLBAR_SPECS` dictionary. +""" + _TOOLBAR_SPECS = { @@ -177,84 +331,16 @@ _TOOLBAR_SPECS = { enabledWhen=lambda p: p.mode == 'selint' and p.limitToRadius) ] } - - - -class OrthoEditToolBar(fsltoolbar.FSLEyesToolBar): - - - selint = props.Boolean(default=False) - - - def __init__(self, parent, overlayList, displayCtx, ortho): - fsltoolbar.FSLEyesToolBar.__init__(self, - parent, - overlayList, - displayCtx, - 24) - - self.orthoPanel = ortho - - self .addListener('selint', self._name, self.__selintChanged) - ortho.addListener('profile', self._name, self.__profileChanged) - - self.__profileTool = props.buildGUI( - self, - ortho, - _TOOLBAR_SPECS['profile']) - - self.AddTool(self.__profileTool) - - self.__profileChanged() - - - def destroy(self): - self.orthoPanel.removeListener('profile', self._name) - fsltoolbar.FSLEyesToolBar.destroy(self) - - - def __selintChanged(self, *a): - - ortho = self.orthoPanel - - if ortho.profile != 'edit': - return - - profile = ortho.getCurrentProfile() - - if self.selint: profile.mode = 'selint' - else: profile.mode = 'sel' - - - def __profileChanged(self, *a): - - # We don't want to remove the profile tool - # created in __init__, so we skip the first - # tool - self.ClearTools(startIdx=1, destroy=True, postevent=False) - - ortho = self.orthoPanel - profile = ortho.profile - profileObj = ortho.getCurrentProfile() - - if profile == 'edit': - self.disableNotification('selint') - self.selint = profileObj.mode == 'selint' - self.enableNotification('selint') - - specs = _TOOLBAR_SPECS[profile] - - tools = [] - - for spec in specs: - - if spec.key == 'selint': target = self - else: target = profileObj - - widget = props.buildGUI(self, target, spec) - if spec.label is not None: - widget = self.MakeLabelledTool(widget, spec.label) - - tools.append(widget) - - self.InsertTools(tools, 1) +"""This dictionary contains specifications for all of the tools shown in an +:class:`OrthoEditToolBar`. The following keys are defined: + + =========== =========================================================== + ``profile`` Contains a single specification defining the control for + switching the :class:`.OrthoPanel` between ``view`` and + ``edit`` profiles. + ``view`` A list of specifications defining controls to be shown when + the ``view`` profile is active. + ``edit`` A list of specifications defining controls to be shown when + the ``view`` profile is active. + =========== =========================================================== +""" diff --git a/fsl/fsleyes/controls/overlaydisplaytoolbar.py b/fsl/fsleyes/controls/overlaydisplaytoolbar.py index 3ffbfec173b4115160487930dec32afe5f989318..28e678899a00a7e25c47547140bf30b0f6b44bfc 100644 --- a/fsl/fsleyes/controls/overlaydisplaytoolbar.py +++ b/fsl/fsleyes/controls/overlaydisplaytoolbar.py @@ -1,14 +1,15 @@ #!/usr/bin/env python # -# overlaydisplaytoolbar.py - A toolbar which shows display control options for -# the currently selected overlay. +# overlaydisplaytoolbar.py - The OverlayDisplayToolBar. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> -"""A :class:`wx.Panel` which shows display control options for the currently -selected overlay. +"""This module provides the :class:`OverlyDisplyaToolBar`, a +:class:`.FSLEyesToolBar` containing controls for changing the display settings +of the currently selected overlay. """ + import logging import wx @@ -27,178 +28,56 @@ import overlaydisplaypanel as overlaydisplay log = logging.getLogger(__name__) -def _modImageLabel(img): - if img is None: return strings.choices['VectorOpts.modulate.none'] - else: return img.name - +class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): + """The ``OverlyDisplyaToolBar`` is a :class:`.FSLEyesToolBar` containing + controls which allow the user to change the display settings of the + currently selected overlay (as defined by the + :attr:`.DisplayContext.selectedOverlay` property). The display settings + for an overlay are contained in the :class:`.Display` and + :class:`.DisplayOpts` instances that are associated with that overlay. -_TOOLTIPS = td.TypeDict({ - - 'Display.name' : fsltooltips.properties['Display.name'], - 'Display.overlayType' : fsltooltips.properties['Display.overlayType'], - 'Display.alpha' : fsltooltips.properties['Display.alpha'], - 'Display.brightness' : fsltooltips.properties['Display.brightness'], - 'Display.contrast' : fsltooltips.properties['Display.contrast'], - - 'VolumeOpts.displayRange' : fsltooltips.properties['VolumeOpts.' - 'displayRange'], - 'VolumeOpts.resetDisplayRange' : fsltooltips.actions[ 'VolumeOpts.reset' - 'DisplayRange'], - 'VolumeOpts.cmap' : fsltooltips.properties['VolumeOpts.cmap'], - 'MaskOpts.threshold' : fsltooltips.properties['MaskOpts.threshold'], - 'MaskOpts.colour' : fsltooltips.properties['MaskOpts.colour'], + An ``OverlyDisplyaToolBar`` looks something like the following: - 'LabelOpts.lut' : fsltooltips.properties['LabelOpts.lut'], - 'LabelOpts.outline' : fsltooltips.properties['LabelOpts.outline'], - 'LabelOpts.outlineWidth' : fsltooltips.properties['LabelOpts.' - 'outlineWidth'], + .. image:: images/overlaydisplaytoolbar.png + :scale: 50% + :align: center - 'RGBVectorOpts.modulate' : fsltooltips.properties['VectorOpts.' - 'modulate'], - 'RGBVectorOpts.modThreshold' : fsltooltips.properties['VectorOpts.' - 'modThreshold'], - - 'LineVectorOpts.modulate' : fsltooltips.properties['VectorOpts.' - 'modulate'], - 'LineVectorOpts.modThreshold' : fsltooltips.properties['VectorOpts.' - 'modThreshold'], - 'LineVectorOpts.lineWidth' : fsltooltips.properties['LineVectorOpts.' - 'lineWidth'], - - 'ModelOpts.colour' : fsltooltips.properties['ModelOpts.colour'], - 'ModelOpts.outline' : fsltooltips.properties['ModelOpts.outline'], - 'ModelOpts.outlineWidth' : fsltooltips.properties['ModelOpts.' - 'outlineWidth'], -}) - - -_TOOLBAR_PROPS = td.TypeDict({ - - 'Display' : { - 'name' : props.Widget( - 'name', - tooltip=_TOOLTIPS['Display.name']), - 'overlayType' : props.Widget( - 'overlayType', - tooltip=_TOOLTIPS['Display.overlayType'], - labels=strings.choices['Display.overlayType']), - 'alpha' : props.Widget( - 'alpha', - spin=False, - showLimits=False, - tooltip=_TOOLTIPS['Display.alpha']), - 'brightness' : props.Widget( - 'brightness', - spin=False, - showLimits=False, - tooltip=_TOOLTIPS['Display.brightness']), - 'contrast' : props.Widget( - 'contrast', - spin=False, - showLimits=False, - tooltip=_TOOLTIPS['Display.contrast'])}, - - 'VolumeOpts' : { - 'displayRange' : props.Widget( - 'displayRange', - slider=False, - showLimits=False, - tooltip=_TOOLTIPS['VolumeOpts.displayRange'], - labels=[strings.choices['VolumeOpts.displayRange.min'], - strings.choices['VolumeOpts.displayRange.max']]), - 'resetDisplayRange' : actions.ActionButton( - 'resetDisplayRange', - icon=icons.findImageFile('verticalReset24'), - tooltip=_TOOLTIPS['VolumeOpts.resetDisplayRange']), - 'cmap' : props.Widget( - 'cmap', - tooltip=_TOOLTIPS['VolumeOpts.cmap'])}, - - 'MaskOpts' : { - 'threshold' : props.Widget( - 'threshold', - showLimits=False, - spin=False, - tooltip=_TOOLTIPS['MaskOpts.threshold']), - 'colour' : props.Widget( - 'colour', - size=(24, 24), - tooltip=_TOOLTIPS['MaskOpts.colour'])}, - - 'LabelOpts' : { - 'lut' : props.Widget( - 'lut', - tooltip=_TOOLTIPS['LabelOpts.lut'], - labels=lambda l: l.name), - 'outline' : props.Widget( - 'outline', - tooltip=_TOOLTIPS['LabelOpts.outline'], - icon=[icons.findImageFile('outline24'), - icons.findImageFile('filled24')], - toggle=True, - enabledWhen=lambda i, sw: not sw, - dependencies=[(lambda o: o.display, 'softwareMode')]), - - 'outlineWidth' : props.Widget( - 'outlineWidth', - tooltip=_TOOLTIPS['LabelOpts.outlineWidth'], - enabledWhen=lambda i, sw: not sw, - dependencies=[(lambda o: o.display, 'softwareMode')], - showLimits=False, - spin=False)}, - - 'RGBVectorOpts' : { - 'modulate' : props.Widget( - 'modulate', - labels=_modImageLabel, - tooltip=_TOOLTIPS['RGBVectorOpts.modulate']), - 'modThreshold' : props.Widget( - 'modThreshold', - showLimits=False, - spin=False, - tooltip=_TOOLTIPS['RGBVectorOpts.modThreshold'])}, - - 'LineVectorOpts' : { - 'modulate' : props.Widget( - 'modulate', - labels=_modImageLabel, - tooltip=_TOOLTIPS['LineVectorOpts.modulate']), - 'modThreshold' : props.Widget( - 'modThreshold', - showLimits=False, - spin=False, - tooltip=_TOOLTIPS['LineVectorOpts.modThreshold']), - 'lineWidth' : props.Widget( - 'lineWidth', - showLimits=False, - spin=False, - tooltip=_TOOLTIPS['LineVectorOpts.lineWidth']), - }, - - 'ModelOpts' : { - 'colour' : props.Widget( - 'colour', - size=(24, 24), - tooltip=_TOOLTIPS['ModelOpts.colour']), - 'outline' : props.Widget( - 'outline', - tooltip=_TOOLTIPS['ModelOpts.outline'], - icon=[icons.findImageFile('outline24'), - icons.findImageFile('filled24')], - toggle=True), - 'outlineWidth' : props.Widget( - 'outlineWidth', - showLimits=False, - spin=False, - tooltip=_TOOLTIPS['ModelOpts.outlineWidth'], - enabledWhen=lambda i: i.outline)} -}) - - -class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): + + The ``OverlayDisplayToolBar`` also defines an action called ``more`` + (which is linked to the :meth:`showMoreSettings` method), which opens an + :class:`.OverlayDisplayPanel`. + + + The specific controls which are displayed are defined in the + :attr:`_TOOLBAR_PROPS` dictionary, and are created by the following + methods: + + .. autosummary:: + :nosignatures: + + __makeDisplayTools + __makeVolumeOptsTools + __makeMaskOptsTools + __makeLabelOptsTools + __makeVectorOptsTools + __makeRGBVectorOptsTools + __makeLineVectorOptsTools + __makeModelOptsTools + """ def __init__(self, parent, overlayList, displayCtx, viewPanel): + """Create an ``OverlyDisplyaToolBar``. + + :arg parent: The :mod:`wx` parent object. + + :arg overlayList: The :class:`.OverlayList` instance. + + :arg displayCtx: The :class:`.DisplayContext` instance. + + :arg viewPanel: The :class:`.ViewPanel` which this + ``OverlayDisplayToolBar`` is owned by. + """ actionz = {'more' : self.showMoreSettings} @@ -221,7 +100,10 @@ class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): def destroy(self): - """Deregisters property listeners. """ + """Must be called when this ``OverlyDisplyaToolBar`` is no longer + needed. Removes some property listeners, and calls the + :meth:`.FSLEyesToolBar.destroy` method. + """ self._overlayList.removeListener('overlays', self._name) self._displayCtx .removeListener('selectedOverlay', self._name) @@ -240,18 +122,82 @@ class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): def showMoreSettings(self, *a): + """Shows/hides a :class:`.OverlayDisplayPanel` dialog. """ self.__viewPanel.togglePanel(overlaydisplay.OverlayDisplayPanel, floatPane=True) + + def __showTools(self, overlay): + """Creates and shows a set of controls allowing the user to change + the display settings of the specified ``overlay``. + """ + + oldTools = self.GetTools() + + # See long comment at bottom + def destroyOldTools(): + for t in oldTools: + t.Destroy() + + for t in oldTools: + t.Show(False) + + self.ClearTools(destroy=False, postevent=False) + + log.debug('Showing tools for {}'.format(overlay)) + + display = self._displayCtx.getDisplay(overlay) + opts = display.getDisplayOpts() + + # Display tools + tools = self.__makeDisplayTools(display) + + # DisplayOpts tools + makeFunc = getattr(self, '_{}__make{}Tools'.format( + type(self).__name__, type(opts).__name__), None) + + if makeFunc is not None: + tools.extend(makeFunc(opts)) + + # Button which opens the OverlayDisplayPanel + more = props.buildGUI( + self, + self, + view=actions.ActionButton( + 'more', + icon=icons.findImageFile('gear24'), + tooltip=fsltooltips.actions[self, 'more'])) + + tools.insert(0, more) + + self.SetTools(tools) + + # This method may have been called via an + # event handler an existing tool in the + # toolbar - in this situation, destroying + # that tool will result in nasty crashes, + # as the wx widget that generated the event + # will be destroyed while said event is + # being processed. So we destroy the old + # tools asynchronously, well after the event + # which triggered this method call will have + # returned. + wx.CallLater(1000, destroyOldTools) + def __overlayEnableChanged(self, *a): + """Called when the :attr:`.Display.enabled` property for the currently + selected overlay changes. Enables/disables this + ``OverlayDisplayToolBar`` accordingly. + """ display = self._displayCtx.getDisplay(self.__currentOverlay) self.Enable(display.enabled) def __selectedOverlayChanged(self, *a): - """Called when the :attr:`.DisplayContext.selectedOverlay` - index changes. Ensures that the correct display panel is visible. + """Called when the :attr:`.DisplayContext.selectedOverlay` or + :class:`.OverlayList` changes. Ensures that controls for the currently + selected overlay are being shown. """ if self.__currentOverlay is not None and \ @@ -282,7 +228,8 @@ class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): def __makeDisplayTools(self, display): - """ + """Creates and returns a collection of controls for editing properties + of the given :class:`.Display` instance. """ dispSpecs = _TOOLBAR_PROPS[display] @@ -336,7 +283,8 @@ class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): def __makeVolumeOptsTools(self, opts): - """ + """Creates and returns a collection of controls for editing properties + of the given :class:`.VolumeOpts` instance. """ rangeSpec = _TOOLBAR_PROPS[opts]['displayRange'] resetSpec = _TOOLBAR_PROPS[opts]['resetDisplayRange'] @@ -354,8 +302,9 @@ class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): def __makeMaskOptsTools(self, opts): - """ - """ + """Creates and returns a collection of controls for editing properties + of the given :class:`.MaskOpts` instance. + """ thresSpec = _TOOLBAR_PROPS[opts]['threshold'] colourSpec = _TOOLBAR_PROPS[opts]['colour'] @@ -366,8 +315,9 @@ class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): def __makeLabelOptsTools(self, opts): - """ - """ + """Creates and returns a collection of controls for editing properties + of the given :class:`.LabelOpts` instance. + """ lutSpec = _TOOLBAR_PROPS[opts]['lut'] outlineSpec = _TOOLBAR_PROPS[opts]['outline'] @@ -401,6 +351,9 @@ class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): def __makeVectorOptsTools(self, opts): + """Creates and returns a collection of controls for editing properties + of the given :class:`.VectorOpts` instance. + """ modSpec = _TOOLBAR_PROPS[opts]['modulate'] thresSpec = _TOOLBAR_PROPS[opts]['modThreshold'] @@ -425,10 +378,16 @@ class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): return [panel] def __makeRGBVectorOptsTools(self, opts): + """Creates and returns a collection of controls for editing properties + of the given :class:`.RGBVectorOpts` instance. + """ return self.__makeVectorOptsTools(opts) def __makeLineVectorOptsTools(self, opts): + """Creates and returns a collection of controls for editing properties + of the given :class:`.LineVectorOpts` instance. + """ widthSpec = _TOOLBAR_PROPS[opts]['lineWidth'] widget = props.buildGUI(self, opts, widthSpec) @@ -439,6 +398,9 @@ class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): def __makeModelOptsTools(self, opts): + """Creates and returns a collection of controls for editing properties + of the given :class:`.ModelOpts` instance. + """ colourSpec = _TOOLBAR_PROPS[opts]['colour'] outlineSpec = _TOOLBAR_PROPS[opts]['outline'] widthSpec = _TOOLBAR_PROPS[opts]['outlineWidth'] @@ -452,56 +414,180 @@ class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): return [colourWidget, outlineWidget, widthWidget] - def __showTools(self, overlay): +def _modImageLabel(img): + """Used to generate labels for the :attr:`.VectorOpts.modulate` + property choices. + """ + if img is None: return strings.choices['VectorOpts.modulate.none'] + else: return img.name - oldTools = self.GetTools() + +_TOOLTIPS = td.TypeDict({ - # See long comment at bottom - def destroyOldTools(): - for t in oldTools: - t.Destroy() + 'Display.name' : fsltooltips.properties['Display.name'], + 'Display.overlayType' : fsltooltips.properties['Display.overlayType'], + 'Display.alpha' : fsltooltips.properties['Display.alpha'], + 'Display.brightness' : fsltooltips.properties['Display.brightness'], + 'Display.contrast' : fsltooltips.properties['Display.contrast'], - for t in oldTools: - t.Show(False) + 'VolumeOpts.displayRange' : fsltooltips.properties['VolumeOpts.' + 'displayRange'], + 'VolumeOpts.resetDisplayRange' : fsltooltips.actions[ 'VolumeOpts.reset' + 'DisplayRange'], + 'VolumeOpts.cmap' : fsltooltips.properties['VolumeOpts.cmap'], - self.ClearTools(destroy=False, postevent=False) + 'MaskOpts.threshold' : fsltooltips.properties['MaskOpts.threshold'], + 'MaskOpts.colour' : fsltooltips.properties['MaskOpts.colour'], - log.debug('Showing tools for {}'.format(overlay)) + 'LabelOpts.lut' : fsltooltips.properties['LabelOpts.lut'], + 'LabelOpts.outline' : fsltooltips.properties['LabelOpts.outline'], + 'LabelOpts.outlineWidth' : fsltooltips.properties['LabelOpts.' + 'outlineWidth'], - display = self._displayCtx.getDisplay(overlay) - opts = display.getDisplayOpts() + 'RGBVectorOpts.modulate' : fsltooltips.properties['VectorOpts.' + 'modulate'], + 'RGBVectorOpts.modThreshold' : fsltooltips.properties['VectorOpts.' + 'modThreshold'], - # Display tools - tools = self.__makeDisplayTools(display) + 'LineVectorOpts.modulate' : fsltooltips.properties['VectorOpts.' + 'modulate'], + 'LineVectorOpts.modThreshold' : fsltooltips.properties['VectorOpts.' + 'modThreshold'], + 'LineVectorOpts.lineWidth' : fsltooltips.properties['LineVectorOpts.' + 'lineWidth'], - # DisplayOpts tools - makeFunc = getattr(self, '_{}__make{}Tools'.format( - type(self).__name__, type(opts).__name__), None) + 'ModelOpts.colour' : fsltooltips.properties['ModelOpts.colour'], + 'ModelOpts.outline' : fsltooltips.properties['ModelOpts.outline'], + 'ModelOpts.outlineWidth' : fsltooltips.properties['ModelOpts.' + 'outlineWidth'], +}) +"""This dictionary contains tooltips for :class:`.Display` and +:class:`.DisplayOpts` properties. It is referenced in the +:attr:`_TOOLBAR_PROPS` dictionary definition. +""" - if makeFunc is not None: - tools.extend(makeFunc(opts)) - # Button which opens the OverlayDisplayPanel - more = props.buildGUI( - self, - self, - view=actions.ActionButton( - 'more', - icon=icons.findImageFile('gear24'), - tooltip=fsltooltips.actions[self, 'more'])) +_TOOLBAR_PROPS = td.TypeDict({ - tools.insert(0, more) + 'Display' : { + 'name' : props.Widget( + 'name', + tooltip=_TOOLTIPS['Display.name']), + 'overlayType' : props.Widget( + 'overlayType', + tooltip=_TOOLTIPS['Display.overlayType'], + labels=strings.choices['Display.overlayType']), + 'alpha' : props.Widget( + 'alpha', + spin=False, + showLimits=False, + tooltip=_TOOLTIPS['Display.alpha']), + 'brightness' : props.Widget( + 'brightness', + spin=False, + showLimits=False, + tooltip=_TOOLTIPS['Display.brightness']), + 'contrast' : props.Widget( + 'contrast', + spin=False, + showLimits=False, + tooltip=_TOOLTIPS['Display.contrast'])}, - self.SetTools(tools) + 'VolumeOpts' : { + 'displayRange' : props.Widget( + 'displayRange', + slider=False, + showLimits=False, + tooltip=_TOOLTIPS['VolumeOpts.displayRange'], + labels=[strings.choices['VolumeOpts.displayRange.min'], + strings.choices['VolumeOpts.displayRange.max']]), + 'resetDisplayRange' : actions.ActionButton( + 'resetDisplayRange', + icon=icons.findImageFile('verticalReset24'), + tooltip=_TOOLTIPS['VolumeOpts.resetDisplayRange']), + 'cmap' : props.Widget( + 'cmap', + tooltip=_TOOLTIPS['VolumeOpts.cmap'])}, + + 'MaskOpts' : { + 'threshold' : props.Widget( + 'threshold', + showLimits=False, + spin=False, + tooltip=_TOOLTIPS['MaskOpts.threshold']), + 'colour' : props.Widget( + 'colour', + size=(24, 24), + tooltip=_TOOLTIPS['MaskOpts.colour'])}, + + 'LabelOpts' : { + 'lut' : props.Widget( + 'lut', + tooltip=_TOOLTIPS['LabelOpts.lut'], + labels=lambda l: l.name), + 'outline' : props.Widget( + 'outline', + tooltip=_TOOLTIPS['LabelOpts.outline'], + icon=[icons.findImageFile('outline24'), + icons.findImageFile('filled24')], + toggle=True, + enabledWhen=lambda i, sw: not sw, + dependencies=[(lambda o: o.display, 'softwareMode')]), - # This method may have been called via an - # event handler an existing tool in the - # toolbar - in this situation, destroying - # that tool will result in nasty crashes, - # as the wx widget that generated the event - # will be destroyed while said event is - # being processed. So we destroy the old - # tools asynchronously, well after the event - # which triggered this method call will have - # returned. - wx.CallLater(1000, destroyOldTools) + 'outlineWidth' : props.Widget( + 'outlineWidth', + tooltip=_TOOLTIPS['LabelOpts.outlineWidth'], + enabledWhen=lambda i, sw: not sw, + dependencies=[(lambda o: o.display, 'softwareMode')], + showLimits=False, + spin=False)}, + + 'RGBVectorOpts' : { + 'modulate' : props.Widget( + 'modulate', + labels=_modImageLabel, + tooltip=_TOOLTIPS['RGBVectorOpts.modulate']), + 'modThreshold' : props.Widget( + 'modThreshold', + showLimits=False, + spin=False, + tooltip=_TOOLTIPS['RGBVectorOpts.modThreshold'])}, + + 'LineVectorOpts' : { + 'modulate' : props.Widget( + 'modulate', + labels=_modImageLabel, + tooltip=_TOOLTIPS['LineVectorOpts.modulate']), + 'modThreshold' : props.Widget( + 'modThreshold', + showLimits=False, + spin=False, + tooltip=_TOOLTIPS['LineVectorOpts.modThreshold']), + 'lineWidth' : props.Widget( + 'lineWidth', + showLimits=False, + spin=False, + tooltip=_TOOLTIPS['LineVectorOpts.lineWidth']), + }, + + 'ModelOpts' : { + 'colour' : props.Widget( + 'colour', + size=(24, 24), + tooltip=_TOOLTIPS['ModelOpts.colour']), + 'outline' : props.Widget( + 'outline', + tooltip=_TOOLTIPS['ModelOpts.outline'], + icon=[icons.findImageFile('outline24'), + icons.findImageFile('filled24')], + toggle=True), + 'outlineWidth' : props.Widget( + 'outlineWidth', + showLimits=False, + spin=False, + tooltip=_TOOLTIPS['ModelOpts.outlineWidth'], + enabledWhen=lambda i: i.outline)} +}) +"""This dictionary defines specifications for all controls shown on an +:class:`OverlayDisplayToolBar`. +"""