diff --git a/fsl/fslview/displaycontext/group.py b/fsl/fslview/displaycontext/group.py index 9a41acc1e524b1b714669865b87593647b60916b..493280b96d5a87ccbdd7d09de62de96d6095b795 100644 --- a/fsl/fslview/displaycontext/group.py +++ b/fsl/fslview/displaycontext/group.py @@ -10,63 +10,80 @@ import copy import props -import display as fsldisplay -import volumeopts +import fsl.utils.typedict as td log = logging.getLogger(__name__) - class OverlayGroup(props.HasProperties): - name = props.String() - - overlays = props.List() + """Do not add/remove overlays directly to this list - use the + :meth:`addOverlay` and :meth:`removeOverlay` methods. + """ - # 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) + _groupBindings = td.TypeDict({ + 'Display' : ['enabled', + 'alpha', + 'brightness', + 'contrast'], + 'ImageOpts' : ['volume', + 'transform'], + 'VolumeOpts' : ['interpolation'], + 'LabelOpts' : ['outline', + 'outlineWidth'], + 'ModelOpts' : ['outline', + 'outlineWidth', + 'refImage', + 'coordSpace', + 'transform'], + 'VectorOpts' : ['suppressX', + 'suppressY', + 'suppressZ', + 'modulate', + 'modThreshold'], + 'LineVectorOpts' : ['lineWidth', + 'directed'], + 'RGBVectorOpts' : ['interpolation'], + }) + """This dictionary defines the properties which are bound across Display + instances, and instances of the DisplayOpts sub-classes, for overlays in + the same group. + """ - # TODO Vector - # TODO Model - # TODO Label - - - def __init__(self, displayCtx, overlayList, number, name=None): + def __init__(self, displayCtx, overlayList): self.__displayCtx = displayCtx self.__overlayList = overlayList - self.__number = number + self.__name = '{}_{}'.format(type(self).__name__, id(self)) + + # Copy all of the properties listed + # in the _groupBindings dict + from . import \ + Display, \ + ImageOpts, \ + VolumeOpts, \ + MaskOpts, \ + VectorOpts, \ + RGBVectorOpts, \ + LineVectorOpts, \ + ModelOpts, \ + LabelOpts - if name is not None: - self.name = name + for clsName, propNames in OverlayGroup._groupBindings.items(): + cls = locals()[clsName] + + for propName in propNames: + prop = copy.copy(getattr(cls, propName)) + self.addProperty('{}_{}'.format(clsName, propName), prop) def __copy__(self): - return OverlayGroup( - self, - self.__displayCtx, - self.__overlayList, - self.__number, - self.name) + return OverlayGroup(self, self.__displayCtx, self.__overlayList) def addOverlay(self, overlay): @@ -76,20 +93,14 @@ class OverlayGroup(props.HasProperties): 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) + self.__bindDisplayOpts(display) + self.__bindDisplayOpts(opts) + display.addListener('overlayType', + self.__name, + self.__overlayTypeChanged) + def removeOverlay(self, overlay): self.overlays.remove(overlay) @@ -97,11 +108,44 @@ class OverlayGroup(props.HasProperties): display = self.__displayCtx.getDisplay(overlay) opts = display.getDisplayOpts() - self.unbindProps('enabled', display) - self.unbindProps('alpha', display) - self.unbindProps('brightness', display) - self.unbindProps('contrast', display) + self.__bindDisplayOpts(display, True) + self.__bindDisplayOpts(opts, True) + + display.removeListener('overlayType', self.__name) - def __overlayTypeChanged(self, *a): - pass + def __bindDisplayOpts(self, target, unbind=False): + + # This is the first overlay to be added - the + # group should inherit its property values + if len(self.overlays) == 1: + master, slave = target, self + + # Other overlays are already in the group - the + # new overlay should inherit the group properties + else: + master, slave = self, target + + bindProps = OverlayGroup._groupBindings.get(target, + allhits=True, + bykey=True) + + for clsName, propNames in bindProps.items(): + for propName in propNames: + + if slave is self: + otherName = propName + propName = '{}_{}'.format(clsName, propName) + else: + otherName = '{}_{}'.format(clsName, propName) + + slave.bindProps(propName, + master, + otherName, + bindatt=False, + unbind=unbind) + + + def __overlayTypeChanged(self, value, valid, display, name): + opts = display.getDisplayOpts() + self.__bindDisplayOpts(opts) diff --git a/fsl/tools/fslview.py b/fsl/tools/fslview.py index a97f9aa6a39332b881b372a37f45714342340321..ddb9e83f658c8e1ef91b276cb9e58253673c0f9c 100644 --- a/fsl/tools/fslview.py +++ b/fsl/tools/fslview.py @@ -145,10 +145,7 @@ def context(args): # 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') + lockGroup = displaycontext.OverlayGroup(displayCtx, overlayList) displayCtx.overlayGroups.append(lockGroup) log.debug('Created overlay list and master DisplayContext ({})'.format( diff --git a/fsl/utils/typedict.py b/fsl/utils/typedict.py index 887eb5447142090ca6627a119de85c9fd3134cea..f66209a2fd7f99637dc102a8440308615dcd2004 100644 --- a/fsl/utils/typedict.py +++ b/fsl/utils/typedict.py @@ -51,7 +51,7 @@ class TypeDict(object): return key - def get(self, key, default=None, allhits=False): + def get(self, key, default=None, allhits=False, bykey=False): """Retrieve the value associated with the given key. If no value is present, return the specified ``default`` value, which itself defaults to ``None``. @@ -60,13 +60,19 @@ class TypeDict(object): ``allhits`` argument evaluates to ``True``, the entire class hierarchy is searched, and all values present for the class, and any base class, are returned as a sequence. + + If ``allhits`` is ``True`` and the ``bykey`` parameter is also + set to ``True``, a dictionary is returned rather than a sequence, + where the dictionary contents are the subset of this dictionary, + containing the keys which equated to the given key, and their + corresponding values. """ - try: return self.__getitem__(key, allhits) + try: return self.__getitem__(key, allhits, bykey) except KeyError: return default - def __getitem__(self, key, allhits=False): + def __getitem__(self, key, allhits=False, bykey=False): origKey = key key = self.__tokenifyKey(key) @@ -95,8 +101,9 @@ class TypeDict(object): newKey.append(elem) bases .append(None) - key = newKey + key = newKey + keys = [] hits = [] while True: @@ -117,7 +124,9 @@ class TypeDict(object): # Otherwise, accumulate the value, and keep # searching - else: hits.append(val) + else: + keys.append(lKey) + hits.append(val) # No more base classes to search for - there # really is no value associated with this key @@ -135,16 +144,30 @@ class TypeDict(object): for elemBase in elemBases: newKey = list(key) - newKey[i] = elemBase + newKey[i] = elemBase.__name__ + + if len(newKey) == 1: newKey = newKey[0] + else: newKey = tuple(newKey) - try: val = self.__getitem__(tuple(newKey)) + try: val = self.__getitem__(newKey) except KeyError: continue if not allhits: return val - else: hits.append(val) + else: + keys.append(newKey) + hits.append(val) # No value for any base classes either if len(hits) == 0: raise KeyError(origKey) + + # if bykey is true, return a dict + # containing the values and their + # corresponding keys + if bykey: + return dict(zip(keys, hits)) + + # otherwise just return the + # list of matched values else: return hits