From 04a7d414e9c61a47c834b02619220160ee39d653 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Wed, 3 Jun 2015 16:03:00 +0100
Subject: [PATCH] Slightly functional lookup table panel, allowing user to
 change label colours, and to toggle labels on/off.

---
 fsl/data/strings.py                      |  14 +-
 fsl/fslview/colourmaps.py                |   5 +-
 fsl/fslview/controls/__init__.py         |   1 +
 fsl/fslview/controls/lookuptablepanel.py | 287 +++++++++++++++++++++++
 fsl/fslview/displaycontext/labelopts.py  |   7 +-
 fsl/fslview/views/canvaspanel.py         |   2 +
 6 files changed, 313 insertions(+), 3 deletions(-)
 create mode 100644 fsl/fslview/controls/lookuptablepanel.py

diff --git a/fsl/data/strings.py b/fsl/data/strings.py
index d92a2bf3a..d144f9937 100644
--- a/fsl/data/strings.py
+++ b/fsl/data/strings.py
@@ -67,6 +67,10 @@ messages = TypeDict({
 
     'SpacePanel.nonVolumetric'          : 'Non-volumetric overlays '
                                           'are not supported',
+
+    'LookupTablePanel.notLutOverlay' : 'Choose an overlay which '
+                                       'uses a lookup table',
+    
 })
 
 
@@ -102,7 +106,8 @@ titles = TypeDict({
     'OrthoSettingsPanel'    : 'Ortho view settings',
     'LightBoxToolBar'       : 'Lightbox view toolbar',
     'LightBoxSettingsPanel' : 'Lightbox view settings',
-    'HistogramToolBar'      : 'Histogram settings', 
+    'HistogramToolBar'      : 'Histogram settings',
+    'LookupTablePanel'      : 'Lookup tables'
 })
 
 
