From b72fd7b1de203b42526ec776c52280fe0f0db2cf Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Tue, 23 Jun 2015 12:20:30 +0100
Subject: [PATCH] Module import fix in gl.__init__, for software renderer
 initialisation. Opts class patching for new colour maps/luts is now in
 colourmaps module. New LUT option in LookupTablePanel is functional, but am
 chasing down the fact that the LUT option is not refreshed automatically when
 a new LUT is added.

---
 fsl/data/strings.py                      | 20 +++--
 fsl/fslview/actions/loadcolourmap.py     | 31 ++------
 fsl/fslview/colourmaps.py                | 93 ++++++++++++++++++++----
 fsl/fslview/controls/lookuptablepanel.py | 75 ++++++++++++++++++-
 fsl/fslview/gl/__init__.py               |  9 +--
 5 files changed, 174 insertions(+), 54 deletions(-)

diff --git a/fsl/data/strings.py b/fsl/data/strings.py
index 4e8ed5ea2..c933d5210 100644
--- a/fsl/data/strings.py
+++ b/fsl/data/strings.py
@@ -72,7 +72,9 @@ messages = TypeDict({
                                        'uses a lookup table',
 
     'LookupTablePanel.labelExists' : 'The {} LUT already contains a '
-                                     'label with value {}'
+                                     'label with value {}',
+
+    'NewLutDialog.newLut' : 'Enter a name for the new LUT', 
     
 })
 
@@ -112,6 +114,7 @@ titles = TypeDict({
     'HistogramToolBar'      : 'Histogram settings',
     'LookupTablePanel'      : 'Lookup tables',
     'LutLabelDialog'        : 'New LUT label',
+    'NewLutDialog'          : 'New LUT',
 
     'LookupTablePanel.labelExists' : 'Label already exists',
 })
@@ -181,11 +184,16 @@ labels = TypeDict({
     'LookupTablePanel.saveLut'  : 'Save',
     'LookupTablePanel.loadLut'  : 'Load',
 
-    'LutLabelDialog.value'  : 'Value',
-    'LutLabelDialog.name'   : 'Name',
-    'LutLabelDialog.colour' : 'Colour',
-    'LutLabelDialog.ok'     : 'Ok',
-    'LutLabelDialog.cancel' : 'Cancel',
+    'LutLabelDialog.value'    : 'Value',
+    'LutLabelDialog.name'     : 'Name',
+    'LutLabelDialog.colour'   : 'Colour',
+    'LutLabelDialog.ok'       : 'Ok',
+    'LutLabelDialog.cancel'   : 'Cancel',
+    'LutLabelDialog.newLabel' : 'New label',
+
+    'NewLutDialog.ok'     : 'Ok',
+    'NewLutDialog.cancel' : 'Cancel',
+    'NewLutDialog.newLut' : 'New LUT', 
 })
 
 
diff --git a/fsl/fslview/actions/loadcolourmap.py b/fsl/fslview/actions/loadcolourmap.py
index ae2eff14d..c965a2c90 100644
--- a/fsl/fslview/actions/loadcolourmap.py
+++ b/fsl/fslview/actions/loadcolourmap.py
@@ -8,15 +8,11 @@
 import logging
 import os.path as op
 
-import fsl.data.strings                      as strings
-import fsl.fslview.actions                   as actions
-import fsl.fslview.colourmaps                as fslcmap
-import fsl.fslview.displaycontext.volumeopts as volumeopts
+import fsl.data.strings       as strings
+import fsl.fslview.actions    as actions
+import fsl.fslview.colourmaps as fslcmap
 
 
-# TODO You will need to patch ColourMap instances
-# for any new display option types
-
 
 log = logging.getLogger(__name__)
 
@@ -74,23 +70,10 @@ class LoadColourMapAction(actions.Action):
             break
 
         # register the selected colour map file
