diff --git a/fsl/props/properties_types.py b/fsl/props/properties_types.py index e2145f221edc46b1c0a67808649b3c2fbfed0834..28d718ba31d1c362879ccec61047b70227be4c5d 100644 --- a/fsl/props/properties_types.py +++ b/fsl/props/properties_types.py @@ -684,39 +684,50 @@ class List(props.PropertyBase): instval[:] = value -# Currently just a string, will soon be a mplcolors.Colormap instance. -#class ColourMap(props.PropertyBase): -class ColourMap(String): +# TODO This would probably be better off as a subclass of Choice. Choice +# would need to be modified to allow for values of any type, not just +# Strings. Shouldn't be a major issue. +class ColourMap(props.PropertyBase): """ A property which encapsulates a matplotlib.colors.Colormap. """ def __init__(self, **kwargs): + """ + Creates a ColourMap property. If a default value is not + given, the matplotlib.cm.Greys_r colour map is used. + """ default = kwargs.get('default', None) - if default is None: default = 'Greys_r' - elif isinstance(default, mplcolors.Colormap): default = default.name - - #elif isinstance(default, str): default = mplcm.get_cmap(default) - - # raise ValueError( - # 'Invalid ColourMap default: '.format( - # default.__class__.__name__)) + if default is None: + default = mplcm.Greys_r + + elif isinstance(default, str): + default = mplcm.get_cmap(default) + + elif not isinstance(default, mplcolors.Colormap): + raise ValueError( + 'Invalid ColourMap default: '.format( + default.__class__.__name__)) kwargs['default'] = default props.PropertyBase.__init__(self, **kwargs) + def __set__(self, instance, value): + """ + Set the current ColourMap property value. If a string + is given, an attempt is made to convert it to a colour map, + via the matplotlib.cm.get_cmap function. + """ - # def __set__(self, instance, value): - - # if isinstance(value, str): - # value = mplcm.get_cmap(value) + if isinstance(value, str): + value = mplcm.get_cmap(value) - # elif not isinstance(value, mplcolors.Colormap): - # raise ValueError( - # 'Invalid ColourMap default: '.format( - # default.__class__.__name__)) + elif not isinstance(value, mplcolors.Colormap): + raise ValueError( + 'Invalid ColourMap value: '.format( + default.__class__.__name__)) - # props.PropertyBase.__set__(self, instance, value) + props.PropertyBase.__set__(self, instance, value) diff --git a/fsl/props/widgets.py b/fsl/props/widgets.py index 15abe6922200bbd07ad53b637fad67dc4a3c5073..349bd768489a4b14fec8f584ccce7a8e5cb76b9b 100644 --- a/fsl/props/widgets.py +++ b/fsl/props/widgets.py @@ -18,6 +18,12 @@ from collections import OrderedDict from collections import Iterable import wx +import wx.combo + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.colors as mplcolors +import matplotlib.cm as mplcm # the List property is complex enough to get its own module. from widgets_list import _List @@ -118,10 +124,7 @@ def _propBind(hasProps, propObj, propVal, guiObj, evType, labelMap=None): if labelMap is not None: propVal.set(valMap[value]) else: propVal.set(value) - val = propVal.get() - if val is None: val = '' - - guiObj.SetValue(val) + _guiUpdate(propVal.get()) # set up the callback functions for ev in evType: guiObj.Bind(ev, _propUpdate) @@ -383,11 +386,76 @@ def _Boolean(parent, hasProps, propObj, propVal): return checkBox +def _makeColourMapComboBox(parent, cmapDict, selected=None): + """ + Makes a wx.combo.BitmapComboBox which allows the user to select a + colour map from the given dictionary of + (name -> matplotlib.colors.Colormap) mappings. The name of each + colour map is shown in the combo box,, along with a little image + for each colour map, showing the colour range. + """ + + bitmaps = [] + width, height = 75, 15 + + cmapNames, cmaps = zip(*cmapDict.items()) + + if selected is not None: selected = cmapNames.index(selected) + else: selected = 0 + + # Make a little bitmap for every colour map. The bitmap + # is a (width*height*3) array of bytes + for cmapName, cmap in zip(cmapNames, cmaps): + + # create a single colour for each horizontal pixel + colours = cmap(np.linspace(0.0, 1.0, width)) + + # discard alpha values + colours = colours[:,:3] + + # repeat each horizontal pixel (height) times + colours = np.tile(colours, (height, 1, 1)) + + # scale to [0,255] and cast to uint8 + colours = colours * 255 + colours = np.array(colours, dtype=np.uint8) + + # make a wx Bitmap from the colour data + colours = colours.ravel(order='C') + bitmap = wx.BitmapFromBuffer(width, height, colours) + + bitmaps.append(bitmap) + + # create the combobox + cbox = wx.combo.BitmapComboBox( + parent, style=wx.CB_READONLY | wx.CB_DROPDOWN) + + for name,bitmap in zip(cmapNames, bitmaps): + cbox.Append(name, bitmap) + + cbox.SetSelection(selected) + + return cbox + + def _ColourMap(parent, hasProps, propObj, propVal): """ + Creates and returns a combobox, allowing the user to change + the value of the given ColourMap property. """ - return _String(parent, hasProps, propObj, propVal) + cmapNames = sorted(mplcm.datad.keys()) + cmapObjs = map(mplcm.get_cmap, cmapNames) + + valMap = OrderedDict(zip(cmapObjs, cmapNames)) + labelMap = OrderedDict(zip(cmapNames, cmapObjs)) + + cbox = _makeColourMapComboBox( + parent, labelMap, propVal.get().name) + + _propBind(hasProps, propObj, propVal, cbox, wx.EVT_COMBOBOX, valMap) + + return cbox def makeWidget(parent, hasProps, propName): diff --git a/fsl/utils/slicecanvas.py b/fsl/utils/slicecanvas.py index e6cdb2d86db68911d61e45ccc78330aaca515375..ff5c20b5d0005d5f42cbc55d245981b8a7b051c9 100644 --- a/fsl/utils/slicecanvas.py +++ b/fsl/utils/slicecanvas.py @@ -265,7 +265,7 @@ class SliceCanvas(wxgl.GLCanvas): self._ypos = self.ydim / 2 self._zpos = zpos - self._colourResolution = 64 + self._colourResolution = 256 # these attributes are created by _initGLData, # which is called on the first EVT_PAINT event @@ -420,7 +420,7 @@ class SliceCanvas(wxgl.GLCanvas): # Create [self.colourResolution] rgb values, # spanning the entire range of the image # colour map (see fsl.data.fslimage.Image) - colourmap = mplcm.get_cmap(iDisplay.cmap)(newRange) + colourmap = iDisplay.cmap(newRange) # The colour data is stored on # the GPU as 8 bit rgb triplets