From abde58ac1b9e70b96f9a8e10904273ecc8e7aed3 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Fri, 17 Jul 2015 14:23:26 +0100 Subject: [PATCH] Basic support for overlay groups. Currently, only Display properties are synced. About to add support for DisplayOpts properties. --- fsl/fslview/controls/overlaylistpanel.py | 72 +++++++++---- fsl/fslview/displaycontext/__init__.py | 1 + fsl/fslview/displaycontext/displaycontext.py | 11 +- fsl/fslview/displaycontext/group.py | 107 +++++++++++++++++++ fsl/fslview/overlay.py | 2 +- fsl/tools/fslview.py | 12 +++ 6 files changed, 181 insertions(+), 24 deletions(-) create mode 100644 fsl/fslview/displaycontext/group.py diff --git a/fsl/fslview/controls/overlaylistpanel.py b/fsl/fslview/controls/overlaylistpanel.py index 933504ccd..46800e001 100644 --- a/fsl/fslview/controls/overlaylistpanel.py +++ b/fsl/fslview/controls/overlaylistpanel.py @@ -29,47 +29,73 @@ class ListItemWidget(wx.Panel): _enabledFG = '#000000' _disabledFG = '#CCCCCC' - def __init__(self, parent, overlay, display, listBox): + def __init__(self, parent, overlay, display, displayCtx, listBox): wx.Panel.__init__(self, parent) - self.overlay = overlay - self.display = display - self.listBox = listBox - self.name = '{}_{}'.format(self.__class__.__name__, id(self)) - - self.saveButton = wx.Button(self, label='S', style=wx.BU_EXACTFIT) - self.visibility = props.makeWidget(self, display, 'enabled') + self.overlay = overlay + self.display = display + self.displayCtx = displayCtx + self.listBox = listBox + self.name = '{}_{}'.format(self.__class__.__name__, id(self)) + + self.saveButton = wx.Button( self, + label='S', + style=wx.BU_EXACTFIT) + self.lockButton = wx.ToggleButton( self, + label='L', + style=wx.BU_EXACTFIT) + self.visibility = props.makeWidget(self, + display, + 'enabled') self.sizer = wx.BoxSizer(wx.HORIZONTAL) self.SetSizer(self.sizer) self.sizer.Add(self.saveButton, flag=wx.EXPAND, proportion=1) + self.sizer.Add(self.lockButton, flag=wx.EXPAND, proportion=1) self.sizer.Add(self.visibility, flag=wx.EXPAND, proportion=1) - self.display.addListener('enabled', self.name, self._vizChanged) + # There is currently only one overlay + # group in the application. In the + # future there may be multiple groups. + group = displayCtx.overlayGroups[0] + display.addListener('enabled', self.name, self.__vizChanged) + group .addListener('overlays', self.name, self.__overlayGroupChanged) + if isinstance(overlay, fslimage.Image): - self.overlay.addListener('saved', - self.name, - self._saveStateChanged) + overlay.addListener('saved', self.name, self.__saveStateChanged) else: log.warn('No save button support for non-volumetric overlays') self.saveButton.Enable(False) - self.saveButton.Bind(wx.EVT_BUTTON, self._onSaveButton) + self.saveButton.Bind(wx.EVT_BUTTON, self.__onSaveButton) + self.lockButton.Bind(wx.EVT_TOGGLEBUTTON, self.__onLockButton) + self .Bind(wx.EVT_WINDOW_DESTROY, self.__onDestroy) + + self.__vizChanged() + self.__saveStateChanged() + - self.Bind(wx.EVT_WINDOW_DESTROY, self._onDestroy) + def __overlayGroupChanged(self, *a): - self._vizChanged() - self._saveStateChanged() + group = self.displayCtx.overlayGroups[0] + self.lockButton.SetValue(self.overlay in group.overlays) - def _onSaveButton(self, ev): + def __onSaveButton(self, ev): self.overlay.save() + + def __onLockButton(self, ev): + group = self.displayCtx.overlayGroups[0] + + if self.lockButton.GetValue(): group.addOverlay( self.overlay) + else: group.removeOverlay(self.overlay) + - def _onDestroy(self, ev): + def __onDestroy(self, ev): ev.Skip() if ev.GetEventObject() is not self: return @@ -80,7 +106,7 @@ class ListItemWidget(wx.Panel): self.overlay.removeListener('saved', self.name) - def _saveStateChanged(self, *a): + def __saveStateChanged(self, *a): if not isinstance(self.overlay, fslimage.Image): return @@ -95,7 +121,7 @@ class ListItemWidget(wx.Panel): self.listBox.SetItemBackgroundColour(idx, '#ffaaaa', '#aa4444') - def _vizChanged(self, *a): + def __vizChanged(self, *a): idx = self.listBox.IndexOf(self.overlay) @@ -226,7 +252,11 @@ class OverlayListPanel(fslpanel.FSLViewPanel): self._listBox.Append(name, overlay, tooltip) - widget = ListItemWidget(self, overlay, display, self._listBox) + widget = ListItemWidget(self, + overlay, + display, + self._displayCtx, + self._listBox) self._listBox.SetItemWidget(i, widget) diff --git a/fsl/fslview/displaycontext/__init__.py b/fsl/fslview/displaycontext/__init__.py index 22cc99362..08ec6a172 100644 --- a/fsl/fslview/displaycontext/__init__.py +++ b/fsl/fslview/displaycontext/__init__.py @@ -11,6 +11,7 @@ import display from displaycontext import DisplayContext from display import Display +from group import OverlayGroup from sceneopts import SceneOpts from orthoopts import OrthoOpts from lightboxopts import LightBoxOpts diff --git a/fsl/fslview/displaycontext/displaycontext.py b/fsl/fslview/displaycontext/displaycontext.py index 9a7134ecc..623c0286e 100644 --- a/fsl/fslview/displaycontext/displaycontext.py +++ b/fsl/fslview/displaycontext/displaycontext.py @@ -66,6 +66,12 @@ class DisplayContext(props.SyncableHasProperties): """ + overlayGroups = props.List() + """A list of :class:`.OverlayGroup` instances, each of which defines + a group of overlays which share display properties. + """ + + def __init__(self, overlayList, parent=None): """Create a :class:`DisplayContext` object. @@ -75,7 +81,9 @@ class DisplayContext(props.SyncableHasProperties): as the parent of this instance. """ - props.SyncableHasProperties.__init__(self, parent) + props.SyncableHasProperties.__init__(self, + parent, + nounbind=['overlayGroups']) self.__overlayList = overlayList self.__name = '{}_{}'.format(self.__class__.__name__, id(self)) @@ -95,7 +103,6 @@ class DisplayContext(props.SyncableHasProperties): self.__name, self.__overlayListChanged) - log.memory('{}.init ({})'.format(type(self).__name__, id(self))) diff --git a/fsl/fslview/displaycontext/group.py b/fsl/fslview/displaycontext/group.py new file mode 100644 index 000000000..9a41acc1e --- /dev/null +++ b/fsl/fslview/displaycontext/group.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# +# group.py - Overlay groups +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# + +import logging +import copy + +import props + +import display as fsldisplay +import volumeopts + + +log = logging.getLogger(__name__) + + + +class OverlayGroup(props.HasProperties): + + + name = props.String() + + + overlays = props.List() + + + # Properties which are linked across all overlays + enabled = copy.copy(fsldisplay.Display.enabled) + alpha = copy.copy(fsldisplay.Display.alpha) + brightness = copy.copy(fsldisplay.Display.brightness) + contrast = copy.copy(fsldisplay.Display.contrast) + + + # Properties which are linked across Image overlays + volume = copy.copy(volumeopts.ImageOpts.transform) + + + # Properties which are linked across Volume overlays + displayRange = copy.copy(volumeopts.VolumeOpts.displayRange) + clippingRange = copy.copy(volumeopts.VolumeOpts.clippingRange) + invertClipping = copy.copy(volumeopts.VolumeOpts.invertClipping) + interpolation = copy.copy(volumeopts.VolumeOpts.interpolation) + + + # TODO Vector + # TODO Model + # TODO Label + + + def __init__(self, displayCtx, overlayList, number, name=None): + + self.__displayCtx = displayCtx + self.__overlayList = overlayList + self.__number = number + + if name is not None: + self.name = name + + + def __copy__(self): + return OverlayGroup( + self, + self.__displayCtx, + self.__overlayList, + self.__number, + self.name) + + + def addOverlay(self, overlay): + + self.overlays.append(overlay) + + display = self.__displayCtx.getDisplay(overlay) + opts = display.getDisplayOpts() + + # This is the first overlay to be added - the group + # should inherit its property values + if len(self.overlays) == 1: master, slave = display, self + + # Other overlays are already in the group - the + # new overlay should inherit the group properties + else: master, slave = self, display + + slave.bindProps('enabled', master) + slave.bindProps('alpha', master) + slave.bindProps('brightness', master) + slave.bindProps('contrast', master) + + + def removeOverlay(self, overlay): + + self.overlays.remove(overlay) + + display = self.__displayCtx.getDisplay(overlay) + opts = display.getDisplayOpts() + + self.unbindProps('enabled', display) + self.unbindProps('alpha', display) + self.unbindProps('brightness', display) + self.unbindProps('contrast', display) + + + def __overlayTypeChanged(self, *a): + pass diff --git a/fsl/fslview/overlay.py b/fsl/fslview/overlay.py index ab751b823..e23cc4d2a 100644 --- a/fsl/fslview/overlay.py +++ b/fsl/fslview/overlay.py @@ -43,7 +43,7 @@ class OverlayList(props.HasProperties): - ``name`` ... - - ``dataSoruce`` .. + - ``dataSource`` .. Furthermore, all overlay types must be able to be created with a single diff --git a/fsl/tools/fslview.py b/fsl/tools/fslview.py index c429066dc..a97f9aa6a 100644 --- a/fsl/tools/fslview.py +++ b/fsl/tools/fslview.py @@ -139,6 +139,18 @@ def context(args): overlayList = fsloverlay.OverlayList() displayCtx = displaycontext.DisplayContext(overlayList) + + # While the DisplayContext may refer to + # multiple overlay groups, we are currently + # using just one, allowing the user to specify + # a set of overlays for which their display + # properties are 'locked'. + lockGroup = displaycontext.OverlayGroup(displayCtx, + overlayList, + 0, + 'LockGroup') + displayCtx.overlayGroups.append(lockGroup) + log.debug('Created overlay list and master DisplayContext ({})'.format( id(displayCtx))) -- GitLab