@@ -120,6 +125,7 @@ actions = TypeDict({
     'CanvasPanel.toggleDisplayProperties' : 'Overlay display properties',
     'CanvasPanel.toggleLocationPanel'     : 'Location panel',
     'CanvasPanel.toggleAtlasPanel'        : 'Atlas panel',
+    'CanvasPanel.toggleLookupTablePanel'  : 'Lookup tables',
     
     'OrthoPanel.toggleOrthoToolBar'     : 'View properties',
     'OrthoPanel.toggleProfileToolBar'   : 'Mode controls',
@@ -161,6 +167,12 @@ labels = TypeDict({
     'CanvasPanel.screenshot.notSaved.skip'   : 'Skip overlay (will not appear '
                                                'in screenshot)',
     'CanvasPanel.screenshot.notSaved.cancel' : 'Cancel screenshot',
+
+
+    'LookupTablePanel.addLabel' : 'Add label',
+    'LookupTablePanel.newLut'   : 'New LUT',
+    'LookupTablePanel.saveLut'  : 'Save LUT',
+    'LookupTablePanel.loadLut'  : 'Load LUT',
 })
 
 
diff --git a/fsl/fslview/colourmaps.py b/fsl/fslview/colourmaps.py
index d3c8e15cf..9d00e60c7 100644
--- a/fsl/fslview/colourmaps.py
+++ b/fsl/fslview/colourmaps.py
@@ -179,7 +179,10 @@ class _Map(object):
     def __repr__(self):
         return self.__str__()
 
-    
+
+# TODO Maybe this should be a HasProps class, so
+#      that interested parties can be# notified
+#      of changes to colours/names/etc.
 class LookupTable(object):
     """Class which encapsulates a list of labels and associated colours and names,
     defining a lookup table to be used for colouring label images.
diff --git a/fsl/fslview/controls/__init__.py b/fsl/fslview/controls/__init__.py
index 4da850160..9ee27b304 100644
--- a/fsl/fslview/controls/__init__.py
+++ b/fsl/fslview/controls/__init__.py
@@ -12,6 +12,7 @@ from overlayselectpanel    import OverlaySelectPanel
 from lightboxsettingspanel import LightBoxSettingsPanel
 from locationpanel         import LocationPanel
 from orthosettingspanel    import OrthoSettingsPanel
+from lookuptablepanel      import LookupTablePanel
 
 from orthotoolbar          import OrthoToolBar
 from orthoprofiletoolbar   import OrthoProfileToolBar
diff --git a/fsl/fslview/controls/lookuptablepanel.py b/fsl/fslview/controls/lookuptablepanel.py
new file mode 100644
index 000000000..28b511678
--- /dev/null
+++ b/fsl/fslview/controls/lookuptablepanel.py
@@ -0,0 +1,287 @@
+#!/usr/bin/env python
+#
+# lookuptablepanel.py -
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+import logging
+
+import wx
+
+import numpy as np
+
+import props
+
+import pwidgets.elistbox          as elistbox
+
+import fsl.fslview.panel          as fslpanel
+import fsl.fslview.displaycontext as fsldisplay
+import fsl.data.strings           as strings
+
+
+log = logging.getLogger(__name__)
+
+
+
+class LabelWidget(wx.Panel):
+    
+    def __init__(self, lutPanel, overlayOpts, lut, value):
+        wx.Panel.__init__(self, lutPanel)
+
+        self.lutPanel = lutPanel
+        self.opts     = overlayOpts
+        self.lut      = lut
+        self.value    = value
+
+        # TODO Change the enable box to a toggle
+        #      button with an eye icon
+        
+        self.valueLabel   = wx.StaticText(self,
+                                          style=wx.ALIGN_CENTRE_VERTICAL |
+                                                wx.ALIGN_RIGHT)
+        self.enableBox    = wx.CheckBox(self)
+        self.colourButton = wx.ColourPickerCtrl(self)
+
+        self.sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.SetSizer(self.sizer)
+        self.sizer.Add(self.valueLabel,   flag=wx.ALIGN_CENTRE, proportion=1)
+        self.sizer.Add(self.enableBox,    flag=wx.ALIGN_CENTRE, proportion=1)
+        self.sizer.Add(self.colourButton, flag=wx.ALIGN_CENTRE, proportion=1)
+
+        colour = [np.floor(c * 255.0) for c in lut.colour(value)]
+
+        self.valueLabel  .SetLabel(str(value))
+        self.colourButton.SetColour(colour)
+        self.enableBox   .SetValue(lut.enabled(value))
+
+        self.enableBox   .Bind(wx.EVT_CHECKBOX,             self.__onEnable)
+        self.colourButton.Bind(wx.EVT_COLOURPICKER_CHANGED, self.__onColour)
+
+
+    def __onEnable(self, ev):
+
+        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)        
+
+
+    def __onColour(self, ev):
+
+        newColour = self.colourButton.GetColour()
+        newColour = [c / 255.0 for c in newColour]
+
+        self.lut.set(self.value, colour=newColour)
+        self.__notifyLut()
+
+
+
+class LookupTablePanel(fslpanel.FSLViewPanel):
+
+    def __init__(self, parent, overlayList, displayCtx):
+
+        fslpanel.FSLViewPanel.__init__(self, parent, overlayList, displayCtx)
+
+        # If non-lut image is shown, just show a message
+
+        # Overlay name
+        # Change lookup table
+        # Add label
+        # Save lut
+        # Load lut
+
+        self.__controlRow = wx.Panel(self)
+
+        self.__disabledLabel = wx.StaticText(self,
+                                             style=wx.ALIGN_CENTER_VERTICAL |
+                                                   wx.ALIGN_CENTER_HORIZONTAL)
+        self.__labelList     = elistbox.EditableListBox(
+            self,
+            style=elistbox.ELB_NO_MOVE | elistbox.ELB_EDITABLE)
+
+        self.__overlayNameLabel = wx.StaticText(self,
+                                                style=wx.ST_ELLIPSIZE_MIDDLE)
+
+        self.__lutWidget        = None
+        self.__newLutButton     = wx.Button(self.__controlRow)
+        self.__saveLutButton    = wx.Button(self.__controlRow)
+        self.__loadLutButton    = wx.Button(self.__controlRow)
+
+        self.__controlRowSizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.__sizer           = wx.BoxSizer(wx.VERTICAL)
+
+        self.__controlRow.SetSizer(self.__controlRowSizer)
+        self             .SetSizer(self.__sizer)
+
+        self.__controlRowSizer.Add(self.__newLutButton,
+                                   flag=wx.EXPAND, proportion=1) 
+        self.__controlRowSizer.Add(self.__loadLutButton,
+                                   flag=wx.EXPAND, proportion=1)
+        self.__controlRowSizer.Add(self.__saveLutButton,
+                                   flag=wx.EXPAND, proportion=1)
+
+        self.__sizer.Add(self.__overlayNameLabel, flag=wx.EXPAND)
+        self.__sizer.Add(self.__controlRow,       flag=wx.EXPAND)
+        self.__sizer.Add(self.__disabledLabel,    flag=wx.EXPAND, proportion=1)
+        self.__sizer.Add(self.__labelList,        flag=wx.EXPAND, proportion=1)
+
+        # Label the labels and buttons
+        self.__disabledLabel.SetLabel(strings.messages[self, 'notLutOverlay'])
+        self.__newLutButton .SetLabel(strings.labels[  self, 'newLut'])
+        self.__loadLutButton.SetLabel(strings.labels[  self, 'loadLut'])
+        self.__saveLutButton.SetLabel(strings.labels[  self, 'saveLut'])
+
+        # Make the label name a bit smaller
+        font = self.__overlayNameLabel.GetFont()
+        font.SetPointSize(font.GetPointSize() - 2)
+        font.SetWeight(wx.FONTWEIGHT_LIGHT)
+        self.__overlayNameLabel.SetFont(font)
+
+        # Listen for listbox events
+        self.__labelList.Bind(elistbox.EVT_ELB_ADD_EVENT,
+                              self.__onLabelAdd)
+        self.__labelList.Bind(elistbox.EVT_ELB_REMOVE_EVENT,
+                              self.__onLabelRemove)
+        self.__labelList.Bind(elistbox.EVT_ELB_EDIT_EVENT,
+                              self.__onLabelEdit) 
+
+        self.__selectedOpts    = None
+        self.__selectedOverlay = None
+
+        overlayList.addListener('overlays',
+                                self._name,
+                                self.__selectedOverlayChanged)
+        displayCtx .addListener('selectedOverlay',
+                                self._name,
+                                self.__selectedOverlayChanged)
+
+        self.__selectedOverlayChanged()
+    
+
+    def __selectedOverlayChanged(self, *a):
+
+        newOverlay = self._displayCtx.getSelectedOverlay()
+
+        if self.__selectedOverlay == newOverlay:
+            return
+
+        if self.__selectedOverlay is not None:
+            
+            display = self._displayCtx.getDisplay(self.__selectedOverlay)
+            
+            display.removeListener('name',        self._name)
+            display.removeListener('overlayType', self._name)
+
+        self.__selectedOverlay = newOverlay
+
+        if newOverlay is not None:
+            display = self._displayCtx.getDisplay(newOverlay)
+            display.addListener('name',
+                                self._name,
+                                self.__overlayNameChanged)
+            display.addListener('overlayType',
+                                self._name,
+                                self.__overlayTypeChanged)
+
+        self.__overlayNameChanged()
+        self.__overlayTypeChanged()
+
+
+    def __overlayNameChanged(self, *a):
+
+        overlay = self.__selectedOverlay
+
+        if overlay is None:
+            self.__overlayNameLabel.SetLabel('')
+            return
+
+        display = self._displayCtx.getDisplay(overlay)
+
+        self.__overlayNameLabel.SetLabel(display.name)
+        
+
+    def __overlayTypeChanged(self, *a):
+
+        if self.__lutWidget is not None:
+            self.__controlRowSizer.Detach(self.__lutWidget)
+            self.__lutWidget.Destroy()
+            self.__lutWidget = None
+
+        if self.__selectedOpts is not None:
+            self.__selectedOpts.removeListener('lut', self._name)
+            self.__selectedOpts = None
+
+        overlay = self.__selectedOverlay
+        enabled = False
+
+        if overlay is not None:
+            opts = self._displayCtx.getOpts(overlay)
+
+            if isinstance(opts, fsldisplay.LabelOpts):
+                enabled = True
+
+        self.__overlayNameLabel.Show(    enabled)
+        self.__controlRow      .Show(    enabled)
+        self.__labelList       .Show(    enabled)
+        self.__disabledLabel   .Show(not enabled)
+
+        if not enabled:
+            self.Layout()
+            return
+
+        opts = self._displayCtx.getOpts(overlay)
+
+        opts.addListener('lut', self._name, self.__initLabelList)
+        
+        self.__selectedOpts = opts
+        self.__lutWidget    = props.makeWidget(
+            self.__controlRow, opts, 'lut')
+
+        self.__controlRowSizer.Insert(
+            0, self.__lutWidget, flag=wx.EXPAND, proportion=1)
+
+        self.__initLabelList()
+
+        self.Layout()
+
+
+    def __initLabelList(self, *a):
+
+        self.__labelList.Clear()
+
+        if self.__selectedOpts is None:
+            return
+
+        opts = self.__selectedOpts
+        lut  = opts.lut
+
+        names   = lut.names()
+        colours = lut.colours()
+        values  = lut.values()
+
+        for i, (name, colour, value) in enumerate(zip(names, colours, values)):
+            self.__labelList.Append(name)
+
+            widget = LabelWidget(self, opts, lut, value)
+            self.__labelList.SetItemWidget(i, widget)
+
+
+    def __onLabelAdd(self, ev):
+        pass
+
+    
+    def __onLabelRemove(self, ev):
+        pass
+
+
+    def __onLabelEdit(self, ev):
+        pass
diff --git a/fsl/fslview/displaycontext/labelopts.py b/fsl/fslview/displaycontext/labelopts.py
index e8ccbb095..e8d63d0c3 100644
--- a/fsl/fslview/displaycontext/labelopts.py
+++ b/fsl/fslview/displaycontext/labelopts.py
@@ -27,7 +27,12 @@ class LabelOpts(volumeopts.ImageOpts):
         names = [lut.lutName() for lut in luts]
 
         self.getProp('lut').setChoices(luts, names, self)
-
+        
+        # TODO create a copy of this LUT? If the user
+        # modifies a LUT for a specific image, do we
+        # want those modfications to be propagated to
+        # other images which also use the same LUT?
+        # Because that's what's happening currently.
         self.lut = luts[0]
 
         volumeopts.ImageOpts.__init__(self, overlay, *args, **kwargs)
diff --git a/fsl/fslview/views/canvaspanel.py b/fsl/fslview/views/canvaspanel.py
index 880321523..c9e44f0e9 100644
--- a/fsl/fslview/views/canvaspanel.py
+++ b/fsl/fslview/views/canvaspanel.py
@@ -192,6 +192,8 @@ class CanvasPanel(viewpanel.ViewPanel):
                 fslcontrols.OverlayDisplayToolBar, False, self),
             'toggleLocationPanel'     : lambda *a: self.togglePanel(
                 fslcontrols.LocationPanel),
+            'toggleLookupTablePanel'  : lambda *a: self.togglePanel(
+                fslcontrols.LookupTablePanel), 
         }.items() + extraActions.items())
         
         viewpanel.ViewPanel.__init__(
-- 
GitLab