123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745 |
- """
- Builtin colormaps, colormap handling utilities, and the `ScalarMappable` mixin.
- .. seealso::
- :doc:`/gallery/color/colormap_reference` for a list of builtin colormaps.
- :ref:`colormap-manipulation` for examples of how to make
- colormaps.
- :ref:`colormaps` an in-depth discussion of choosing
- colormaps.
- :ref:`colormapnorms` for more details about data normalization.
- """
- from collections.abc import Mapping
- import functools
- import numpy as np
- from numpy import ma
- import matplotlib as mpl
- from matplotlib import _api, colors, cbook, scale
- from matplotlib._cm import datad
- from matplotlib._cm_listed import cmaps as cmaps_listed
- _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))
- # Register colormap aliases for gray and grey.
- cmap_d['grey'] = cmap_d['gray']
- cmap_d['gist_grey'] = cmap_d['gist_gray']
- cmap_d['gist_yerg'] = cmap_d['gist_yarg']
- cmap_d['Grays'] = cmap_d['Greys']
- # Generate reversed cmaps.
- for cmap in list(cmap_d.values()):
- rmap = cmap.reversed()
- cmap_d[rmap.name] = rmap
- return cmap_d
- class ColormapRegistry(Mapping):
- r"""
- Container for colormaps that are known to Matplotlib by name.
- The universal registry instance is `matplotlib.colormaps`. There should be
- no need for users to instantiate `.ColormapRegistry` themselves.
- Read access uses a dict-like interface mapping names to `.Colormap`\s::
- import matplotlib as mpl
- cmap = mpl.colormaps['viridis']
- Returned `.Colormap`\s are copies, so that their modification does not
- change the global definition of the colormap.
- Additional colormaps can be added via `.ColormapRegistry.register`::
- mpl.colormaps.register(my_colormap)
- To get a list of all registered colormaps, you can do::
- from matplotlib import colormaps
- list(colormaps)
- """
- def __init__(self, cmaps):
- self._cmaps = cmaps
- self._builtin_cmaps = tuple(cmaps)
- # A shim to allow register_cmap() to force an override
- self._allow_override_builtin = False
- def __getitem__(self, item):
- try:
- return self._cmaps[item].copy()
- except KeyError:
- raise KeyError(f"{item!r} is not a known colormap name") from None
- def __iter__(self):
- return iter(self._cmaps)
- def __len__(self):
- return len(self._cmaps)
- def __str__(self):
- return ('ColormapRegistry; available colormaps:\n' +
- ', '.join(f"'{name}'" for name in self))
- def __call__(self):
- """
- Return a list of the registered colormap names.
- This exists only for backward-compatibility in `.pyplot` which had a
- ``plt.colormaps()`` method. The recommended way to get this list is
- now ``list(colormaps)``.
- """
- return list(self)
- def register(self, cmap, *, name=None, force=False):
- """
- Register a new colormap.
- The colormap name can then be used as a string argument to any ``cmap``
- parameter in Matplotlib. It is also available in ``pyplot.get_cmap``.
- The colormap registry stores a copy of the given colormap, so that
- future changes to the original colormap instance do not affect the
- registered colormap. Think of this as the registry taking a snapshot
- of the colormap at registration.
- Parameters
- ----------
- cmap : matplotlib.colors.Colormap
- The colormap to register.
- name : str, optional
- The name for the colormap. If not given, ``cmap.name`` is used.
- force : bool, default: False
- If False, a ValueError is raised if trying to overwrite an already
- registered name. True supports overwriting registered colormaps
- other than the builtin colormaps.
- """
- _api.check_isinstance(colors.Colormap, cmap=cmap)
- name = name or cmap.name
- if name in self:
- if not force:
- # don't allow registering an already existing cmap
- # unless explicitly asked to
- raise ValueError(
- f'A colormap named "{name}" is already registered.')
- elif (name in self._builtin_cmaps
- and not self._allow_override_builtin):
- # We don't allow overriding a builtin unless privately
- # coming from register_cmap()
- raise ValueError("Re-registering the builtin cmap "
- f"{name!r} is not allowed.")
- # Warn that we are updating an already existing colormap
- _api.warn_external(f"Overwriting the cmap {name!r} "
- "that was already in the registry.")
- self._cmaps[name] = cmap.copy()
- # Someone may set the extremes of a builtin colormap and want to register it
- # with a different name for future lookups. The object would still have the
- # builtin name, so we should update it to the registered name
- if self._cmaps[name].name != name:
- self._cmaps[name].name = name
- def unregister(self, name):
- """
- Remove a colormap from the registry.
- You cannot remove built-in colormaps.
- If the named colormap is not registered, returns with no error, raises
- if you try to de-register a default colormap.
- .. warning::
- Colormap names are currently a shared namespace that may be used
- by multiple packages. Use `unregister` only if you know you
- have registered that name before. In particular, do not
- unregister just in case to clean the name before registering a
- new colormap.
- Parameters
- ----------
- name : str
- The name of the colormap to be removed.
- Raises
- ------
- ValueError
- If you try to remove a default built-in colormap.
- """
- if name in self._builtin_cmaps:
- raise ValueError(f"cannot unregister {name!r} which is a builtin "
- "colormap.")
- self._cmaps.pop(name, None)
- def get_cmap(self, cmap):
- """
- Return a color map specified through *cmap*.
- Parameters
- ----------
- cmap : str or `~matplotlib.colors.Colormap` or None
- - if a `.Colormap`, return it
- - if a string, look it up in ``mpl.colormaps``
- - if None, return the Colormap defined in :rc:`image.cmap`
- Returns
- -------
- Colormap
- """
- # get the default color map
- if cmap is None:
- return self[mpl.rcParams["image.cmap"]]
- # if the user passed in a Colormap, simply return it
- if isinstance(cmap, colors.Colormap):
- return cmap
- if isinstance(cmap, str):
- _api.check_in_list(sorted(_colormaps), cmap=cmap)
- # otherwise, it must be a string so look it up
- return self[cmap]
- raise TypeError(
- 'get_cmap expects None or an instance of a str or Colormap . ' +
- f'you passed {cmap!r} of type {type(cmap)}'
- )
- # public access to the colormaps should be via `matplotlib.colormaps`. For now,
- # we still create the registry here, but that should stay an implementation
- # detail.
- _colormaps = ColormapRegistry(_gen_cmap_registry())
- globals().update(_colormaps)
- @_api.deprecated("3.7", alternative="``matplotlib.colormaps.register(name)``")
- def register_cmap(name=None, cmap=None, *, override_builtin=False):
- """
- Add a colormap to the set recognized by :func:`get_cmap`.
- Register a new colormap to be accessed by name ::
- LinearSegmentedColormap('swirly', data, lut)
- register_cmap(cmap=swirly_cmap)
- Parameters
- ----------
- name : str, optional
- The name that can be used in :func:`get_cmap` or :rc:`image.cmap`
- If absent, the name will be the :attr:`~matplotlib.colors.Colormap.name`
- attribute of the *cmap*.
- cmap : matplotlib.colors.Colormap
- Despite being the second argument and having a default value, this
- is a required argument.
- override_builtin : bool
- Allow built-in colormaps to be overridden by a user-supplied
- colormap.
- Please do not use this unless you are sure you need it.
- """
- _api.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
- # override_builtin is allowed here for backward compatibility
- # this is just a shim to enable that to work privately in
- # the global ColormapRegistry
- _colormaps._allow_override_builtin = override_builtin
- _colormaps.register(cmap, name=name, force=override_builtin)
- _colormaps._allow_override_builtin = False
- def _get_cmap(name=None, lut=None):
- """
- Get a colormap instance, defaulting to rc values if *name* is None.
- 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.
- Returns
- -------
- Colormap
- """
- if name is None:
- name = mpl.rcParams['image.cmap']
- if isinstance(name, colors.Colormap):
- return name
- _api.check_in_list(sorted(_colormaps), name=name)
- if lut is None:
- return _colormaps[name]
- else:
- return _colormaps[name].resampled(lut)
- # do it in two steps like this so we can have an un-deprecated version in
- # pyplot.
- get_cmap = _api.deprecated(
- '3.7',
- name='get_cmap',
- alternative=(
- "``matplotlib.colormaps[name]`` " +
- "or ``matplotlib.colormaps.get_cmap(obj)``"
- )
- )(_get_cmap)
- @_api.deprecated("3.7",
- alternative="``matplotlib.colormaps.unregister(name)``")
- def unregister_cmap(name):
- """
- Remove a colormap recognized by :func:`get_cmap`.
- You may not remove built-in colormaps.
- If the named colormap is not registered, returns with no error, raises
- if you try to de-register a default colormap.
- .. warning::
- Colormap names are currently a shared namespace that may be used
- by multiple packages. Use `unregister_cmap` only if you know you
- have registered that name before. In particular, do not
- unregister just in case to clean the name before registering a
- new colormap.
- Parameters
- ----------
- name : str
- The name of the colormap to be un-registered
- Returns
- -------
- ColorMap or None
- If the colormap was registered, return it if not return `None`
- Raises
- ------
- ValueError
- If you try to de-register a default built-in colormap.
- """
- cmap = _colormaps.get(name, None)
- _colormaps.unregister(name)
- return cmap
- def _auto_norm_from_scale(scale_cls):
- """
- Automatically generate a norm class from *scale_cls*.
- This differs from `.colors.make_norm_from_scale` in the following points:
- - This function is not a class decorator, but directly returns a norm class
- (as if decorating `.Normalize`).
- - The scale is automatically constructed with ``nonpositive="mask"``, if it
- supports such a parameter, to work around the difference in defaults
- between standard scales (which use "clip") and norms (which use "mask").
- Note that ``make_norm_from_scale`` caches the generated norm classes
- (not the instances) and reuses them for later calls. For example,
- ``type(_auto_norm_from_scale("log")) == LogNorm``.
- """
- # Actually try to construct an instance, to verify whether
- # ``nonpositive="mask"`` is supported.
- try:
- norm = colors.make_norm_from_scale(
- functools.partial(scale_cls, nonpositive="mask"))(
- colors.Normalize)()
- except TypeError:
- norm = colors.make_norm_from_scale(scale_cls)(
- colors.Normalize)()
- return type(norm)
- 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 : `.Normalize` (or subclass thereof) or str or None
- The normalizing object which scales data, typically into the
- interval ``[0, 1]``.
- If a `str`, a `.Normalize` subclass is dynamically generated based
- on the scale with the corresponding name.
- 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.callbacks = cbook.CallbackRegistry(signals=["changed"])
- 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 isinstance(norm, colors.Normalize):
- raise ValueError(
- "Passing a Normalize instance simultaneously with "
- "vmin/vmax is not supported. 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 1D or 2D sequence of scalars, and
- the corresponding `~numpy.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 `~numpy.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 `~numpy.uint8`, or it can be floats with
- values in the 0-1 range; otherwise a ValueError will be raised.
- If it is a masked array, any masked elements will be set to 0 alpha.
- 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 preexisting 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 `~numpy.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)
- # Account for any masked entries in the original array
- # If any of R, G, B, or A are masked for an entry, we set alpha to 0
- if np.ma.is_masked(x):
- xx[np.any(np.ma.getmaskarray(x), axis=2), 3] = 0
- 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 value array from array-like *A*.
- Parameters
- ----------
- A : array-like or None
- The values that are mapped to colors.
- The base class `.ScalarMappable` does not make any assumptions on
- the dimensionality and shape of the value array *A*.
- """
- if A is None:
- self._A = None
- return
- A = cbook.safe_masked_invalid(A, copy=True)
- if not np.can_cast(A.dtype, float, "same_kind"):
- raise TypeError(f"Image data of dtype {A.dtype} cannot be "
- "converted to float")
- self._A = A
- def get_array(self):
- """
- Return the array of values, that are mapped to colors.
- The base class `.ScalarMappable` does not make any assumptions on
- the dimensionality and shape of the 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 the norm's limits are updated self.changed() will be called
- # through the callbacks attached to the norm
- 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)
- 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
- self.cmap = _ensure_cmap(cmap)
- if not in_init:
- self.changed() # Things are not set up properly yet.
- @property
- def norm(self):
- return self._norm
- @norm.setter
- def norm(self, norm):
- _api.check_isinstance((colors.Normalize, str, None), norm=norm)
- if norm is None:
- norm = colors.Normalize()
- elif isinstance(norm, str):
- try:
- scale_cls = scale._scale_mapping[norm]
- except KeyError:
- raise ValueError(
- "Invalid norm str name; the following values are "
- f"supported: {', '.join(scale._scale_mapping)}"
- ) from None
- norm = _auto_norm_from_scale(scale_cls)()
- if norm is self.norm:
- # We aren't updating anything
- return
- in_init = self.norm is None
- # Remove the current callback and connect to the new one
- if not in_init:
- self.norm.callbacks.disconnect(self._id_norm)
- self._norm = norm
- self._id_norm = self.norm.callbacks.connect('changed',
- self.changed)
- if not in_init:
- self.changed()
- def set_norm(self, norm):
- """
- Set the normalization instance.
- Parameters
- ----------
- norm : `.Normalize` or str 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.
- """
- self.norm = norm
- 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')
- # If the norm's limits are updated self.changed() will be called
- # through the callbacks attached to the norm
- self.norm.autoscale(self._A)
- 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')
- # If the norm's limits are updated self.changed() will be called
- # through the callbacks attached to the norm
- self.norm.autoscale_None(self._A)
- def changed(self):
- """
- Call this whenever the mappable is changed to notify all the
- callbackSM listeners to the 'changed' signal.
- """
- self.callbacks.process('changed', self)
- self.stale = True
- # The docstrings here must be generic enough to apply to all relevant methods.
- mpl._docstring.interpd.update(
- cmap_doc="""\
- cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
- The Colormap instance or registered colormap name used to map scalar data
- to colors.""",
- norm_doc="""\
- norm : str or `~matplotlib.colors.Normalize`, optional
- The normalization method used to scale scalar data to the [0, 1] range
- before mapping to colors using *cmap*. By default, a linear scaling is
- used, mapping the lowest value to 0 and the highest to 1.
- If given, this can be one of the following:
- - An instance of `.Normalize` or one of its subclasses
- (see :ref:`colormapnorms`).
- - A scale name, i.e. one of "linear", "log", "symlog", "logit", etc. For a
- list of available scales, call `matplotlib.scale.get_scale_names()`.
- In that case, a suitable `.Normalize` subclass is dynamically generated
- and instantiated.""",
- vmin_vmax_doc="""\
- vmin, vmax : float, optional
- When using scalar data and no explicit *norm*, *vmin* and *vmax* define
- the data range that the colormap covers. By default, the colormap covers
- the complete value range of the supplied data. It is an error to use
- *vmin*/*vmax* when a *norm* instance is given (but using a `str` *norm*
- name together with *vmin*/*vmax* is acceptable).""",
- )
- def _ensure_cmap(cmap):
- """
- Ensure that we have a `.Colormap` object.
- For internal use to preserve type stability of errors.
- Parameters
- ----------
- cmap : None, str, Colormap
- - if a `Colormap`, return it
- - if a string, look it up in mpl.colormaps
- - if None, look up the default color map in mpl.colormaps
- Returns
- -------
- Colormap
- """
- if isinstance(cmap, colors.Colormap):
- return cmap
- cmap_name = cmap if cmap is not None else mpl.rcParams["image.cmap"]
- # use check_in_list to ensure type stability of the exception raised by
- # the internal usage of this (ValueError vs KeyError)
- if cmap_name not in _colormaps:
- _api.check_in_list(sorted(_colormaps), cmap=cmap_name)
- return mpl.colormaps[cmap_name]
|