From 270d5ddc31c26b62379e3caa9044dd75ccc71847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Picca=20Fr=C3=A9d=C3=A9ric-Emmanuel?= Date: Sun, 4 Mar 2018 10:20:27 +0100 Subject: New upstream version 0.7.0+dfsg --- silx/gui/plot/Colormap.py | 243 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 211 insertions(+), 32 deletions(-) (limited to 'silx/gui/plot/Colormap.py') 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')) -- cgit v1.2.3