summaryrefslogtreecommitdiff
path: root/silx/gui/plot/Colormap.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/Colormap.py')
-rw-r--r--silx/gui/plot/Colormap.py243
1 files changed, 211 insertions, 32 deletions
diff --git a/silx/gui/plot/Colormap.py b/silx/gui/plot/Colormap.py
index abe8546..9adf0d4 100644
--- a/silx/gui/plot/Colormap.py
+++ b/silx/gui/plot/Colormap.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2015-2017 European Synchrotron Radiation Facility
+# Copyright (c) 2015-2018 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -29,7 +29,7 @@ from __future__ import absolute_import
__authors__ = ["T. Vincent", "H.Payno"]
__license__ = "MIT"
-__date__ = "05/12/2016"
+__date__ = "08/01/2018"
from silx.gui import qt
import copy as copy_mdl
@@ -37,6 +37,7 @@ import numpy
from .matplotlib import Colormap as MPLColormap
import logging
from silx.math.combo import min_max
+from silx.utils.exceptions import NotEditableError
_logger = logging.getLogger(__file__)
@@ -62,7 +63,7 @@ class Colormap(qt.QObject):
Nx3 or Nx4 numpy array of RGB(A) colors,
either uint8 or float in [0, 1].
If 'name' is None, then this array is used as the colormap.
- :param str norm: Normalization: 'linear' (default) or 'log'
+ :param str normalization: Normalization: 'linear' (default) or 'log'
:param float vmin:
Lower bound of the colormap or None for autoscale (default)
:param float vmax:
@@ -79,6 +80,7 @@ class Colormap(qt.QObject):
"""Tuple of managed normalizations"""
sigChanged = qt.Signal()
+ """Signal emitted when the colormap has changed."""
def __init__(self, name='gray', colors=None, normalization=LINEAR, vmin=None, vmax=None):
qt.QObject.__init__(self)
@@ -98,10 +100,11 @@ class Colormap(qt.QObject):
self._normalization = str(normalization)
self._vmin = float(vmin) if vmin is not None else None
self._vmax = float(vmax) if vmax is not None else None
+ self._editable = True
def isAutoscale(self):
"""Return True if both min and max are in autoscale mode"""
- return self._vmin is None or self._vmax is None
+ return self._vmin is None and self._vmax is None
def getName(self):
"""Return the name of the colormap
@@ -115,35 +118,69 @@ class Colormap(qt.QObject):
else:
self._colors = numpy.array(colors, copy=True)
+ def getNColors(self, nbColors=None):
+ """Returns N colors computed by sampling the colormap regularly.
+
+ :param nbColors:
+ The number of colors in the returned array or None for the default value.
+ The default value is 256 for colormap with a name (see :meth:`setName`) and
+ it is the size of the LUT for colormap defined with :meth:`setColormapLUT`.
+ :type nbColors: int or None
+ :return: 2D array of uint8 of shape (nbColors, 4)
+ :rtype: numpy.ndarray
+ """
+ # Handle default value for nbColors
+ if nbColors is None:
+ lut = self.getColormapLUT()
+ if lut is not None: # In this case uses LUT length
+ nbColors = len(lut)
+ else: # Default to 256
+ nbColors = 256
+
+ nbColors = int(nbColors)
+
+ colormap = self.copy()
+ colormap.setNormalization(Colormap.LINEAR)
+ colormap.setVRange(vmin=None, vmax=None)
+ colors = colormap.applyToData(
+ numpy.arange(nbColors, dtype=numpy.int))
+ return colors
+
def setName(self, name):
- """Set the name of the colormap and load the colors corresponding to
- the name
+ """Set the name of the colormap to use.
- :param str name: the name of the colormap (should be in ['gray',
+ :param str name: The name of the colormap.
+ At least the following names are supported: 'gray',
'reversed gray', 'temperature', 'red', 'green', 'blue', 'jet',
- 'viridis', 'magma', 'inferno', 'plasma']
+ 'viridis', 'magma', 'inferno', 'plasma'.
"""
+ if self.isEditable() is False:
+ raise NotEditableError('Colormap is not editable')
assert name in self.getSupportedColormaps()
self._name = str(name)
self._colors = None
self.sigChanged.emit()
def getColormapLUT(self):
- """Return the list of colors for the colormap. None if not setted
-
- :return: the list of colors for the colormap. None if not setted
- :rtype: numpy.ndarray
+ """Return the list of colors for the colormap or None if not set
+
+ :return: the list of colors for the colormap or None if not set
+ :rtype: numpy.ndarray or None
"""
- return self._colors
+ if self._colors is None:
+ return None
+ else:
+ return numpy.array(self._colors, copy=True)
def setColormapLUT(self, colors):
- """
- Set the colors of the colormap.
+ """Set the colors of the colormap.
:param numpy.ndarray colors: the colors of the LUT
- .. warning: this will set the value of name to an empty string
+ .. warning: this will set the value of name to None
"""
+ if self.isEditable() is False:
+ raise NotEditableError('Colormap is not editable')
self._setColors(colors)
if len(colors) is 0:
self._colors = None
@@ -153,7 +190,7 @@ class Colormap(qt.QObject):
def getNormalization(self):
"""Return the normalization of the colormap ('log' or 'linear')
-
+
:return: the normalization of the colormap
:rtype: str
"""
@@ -164,12 +201,14 @@ class Colormap(qt.QObject):
:param str norm: the norm to set
"""
+ if self.isEditable() is False:
+ raise NotEditableError('Colormap is not editable')
self._normalization = str(norm)
self.sigChanged.emit()
def getVMin(self):
"""Return the lower bound of the colormap
-
+
:return: the lower bound of the colormap
:rtype: float or None
"""
@@ -182,10 +221,12 @@ class Colormap(qt.QObject):
(default)
value)
"""
+ if self.isEditable() is False:
+ raise NotEditableError('Colormap is not editable')
if vmin is not None:
- if self._vmax is not None and vmin >= self._vmax:
- err = "Can't set vmin because vmin >= vmax."
- err += "vmin = %s, vmax = %s" %(vmin, self._vmax)
+ if self._vmax is not None and vmin > self._vmax:
+ err = "Can't set vmin because vmin >= vmax. " \
+ "vmin = %s, vmax = %s" % (vmin, self._vmax)
raise ValueError(err)
self._vmin = vmin
@@ -193,7 +234,7 @@ class Colormap(qt.QObject):
def getVMax(self):
"""Return the upper bounds of the colormap or None
-
+
:return: the upper bounds of the colormap or None
:rtype: float or None
"""
@@ -205,15 +246,35 @@ class Colormap(qt.QObject):
:param float vmax: Upper bounds of the colormap or None for autoscale
(default)
"""
+ if self.isEditable() is False:
+ raise NotEditableError('Colormap is not editable')
if vmax is not None:
- if self._vmin is not None and vmax <= self._vmin:
- err = "Can't set vmax because vmax <= vmin."
- err += "vmin = %s, vmax = %s" %(self._vmin, vmax)
+ if self._vmin is not None and vmax < self._vmin:
+ err = "Can't set vmax because vmax <= vmin. " \
+ "vmin = %s, vmax = %s" % (self._vmin, vmax)
raise ValueError(err)
self._vmax = vmax
self.sigChanged.emit()
+ def isEditable(self):
+ """ Return if the colormap is editable or not
+
+ :return: editable state of the colormap
+ :rtype: bool
+ """
+ return self._editable
+
+ def setEditable(self, editable):
+ """
+ Set the editable state of the colormap
+
+ :param bool editable: is the colormap editable
+ """
+ assert type(editable) is bool
+ self._editable = editable
+ self.sigChanged.emit()
+
def getColormapRange(self, data=None):
"""Return (vmin, vmax)
@@ -267,20 +328,24 @@ class Colormap(qt.QObject):
return vmin, vmax
def setVRange(self, vmin, vmax):
- """
- Set bounds to the colormap
+ """Set the bounds of the colormap
:param vmin: Lower bound of the colormap or None for autoscale
(default)
:param vmax: Upper bounds of the colormap or None for autoscale
(default)
"""
+ if self.isEditable() is False:
+ raise NotEditableError('Colormap is not editable')
if vmin is not None and vmax is not None:
- if vmin >= vmax:
- err = "Can't set vmin and vmax because vmin >= vmax"
- err += "vmin = %s, vmax = %s" %(vmin, self._vmax)
+ if vmin > vmax:
+ err = "Can't set vmin and vmax because vmin >= vmax " \
+ "vmin = %s, vmax = %s" % (vmin, vmax)
raise ValueError(err)
+ if self._vmin == vmin and self._vmax == vmax:
+ return
+
self._vmin = vmin
self._vmax = vmax
self.sigChanged.emit()
@@ -322,6 +387,8 @@ class Colormap(qt.QObject):
:param dict dic: the colormap as a dictionary
"""
+ if self.isEditable() is False:
+ raise NotEditableError('Colormap is not editable')
name = dic['name'] if 'name' in dic else None
colors = dic['colors'] if 'colors' in dic else None
vmin = dic['vmin'] if 'vmin' in dic else None
@@ -361,9 +428,9 @@ class Colormap(qt.QObject):
return colormap
def copy(self):
- """
+ """Return a copy of the Colormap.
- :return: a copy of the Colormap object
+ :rtype: silx.gui.plot.Colormap.Colormap
"""
return Colormap(name=self._name,
colors=copy_mdl.copy(self._colors),
@@ -408,3 +475,115 @@ class Colormap(qt.QObject):
numpy.array_equal(self.getColormapLUT(), other.getColormapLUT())
)
+ _SERIAL_VERSION = 1
+
+ def restoreState(self, byteArray):
+ """
+ Read the colormap state from a QByteArray.
+
+ :param qt.QByteArray byteArray: Stream containing the state
+ :return: True if the restoration sussseed
+ :rtype: bool
+ """
+ if self.isEditable() is False:
+ raise NotEditableError('Colormap is not editable')
+ stream = qt.QDataStream(byteArray, qt.QIODevice.ReadOnly)
+
+ className = stream.readQString()
+ if className != self.__class__.__name__:
+ _logger.warning("Classname mismatch. Found %s." % className)
+ return False
+
+ version = stream.readUInt32()
+ if version != self._SERIAL_VERSION:
+ _logger.warning("Serial version mismatch. Found %d." % version)
+ return False
+
+ name = stream.readQString()
+ isNull = stream.readBool()
+ if not isNull:
+ vmin = stream.readQVariant()
+ else:
+ vmin = None
+ isNull = stream.readBool()
+ if not isNull:
+ vmax = stream.readQVariant()
+ else:
+ vmax = None
+ normalization = stream.readQString()
+
+ # emit change event only once
+ old = self.blockSignals(True)
+ try:
+ self.setName(name)
+ self.setNormalization(normalization)
+ self.setVRange(vmin, vmax)
+ finally:
+ self.blockSignals(old)
+ self.sigChanged.emit()
+ return True
+
+ def saveState(self):
+ """
+ Save state of the colomap into a QDataStream.
+
+ :rtype: qt.QByteArray
+ """
+ data = qt.QByteArray()
+ stream = qt.QDataStream(data, qt.QIODevice.WriteOnly)
+
+ stream.writeQString(self.__class__.__name__)
+ stream.writeUInt32(self._SERIAL_VERSION)
+ stream.writeQString(self.getName())
+ stream.writeBool(self.getVMin() is None)
+ if self.getVMin() is not None:
+ stream.writeQVariant(self.getVMin())
+ stream.writeBool(self.getVMax() is None)
+ if self.getVMax() is not None:
+ stream.writeQVariant(self.getVMax())
+ stream.writeQString(self.getNormalization())
+ return data
+
+
+_PREFERRED_COLORMAPS = DEFAULT_COLORMAPS
+"""
+Tuple of preferred colormap names accessed with :meth:`preferredColormaps`.
+"""
+
+
+def preferredColormaps():
+ """Returns the name of the preferred colormaps.
+
+ This list is used by widgets allowing to change the colormap
+ like the :class:`ColormapDialog` as a subset of colormap choices.
+
+ :rtype: tuple of str
+ """
+ return _PREFERRED_COLORMAPS
+
+
+def setPreferredColormaps(colormaps):
+ """Set the list of preferred colormap names.
+
+ Warning: If a colormap name is not available
+ it will be removed from the list.
+
+ :param colormaps: Not empty list of colormap names
+ :type colormaps: iterable of str
+ :raise ValueError: if the list of available preferred colormaps is empty.
+ """
+ supportedColormaps = Colormap.getSupportedColormaps()
+ colormaps = tuple(
+ cmap for cmap in colormaps if cmap in supportedColormaps)
+ if len(colormaps) == 0:
+ raise ValueError("Cannot set preferred colormaps to an empty list")
+
+ global _PREFERRED_COLORMAPS
+ _PREFERRED_COLORMAPS = colormaps
+
+
+# Initialize preferred colormaps
+setPreferredColormaps(('gray', 'reversed gray',
+ 'temperature', 'red', 'green', 'blue', 'jet',
+ 'viridis', 'magma', 'inferno', 'plasma',
+ 'hsv'))