diff --git a/fsl/data/strings.py b/fsl/data/strings.py index 0cdd9982d5a3d4ae4a745c6a1f02caca851131a3..4e8ed5ea2bebee8ea967fb8964776bcdb1652c2f 100644 --- a/fsl/data/strings.py +++ b/fsl/data/strings.py @@ -21,8 +21,8 @@ messages = TypeDict({ 'mapping...', 'overlay.loadOverlays.loading' : 'Loading {} ...', - 'overlay.loadOverlays.error' : 'An error occurred loading the image {}\n\n' - 'Details: {}', + 'overlay.loadOverlays.error' : 'An error occurred loading the image ' + '{}\n\nDetails: {}', 'overlay.loadOverlays.unknownType' : 'Unknown data type', @@ -70,6 +70,9 @@ messages = TypeDict({ 'LookupTablePanel.notLutOverlay' : 'Choose an overlay which ' 'uses a lookup table', + + 'LookupTablePanel.labelExists' : 'The {} LUT already contains a ' + 'label with value {}' }) @@ -107,7 +110,10 @@ titles = TypeDict({ 'LightBoxToolBar' : 'Lightbox view toolbar', 'LightBoxSettingsPanel' : 'Lightbox view settings', 'HistogramToolBar' : 'Histogram settings', - 'LookupTablePanel' : 'Lookup tables' + 'LookupTablePanel' : 'Lookup tables', + 'LutLabelDialog' : 'New LUT label', + + 'LookupTablePanel.labelExists' : 'Label already exists', }) @@ -174,6 +180,12 @@ labels = TypeDict({ 'LookupTablePanel.copyLut' : 'Copy', 'LookupTablePanel.saveLut' : 'Save', 'LookupTablePanel.loadLut' : 'Load', + + 'LutLabelDialog.value' : 'Value', + 'LutLabelDialog.name' : 'Name', + 'LutLabelDialog.colour' : 'Colour', + 'LutLabelDialog.ok' : 'Ok', + 'LutLabelDialog.cancel' : 'Cancel', }) diff --git a/fsl/fslview/colourmaps.py b/fsl/fslview/colourmaps.py index 25784c531c0932c869ebf63f8f45d2d4e71b0e64..8775140ba1439c4cc7d919d516ce037318341438 100644 --- a/fsl/fslview/colourmaps.py +++ b/fsl/fslview/colourmaps.py @@ -91,8 +91,6 @@ give the label name. For example:: - :func:`isLookupTableInstalled`: - - :func:`saveLookupTable`: TODO - update an installed lookup table - ------------- Miscellaneous @@ -120,6 +118,7 @@ and generating/manipulating colours.: import glob import shutil +import bisect import os.path as op from collections import OrderedDict @@ -213,6 +212,11 @@ class LutLabel(object): self.__colour == other.__colour and self.__enabled == other.__enabled) + + def __cmp__(self, other): + return self.__value.__cmp__(other.__value) + + def __hash__(self): return (hash(self.__value) ^ hash(self.__name) ^ @@ -232,7 +236,11 @@ class LookupTable(props.HasProperties): labels = props.List() """A list of :class:`LutLabel` instances, defining the label -> - colour/name mappings. + colour/name mappings. This list is sorted in increasing order + by the label value. + + If you modify this list directly, you will probably break things. Use + the get/set methods instead. """ @@ -288,7 +296,9 @@ class LookupTable(props.HasProperties): enabled = kwargs.get('enabled', label.enabled()) label = LutLabel(value, name, colour, enabled) - if idx == -1: self.labels.append(label) + # Use the bisect module to + # maintain the list order + if idx == -1: bisect.insort(self.labels, label) else: self.labels[idx] = label diff --git a/fsl/fslview/controls/lookuptablepanel.py b/fsl/fslview/controls/lookuptablepanel.py index 37eda0a4e5d9a5e0adb6e3a0ef96f66fbee28d5c..dc13bb5e4686cb86eb51929b9404eeb8b8b7424f 100644 --- a/fsl/fslview/controls/lookuptablepanel.py +++ b/fsl/fslview/controls/lookuptablepanel.py @@ -24,6 +24,7 @@ log = logging.getLogger(__name__) + class LabelWidget(wx.Panel): def __init__(self, lutPanel, overlayOpts, lut, value): @@ -59,31 +60,23 @@ class LabelWidget(wx.Panel): self.enableBox .Bind(wx.EVT_CHECKBOX, self.__onEnable) self.colourButton.Bind(wx.EVT_COLOURPICKER_CHANGED, self.__onColour) - def __onEnable(self, ev): + # Disable the LutPanel listener, otherwise + # it will recreate the label list (see + # LookupTablePanel._initLabelList) + self.lut.disableListener('labels', self.lutPanel._name) self.lut.set(self.value, enabled=self.enableBox.GetValue()) - self.__notifyLut() - - - def __notifyLut(self): - - # Disable the LookupTablePanel listener - # on the lut property, otherwise it will - # re-create the label list - self.opts.disableListener('lut', self.lutPanel._name) - self.opts.notify('lut') - self.opts.enableListener('lut', self.lutPanel._name) - + self.lut.enableListener('labels', self.lutPanel._name) def __onColour(self, ev): newColour = self.colourButton.GetColour() newColour = [c / 255.0 for c in newColour] + self.lut.disableListener('labels', self.lutPanel._name) self.lut.set(self.value, colour=newColour) - self.__notifyLut() - + self.lut.enableListener('labels', self.lutPanel._name) class LookupTablePanel(fslpanel.FSLViewPanel): @@ -166,8 +159,9 @@ class LookupTablePanel(fslpanel.FSLViewPanel): self.__loadLutButton.Bind(wx.EVT_BUTTON, self.__onLoadLut) self.__saveLutButton.Bind(wx.EVT_BUTTON, self.__onSaveLut) - self.__selectedOpts = None self.__selectedOverlay = None + self.__selectedOpts = None + self.__selectedLut = None overlayList.addListener('overlays', self._name, @@ -177,6 +171,28 @@ class LookupTablePanel(fslpanel.FSLViewPanel): self.__selectedOverlayChanged) self.__selectedOverlayChanged() + + def destroy(self): + + self._overlayList.removeListener('overlays', self._name) + self._displayCtx .removeListener('selectedOverlay', self._name) + + overlay = self.__selectedOverlay + opts = self.__selectedOpts + lut = self.__selectedLut + + if overlay is not None: + + display = self._displayCtx.getDisplay(overlay) + + display.removeListener('name', self._name) + display.removeListener('overlayType', self._name) + + if opts is not None: + opts.removeListener('lut', self._name) + + if lut is not None: + lut.removeListener('labels', self._name) def __selectedOverlayChanged(self, *a): @@ -252,7 +268,7 @@ class LookupTablePanel(fslpanel.FSLViewPanel): opts = self._displayCtx.getOpts(overlay) - opts.addListener('lut', self._name, self.__initLabelList) + opts.addListener('lut', self._name, self.__lutChanged) self.__selectedOpts = opts self.__lutWidget = props.makeWidget( @@ -261,11 +277,28 @@ class LookupTablePanel(fslpanel.FSLViewPanel): self.__controlRowSizer.Insert( 0, self.__lutWidget, flag=wx.EXPAND, proportion=1) - self.__initLabelList() + self.__lutChanged() self.Layout() + def __lutChanged(self, *a): + + if self.__selectedLut is not None: + self.__selectedLut.removeListener('labels', self._name) + self.__selecedLut = None + + opts = self.__selectedOpts + + if opts is not None: + self.__selectedLut = opts.lut + + self.__selectedLut.addListener( + 'labels', self._name, self.__initLabelList) + + self.__initLabelList() + + def __initLabelList(self, *a): self.__labelList.Clear() @@ -275,7 +308,6 @@ class LookupTablePanel(fslpanel.FSLViewPanel): opts = self.__selectedOpts lut = opts.lut - for i, label in enumerate(lut.labels): @@ -302,20 +334,112 @@ class LookupTablePanel(fslpanel.FSLViewPanel): def __onLabelAdd(self, ev): - # Prompt for value and name - # Add to lut - pass + + dlg = LutLabelDialog(self.GetTopLevelParent()) + if dlg.ShowModal() != wx.ID_OK: + return + + opts = self.__selectedOpts + value = dlg.value + name = dlg.name + colour = dlg.colour[:3] + colour = [c / 255.0 for c in colour] + + if opts.lut.get(value) is not None: + wx.MessageBox( + strings.messages[self, 'labelExists'].format( + opts.lut.name, value), + strings.titles[ self, 'labelExists'], + wx.ICON_INFORMATION | wx.OK) + return + + log.debug('New lut label for {}: {}, {}, {}'.format( + opts.lut.name, + value, + name, + colour)) + + opts.lut.set(value, name=name, colour=colour) def __onLabelRemove(self, ev): - opts = self._displayCtx.getOpts(self.__selectedOverlay) + opts = self.__selectedOpts value = opts.lut.labels[ev.idx].value() + + self.__selectedLut.disableListener('labels', self._name) opts.lut.delete(value) + self.__selectedLut.enableListener('labels', self._name) def __onLabelEdit(self, ev): - opts = self._displayCtx.getOpts(self.__selectedOverlay) + opts = self.__selectedOpts value = opts.lut.labels[ev.idx].value() + + self.__selectedLut.disableListener('labels', self._name) opts.lut.set(value, name=ev.label) + self.__selectedLut.enableListener('labels', self._name) + + +class LutLabelDialog(wx.Dialog): + + def __init__(self, parent): + + wx.Dialog.__init__(self, parent, title=strings.titles[self]) + + self._value = wx.SpinCtrl( self) + self._name = wx.TextCtrl( self) + self._colour = wx.ColourPickerCtrl(self) + + self._valueLabel = wx.StaticText(self) + self._nameLabel = wx.StaticText(self) + self._colourLabel = wx.StaticText(self) + + self._ok = wx.Button(self) + self._cancel = wx.Button(self) + + self._valueLabel .SetLabel(strings.labels[self, 'value']) + self._nameLabel .SetLabel(strings.labels[self, 'name']) + self._colourLabel.SetLabel(strings.labels[self, 'colour']) + self._ok .SetLabel(strings.labels[self, 'ok']) + self._cancel .SetLabel(strings.labels[self, 'cancel']) + + self._value.SetValue(0) + self._name .SetValue('New label') + + self._sizer = wx.GridSizer(4, 2) + self.SetSizer(self._sizer) + + self._sizer.Add(self._valueLabel, flag=wx.EXPAND) + self._sizer.Add(self._value, flag=wx.EXPAND) + self._sizer.Add(self._nameLabel, flag=wx.EXPAND) + self._sizer.Add(self._name, flag=wx.EXPAND) + self._sizer.Add(self._colourLabel, flag=wx.EXPAND) + self._sizer.Add(self._colour, flag=wx.EXPAND) + self._sizer.Add(self._ok, flag=wx.EXPAND) + self._sizer.Add(self._cancel, flag=wx.EXPAND) + + self._ok .Bind(wx.EVT_BUTTON, self.onOk) + self._cancel.Bind(wx.EVT_BUTTON, self.onCancel) + + self.Fit() + self.Layout() + + self.CentreOnParent() + + self.value = None + self.name = None + self.colour = None + + + def onOk(self, ev): + self.value = self._value .GetValue() + self.name = self._name .GetValue() + self.colour = self._colour.GetColour() + + self.EndModal(wx.ID_OK) + + + def onCancel(self, ev): + self.EndModal(wx.ID_CANCEL)