diff --git a/fsl/data/strings.py b/fsl/data/strings.py index 4e8ed5ea2bebee8ea967fb8964776bcdb1652c2f..c933d5210f7a41b9d1947e47e0b68b54151fe542 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 ae2eff14d73376e5c1379ed32ac97e6c2acf5cc9..c965a2c907bf60da485ada94cf52972748a6b7ec 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 5d39d7b543c599a330c2a7ef53d2cc3cf7aaca6e..6bc98900e8c13aa615e5fe8ebeaea8f3955ecb0f 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 c482f9bb2644c230338390e11e168c0a6a3b14bb..ea61d3de2442ec9406b233ec6a582a64740060f1 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 7cf448c835abd48b44f49c5f61adb7fa4ed4cb0a..e63a3ca6c42c51107cbe413528cdecf4bec785da 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