-        fslcmap.registerColourMap(cmapFile, cmapName)
-
-        # update the VolumeOpts colour map property ...
-        #
-        # for future images
-        volumeopts.VolumeOpts.cmap.setConstraint(
-            None,
-            'cmapNames',
-            fslcmap.getColourMaps())
-
-        # and for any existing VolumeOpts instances
-        for overlay in self._overlayList:
-            opts = self._displayCtx.getOpts(overlay)
-            if isinstance(opts, volumeopts.VolumeOpts):
-                opts.setConstraint('cmap',
-                                   'cmapNames',
-                                   fslcmap.getColourMaps())
+        fslcmap.registerColourMap(cmapFile,
+                                  self._overlayList,
+                                  self._displayCtx,
+                                  cmapName)
 
         # ask the user if they want to install
         # the colour map for future use
diff --git a/fsl/fslview/colourmaps.py b/fsl/fslview/colourmaps.py
index 5d39d7b54..6bc98900e 100644
--- a/fsl/fslview/colourmaps.py
+++ b/fsl/fslview/colourmaps.py
@@ -131,6 +131,7 @@ and generating/manipulating colours.:
 """
 
 
+import logging
 import glob
 import shutil
 import bisect
@@ -144,9 +145,6 @@ import matplotlib.cm     as mplcm
 
 import props
 
-import logging
-
-
 log = logging.getLogger(__name__)
 
 
@@ -483,8 +481,8 @@ def init():
             mapFile = op.join(rdir, '{}.{}'.format(key, suffix))
 
             try:
-                if   suffix == 'cmap': registerColourMap(  mapFile, name)
-                elif suffix == 'lut':  registerLookupTable(mapFile, name)
+                if   suffix == 'cmap': registerColourMap(  mapFile, name=name)
+                elif suffix == 'lut':  registerLookupTable(mapFile, name=name)
                 
                 register[name].installed = True
 
@@ -493,19 +491,29 @@ def init():
                          'file {}: {}'.format(suffix, mapFile, str(e)))
 
 
-def registerColourMap(cmapFile, name=None):
+def registerColourMap(cmapFile, overlayList=None, displayCtx=None, name=None):
     """Loads RGB data from the given file, and registers
     it as a :mod:`matplotlib` :class:`~matplotlib.colors.ListedColormap`
     instance.
 
-    :arg cmapFile: Name of a file containing RGB values
+    :arg cmapFile:    Name of a file containing RGB values
+
+    :arg overlayList: A :class:`.OverlayList` instance which contains all
+                      overlays that are being displayed (can be ``None``).
     
-    :arg name:     Name to give the colour map. If ``None``, defaults
-                   to the file name prefix.
+    :arg displayCtx:  A :class:`.DisplayContext` instance describing how
+                      the overlays in ``overlayList`` are being displayed.
+                      Must be provided if ``overlayList`` is provided.
+
+    :arg name:        Name to give the colour map. If ``None``, defaults
+                      to the file name prefix.
     """
     if name is None:
         name = op.basename(cmapFile).split('.')[0]
 
+    if overlayList is None:
+        overlayList = []
+
     data = np.loadtxt(cmapFile)
     cmap = colors.ListedColormap(data, name)
 
@@ -515,22 +523,55 @@ def registerColourMap(cmapFile, name=None):
     mplcm.register_cmap(name, cmap)
 
     _cmaps[name] = _Map(name, cmap, cmapFile, False)
+
+    # TODO Any new Opts types which have a colour
+    #      map will need to be patched here
+
+    log.debug('Patching VolumeOpts instances and class '
+              'to support new colour map {}'.format(name))
+
+    import fsl.fslview.displaycontext as fsldisplay
+    
+    # update the VolumeOpts colour map property
+    # for any existing VolumeOpts instances
+    for overlay in overlayList:
+        opts = displayCtx.getOpts(overlay)
+        if isinstance(opts, fsldisplay.VolumeOpts):
+            opts.setConstraint('cmap',
+                               'cmapNames',
+                               getColourMaps()) 
+
+    # and for all future volume overlays
+    fsldisplay.VolumeOpts.cmap.setConstraint(
+        None,
+        'cmapNames',
+        getColourMaps())
                 
 
-def registerLookupTable(lut, name=None):
+def registerLookupTable(lut, overlayList=None, displayCtx=None, name=None):
     """Registers the given ``LookupTable`` instance (if ``lut`` is a string,
     it is assumed to be the name of a ``.lut`` file, which is loaded).
 
