Skip to content
Snippets Groups Projects
Commit 884774c6 authored by Paul McCarthy's avatar Paul McCarthy
Browse files

Group class now supports bind/unbind of all overlay types, and re-binds

to Opts instances when overlay type changes.

Updates to TypeDict to support returning the key value for all hits in a
class hierarchy search.
parent abde58ac
No related branches found
No related tags found
No related merge requests found
......@@ -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)
......@@ -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(
......
......@@ -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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment