123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- """
- Builtin colormaps, colormap handling utilities, and the `ScalarMappable` mixin.
- .. seealso::
- :doc:`/gallery/color/colormap_reference` for a list of builtin colormaps.
- :doc:`/tutorials/colors/colormap-manipulation` for examples of how to
- make colormaps.
- :doc:`/tutorials/colors/colormaps` an in-depth discussion of
- choosing colormaps.
- :doc:`/tutorials/colors/colormapnorms` for more details about data
- normalization.
- """
- from collections.abc import MutableMapping
- import functools
- import numpy as np
- from numpy import ma
- import matplotlib as mpl
- import matplotlib.colors as colors
- import matplotlib.cbook as cbook
- from matplotlib._cm import datad
- from matplotlib._cm_listed import cmaps as cmaps_listed
- def _reverser(f, x): # Deprecated, remove this at the same time as revcmap.
- return f(1 - x) # Toplevel helper for revcmap ensuring cmap picklability.
- @cbook.deprecated("3.2", alternative="Colormap.reversed()")
- def revcmap(data):
- """Can only handle specification *data* in dictionary format."""
- data_r = {}
- for key, val in data.items():
- if callable(val):
- # Return a partial object so that the result is picklable.
- valnew = functools.partial(_reverser, val)
- else:
- # Flip x and exchange the y values facing x = 0 and x = 1.
- valnew = [(1.0 - x, y1, y0) for x, y0, y1 in reversed(val)]
- data_r[key] = valnew
- return data_r
- LUTSIZE = mpl.rcParams['image.lut']
- def _gen_cmap_registry():
- """
- Generate a dict mapping standard colormap names to standard colormaps, as
- well as the reversed colormaps.
- """
- cmap_d = {**cmaps_listed}
- for name, spec in datad.items():
- cmap_d[name] = ( # Precache the cmaps at a fixed lutsize..
- colors.LinearSegmentedColormap(name, spec, LUTSIZE)
- if 'red' in spec else
- colors.ListedColormap(spec['listed'], name)
- if 'listed' in spec else
- colors.LinearSegmentedColormap.from_list(name, spec, LUTSIZE))
- # Generate reversed cmaps.
- for cmap in list(cmap_d.values()):
- rmap = cmap.reversed()
- cmap._global = True
- rmap._global = True
- cmap_d[rmap.name] = rmap
- return cmap_d
- class _DeprecatedCmapDictWrapper(MutableMapping):
- """Dictionary mapping for deprecated _cmap_d access."""
- def __init__(self, cmap_registry):
- self._cmap_registry = cmap_registry
- def __delitem__(self, key):
- self._warn_deprecated()
- self._cmap_registry.__delitem__(key)
- def __getitem__(self, key):
- self._warn_deprecated()
- return self._cmap_registry.__getitem__(key)
- def __iter__(self):
- self._warn_deprecated()
- return self._cmap_registry.__iter__()
- def __len__(self):
- self._warn_deprecated()
- return self._cmap_registry.__len__()
- def __setitem__(self, key, val):
- self._warn_deprecated()
- self._cmap_registry.__setitem__(key, val)
- def get(self, key, default=None):
- self._warn_deprecated()
- return self._cmap_registry.get(key, default)
- def _warn_deprecated(self):
- cbook.warn_deprecated(
- "3.3",
- message="The global colormaps dictionary is no longer "
- "considered public API.",
- alternative="Please use register_cmap() and get_cmap() to "
- "access the contents of the dictionary."
- )
- _cmap_registry = _gen_cmap_registry()
- locals().update(_cmap_registry)
- # This is no longer considered public API
- cmap_d = _DeprecatedCmapDictWrapper(_cmap_registry)
- # Continue with definitions ...
- def register_cmap(name=None, cmap=None, data=None, lut=None):
- """
- Add a colormap to the set recognized by :func:`get_cmap`.
- It can be used in two ways::
- register_cmap(name='swirly', cmap=swirly_cmap)
- register_cmap(name='choppy', data=choppydata, lut=128)
- In the first case, *cmap* must be a :class:`matplotlib.colors.Colormap`
- instance. The *name* is optional; if absent, the name will
- be the :attr:`~matplotlib.colors.Colormap.name` attribute of the *cmap*.
- The second case is deprecated. Here, the three arguments are passed to
- the :class:`~matplotlib.colors.LinearSegmentedColormap` initializer,
- and the resulting colormap is registered. Instead of this implicit
- colormap creation, create a `.LinearSegmentedColormap` and use the first
- case: ``register_cmap(cmap=LinearSegmentedColormap(name, data, lut))``.
- Notes
- -----
- Registering a colormap stores a reference to the colormap object
- which can currently be modified and inadvertantly change the global
- colormap state. This behavior is deprecated and in Matplotlib 3.5
- the registered colormap will be immutable.
- """
- cbook._check_isinstance((str, None), name=name)
- if name is None:
- try:
- name = cmap.name
- except AttributeError as err:
- raise ValueError("Arguments must include a name or a "
- "Colormap") from err
- if isinstance(cmap, colors.Colormap):
- cmap._global = True
- _cmap_registry[name] = cmap
- return
- if lut is not None or data is not None:
- cbook.warn_deprecated(
- "3.3",
- message="Passing raw data via parameters data and lut to "
- "register_cmap() is deprecated since %(since)s and will "
- "become an error %(removal)s. Instead use: register_cmap("
- "cmap=LinearSegmentedColormap(name, data, lut))")
- # For the remainder, let exceptions propagate.
- if lut is None:
- lut = mpl.rcParams['image.lut']
- cmap = colors.LinearSegmentedColormap(name, data, lut)
- cmap._global = True
- _cmap_registry[name] = cmap
- def get_cmap(name=None, lut=None):
- """
- Get a colormap instance, defaulting to rc values if *name* is None.
- Colormaps added with :func:`register_cmap` take precedence over
- built-in colormaps.
- Notes
- -----
- Currently, this returns the global colormap object, which is deprecated.
- In Matplotlib 3.5, you will no longer be able to modify the global
- colormaps in-place.
- Parameters
- ----------
- name : `matplotlib.colors.Colormap` or str or None, default: None
- If a `.Colormap` instance, it will be returned. Otherwise, the name of
- a colormap known to Matplotlib, which will be resampled by *lut*. The
- default, None, means :rc:`image.cmap`.
- lut : int or None, default: None
- If *name* is not already a Colormap instance and *lut* is not None, the
- colormap will be resampled to have *lut* entries in the lookup table.
- """
- if name is None:
- name = mpl.rcParams['image.cmap']
- if isinstance(name, colors.Colormap):
- return name
- cbook._check_in_list(sorted(_cmap_registry), name=name)
- if lut is None:
- return _cmap_registry[name]
- else:
- return _cmap_registry[name]._resample(lut)
- class ScalarMappable:
- """
- A mixin class to map scalar data to RGBA.
- The ScalarMappable applies data normalization before returning RGBA colors
- from the given colormap.
- """
- def __init__(self, norm=None, cmap=None):
- """
- Parameters
- ----------
- norm : `matplotlib.colors.Normalize` (or subclass thereof)
- The normalizing object which scales data, typically into the
- interval ``[0, 1]``.
- If *None*, *norm* defaults to a *colors.Normalize* object which
- initializes its scaling based on the first data processed.
- cmap : str or `~matplotlib.colors.Colormap`
- The colormap used to map normalized data values to RGBA colors.
- """
- self._A = None
- self.norm = None # So that the setter knows we're initializing.
- self.set_norm(norm) # The Normalize instance of this ScalarMappable.
- self.cmap = None # So that the setter knows we're initializing.
- self.set_cmap(cmap) # The Colormap instance of this ScalarMappable.
- #: The last colorbar associated with this ScalarMappable. May be None.
- self.colorbar = None
- self.callbacksSM = cbook.CallbackRegistry()
- self._update_dict = {'array': False}
- def _scale_norm(self, norm, vmin, vmax):
- """
- Helper for initial scaling.
- Used by public functions that create a ScalarMappable and support
- parameters *vmin*, *vmax* and *norm*. This makes sure that a *norm*
- will take precedence over *vmin*, *vmax*.
- Note that this method does not set the norm.
- """
- if vmin is not None or vmax is not None:
- self.set_clim(vmin, vmax)
- if norm is not None:
- cbook.warn_deprecated(
- "3.3",
- message="Passing parameters norm and vmin/vmax "
- "simultaneously is deprecated since %(since)s and "
- "will become an error %(removal)s. Please pass "
- "vmin/vmax directly to the norm when creating it.")
- # always resolve the autoscaling so we have concrete limits
- # rather than deferring to draw time.
- self.autoscale_None()
- def to_rgba(self, x, alpha=None, bytes=False, norm=True):
- """
- Return a normalized rgba array corresponding to *x*.
- In the normal case, *x* is a 1-D or 2-D sequence of scalars, and
- the corresponding ndarray of rgba values will be returned,
- based on the norm and colormap set for this ScalarMappable.
- There is one special case, for handling images that are already
- rgb or rgba, such as might have been read from an image file.
- If *x* is an ndarray with 3 dimensions,
- and the last dimension is either 3 or 4, then it will be
- treated as an rgb or rgba array, and no mapping will be done.
- The array can be uint8, or it can be floating point with
- values in the 0-1 range; otherwise a ValueError will be raised.
- If it is a masked array, the mask will be ignored.
- If the last dimension is 3, the *alpha* kwarg (defaulting to 1)
- will be used to fill in the transparency. If the last dimension
- is 4, the *alpha* kwarg is ignored; it does not
- replace the pre-existing alpha. A ValueError will be raised
- if the third dimension is other than 3 or 4.
- In either case, if *bytes* is *False* (default), the rgba
- array will be floats in the 0-1 range; if it is *True*,
- the returned rgba array will be uint8 in the 0 to 255 range.
- If norm is False, no normalization of the input data is
- performed, and it is assumed to be in the range (0-1).
- """
- # First check for special case, image input:
- try:
- if x.ndim == 3:
- if x.shape[2] == 3:
- if alpha is None:
- alpha = 1
- if x.dtype == np.uint8:
- alpha = np.uint8(alpha * 255)
- m, n = x.shape[:2]
- xx = np.empty(shape=(m, n, 4), dtype=x.dtype)
- xx[:, :, :3] = x
- xx[:, :, 3] = alpha
- elif x.shape[2] == 4:
- xx = x
- else:
- raise ValueError("Third dimension must be 3 or 4")
- if xx.dtype.kind == 'f':
- if norm and (xx.max() > 1 or xx.min() < 0):
- raise ValueError("Floating point image RGB values "
- "must be in the 0..1 range.")
- if bytes:
- xx = (xx * 255).astype(np.uint8)
- elif xx.dtype == np.uint8:
- if not bytes:
- xx = xx.astype(np.float32) / 255
- else:
- raise ValueError("Image RGB array must be uint8 or "
- "floating point; found %s" % xx.dtype)
- return xx
- except AttributeError:
- # e.g., x is not an ndarray; so try mapping it
- pass
- # This is the normal case, mapping a scalar array:
- x = ma.asarray(x)
- if norm:
- x = self.norm(x)
- rgba = self.cmap(x, alpha=alpha, bytes=bytes)
- return rgba
- def set_array(self, A):
- """
- Set the image array from numpy array *A*.
- Parameters
- ----------
- A : ndarray
- """
- self._A = A
- self._update_dict['array'] = True
- def get_array(self):
- """Return the data array."""
- return self._A
- def get_cmap(self):
- """Return the `.Colormap` instance."""
- return self.cmap
- def get_clim(self):
- """
- Return the values (min, max) that are mapped to the colormap limits.
- """
- return self.norm.vmin, self.norm.vmax
- def set_clim(self, vmin=None, vmax=None):
- """
- Set the norm limits for image scaling.
- Parameters
- ----------
- vmin, vmax : float
- The limits.
- The limits may also be passed as a tuple (*vmin*, *vmax*) as a
- single positional argument.
- .. ACCEPTS: (vmin: float, vmax: float)
- """
- if vmax is None:
- try:
- vmin, vmax = vmin
- except (TypeError, ValueError):
- pass
- if vmin is not None:
- self.norm.vmin = colors._sanitize_extrema(vmin)
- if vmax is not None:
- self.norm.vmax = colors._sanitize_extrema(vmax)
- self.changed()
- def get_alpha(self):
- """
- Returns
- -------
- float
- Always returns 1.
- """
- # This method is intended to be overridden by Artist sub-classes
- return 1.
- def set_cmap(self, cmap):
- """
- Set the colormap for luminance data.
- Parameters
- ----------
- cmap : `.Colormap` or str or None
- """
- in_init = self.cmap is None
- cmap = get_cmap(cmap)
- self.cmap = cmap
- if not in_init:
- self.changed() # Things are not set up properly yet.
- def set_norm(self, norm):
- """
- Set the normalization instance.
- Parameters
- ----------
- norm : `.Normalize` or None
- Notes
- -----
- If there are any colorbars using the mappable for this norm, setting
- the norm of the mappable will reset the norm, locator, and formatters
- on the colorbar to default.
- """
- cbook._check_isinstance((colors.Normalize, None), norm=norm)
- in_init = self.norm is None
- if norm is None:
- norm = colors.Normalize()
- self.norm = norm
- if not in_init:
- self.changed() # Things are not set up properly yet.
- def autoscale(self):
- """
- Autoscale the scalar limits on the norm instance using the
- current array
- """
- if self._A is None:
- raise TypeError('You must first set_array for mappable')
- self.norm.autoscale(self._A)
- self.changed()
- def autoscale_None(self):
- """
- Autoscale the scalar limits on the norm instance using the
- current array, changing only limits that are None
- """
- if self._A is None:
- raise TypeError('You must first set_array for mappable')
- self.norm.autoscale_None(self._A)
- self.changed()
- def _add_checker(self, checker):
- """
- Add an entry to a dictionary of boolean flags
- that are set to True when the mappable is changed.
- """
- self._update_dict[checker] = False
- def _check_update(self, checker):
- """Return whether mappable has changed since the last check."""
- if self._update_dict[checker]:
- self._update_dict[checker] = False
- return True
- return False
- def changed(self):
- """
- Call this whenever the mappable is changed to notify all the
- callbackSM listeners to the 'changed' signal.
- """
- self.callbacksSM.process('changed', self)
- for key in self._update_dict:
- self._update_dict[key] = True
- self.stale = True
- update_dict = cbook._deprecate_privatize_attribute("3.3")
- @cbook.deprecated("3.3")
- def add_checker(self, checker):
- return self._add_checker(checker)
- @cbook.deprecated("3.3")
- def check_update(self, checker):
- return self._check_update(checker)
|