-    :arg lut:      A :class:`LookupTable` instance, or the name of a
-                   ``.lut`` file.
+    :arg lut:         A :class:`LookupTable` instance, or the name of a
+                      ``.lut`` file.
+
+    :arg overlayList: A :class:`.OverlayList` instance which contains all
+                      overlays that are being displayed (can be ``None``).
     
-    :arg name:     Name to give the lookup table. If ``None``, defaults
-                   to the file name prefix.    
+    :arg displayCtx:  A :class:`.DisplayContext` instance describing how
+                      the overlays in ``overlayList`` are being displayed.
+                      Must be provided if ``overlayList`` is provided. 
+    
+    :arg name:        Name to give the lookup table. If ``None``, defaults
+                      to the file name prefix.    
     """
 
     if isinstance(lut, basestring): lutFile = lut
     else:                           lutFile = None
 
+    if overlayList is None:
+        overlayList = []
+
     # lut may be either a file name
     # or a LookupTable instance
     if lutFile is not None:
@@ -542,9 +583,31 @@ def registerLookupTable(lut, name=None):
                   'lookup table: {}'.format(lutFile)) 
         
         lut = LookupTable(name, lutFile)
-
+    else:
+        if name is None:
+            name = lut.name
+            
     _luts[name] = _Map(name, lut, lutFile, False)
 
+    log.debug('Patching LabelOpts classes to support '
+              'new LookupTable {}'.format(name))
+
+    import fsl.fslview.displaycontext as fsldisplay
+
+    # Update the lut property for
+    # any existing label overlays
+    for overlay in overlayList:
+        opts = displayCtx.getOpts(overlay)
+
+        if not isinstance(opts, fsldisplay.LabelOpts):
+            continue
+
+        lutChoice = opts.getProp('lut')
+        lutChoice.addChoice(lut, lut.name, opts)
+
+    # and for any future label overlays
+    fsldisplay.LabelOpts.lut.addChoice(lut, lut.name)
+
 
 def getLookupTables():
     """Returns a list containing all available lookup tables."""
diff --git a/fsl/fslview/controls/lookuptablepanel.py b/fsl/fslview/controls/lookuptablepanel.py
index c482f9bb2..ea61d3de2 100644
--- a/fsl/fslview/controls/lookuptablepanel.py
+++ b/fsl/fslview/controls/lookuptablepanel.py
@@ -17,6 +17,7 @@ import pwidgets.elistbox          as elistbox
 
 import fsl.fslview.panel          as fslpanel
 import fsl.fslview.displaycontext as fsldisplay
+import fsl.fslview.colourmaps     as fslcmaps
 import fsl.data.strings           as strings
 
 
@@ -330,7 +331,17 @@ class LookupTablePanel(fslpanel.FSLViewPanel):
 
 
     def __onNewLut(self, ev):
-        pass
+
+        dlg = NewLutDialog(self.GetTopLevelParent())
+        if dlg.ShowModal() != wx.ID_OK:
+            return
+
+
+        log.debug('Creating and registering new '
+                  'LookupTable: {}'.format(dlg.name))
+
+        lut = fslcmaps.LookupTable(dlg.name)
+        fslcmaps.registerLookupTable(lut, self._overlayList, self._displayCtx)
 
 
     def __onCopyLut(self, ev):
@@ -394,7 +405,64 @@ class LookupTablePanel(fslpanel.FSLViewPanel):
         self.__selectedLut.enableListener('labels', self._name)
 
 
+class NewLutDialog(wx.Dialog):
+    """A dialog which is displayed when the user chooses to create a new LUT.
+
+    Prompts the user to enter a name.
+    """
+    
+    def __init__(self, parent):
+
+        wx.Dialog.__init__(self, parent, title=strings.titles[self])
+
+        self._message = wx.StaticText(self)
+        self._name    = wx.TextCtrl(  self)
+        self._ok      = wx.Button(    self)
+        self._cancel  = wx.Button(    self)
+
+        self._message.SetLabel(strings.messages[self, 'newLut'])
+        self._ok     .SetLabel(strings.labels[  self, 'ok'])
+        self._cancel .SetLabel(strings.labels[  self, 'cancel'])
+        self._name   .SetValue(strings.labels[  self, 'newLut'])
+
+        self._sizer    = wx.BoxSizer(wx.VERTICAL)
+        self._btnSizer = wx.BoxSizer(wx.HORIZONTAL)
+        
+        self.SetSizer(self._sizer)
+
+        self._sizer   .Add(self._message,  flag=wx.EXPAND | wx.ALL, border=10)
+        self._sizer   .Add(self._name,     flag=wx.EXPAND | wx.ALL, border=10)
+        self._sizer   .Add(self._btnSizer, flag=wx.EXPAND)
+        self._btnSizer.Add(self._ok,       flag=wx.EXPAND, proportion=1)
+        self._btnSizer.Add(self._cancel,   flag=wx.EXPAND, proportion=1)
+
+        self._ok    .Bind(wx.EVT_BUTTON, self.onOk)
+        self._cancel.Bind(wx.EVT_BUTTON, self.onCancel)
+
+        self.Fit()
+        self.Layout()
+
+        self.CentreOnParent()
+
+        self.name = None
+
+
+    def onOk(self, ev):
+        self.name = self._name.GetValue()
+        self.EndModal(wx.ID_OK)
+
+
+    def onCancel(self, ev):
+        self.EndModal(wx.ID_CANCEL)
+ 
+        
+
 class LutLabelDialog(wx.Dialog):
+    """A dialog which is displayed when the user adds a new label to the
+    current :class:`.LookupTable`.
+
+    Prompts the user to enter a label value, name, and colour.
+    """
 
     def __init__(self, parent):
 
@@ -416,9 +484,8 @@ class LutLabelDialog(wx.Dialog):
         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._name       .SetValue(strings.labels[self, 'newLabel'])
+        self._value      .SetValue(0)
 
         self._sizer = wx.GridSizer(4, 2)
         self.SetSizer(self._sizer)
diff --git a/fsl/fslview/gl/__init__.py b/fsl/fslview/gl/__init__.py
index 7cf448c83..e63a3ca6c 100644
--- a/fsl/fslview/gl/__init__.py
+++ b/fsl/fslview/gl/__init__.py
@@ -197,10 +197,9 @@ def bootstrap(glVersion=None):
         log.debug('Software-based rendering detected - '
                   'lowering default performance settings.')
 
-        import fsl.fslview.displaycontext.display   as di
-        import fsl.fslview.displaycontext.sceneopts as so
+        import fsl.fslview.displaycontext as dc
 
-        so.SceneOpts.performance.setConstraint(None, 'default', 2)
+        dc.SceneOpts.performance.setConstraint(None, 'default', 2)
 
         # And disable some fancy options - spline
         # may have been disabled above, so absorb
@@ -208,8 +207,8 @@ def bootstrap(glVersion=None):
 
         # TODO Remove this code duplication
         try:
-            di.VolumeOpts   .interpolation.removeChoice('spline')
-            di.RGBVectorOpts.interpolation.removeChoice('spline')
+            dc.VolumeOpts   .interpolation.removeChoice('spline')
+            dc.RGBVectorOpts.interpolation.removeChoice('spline')
             
         except ValueError: pass
 
-- 
GitLab