Skip to content

Api deprecate cmap functions #23668

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Aug 20, 2022
22 changes: 22 additions & 0 deletions doc/api/next_api_changes/deprecations/23668-TC.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Pending deprecation top-level cmap registration and access functions in ``mpl.cm``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

As part of a `multi-step process
<https://github.com/matplotlib/matplotlib/issues/20853>`_ we are refactoring
the global state for managing the registered colormaps.

In Matplotlib 3.5 we added a `.ColormapRegistry` class and exposed an
instance at the top level as ``matplotlib.colormaps``. The existing
top level functions in `matplotlib.cm` (``get_cmap``, ``register_cmap``,
``unregister_cmap``) were changed to be aliases around the same instance.

In Matplotlib 3.6 we have marked those top level functions as pending
deprecation with the intention of deprecation in Matplotlib 3.7. The
following functions have been marked for pending deprecation:

- `matplotlib.cm.get_cmap` - use ``matplotlib.colormaps[name]`` instead
- `matplotlib.cm.register_cmap` - use ``matplotlib.colormaps.register`` instead
- `matplotlib.cm.unregister_cmap` - use ``matplotlib.colormaps.unregister`` instead
- ``matplotlib.pyplot.register_cmap`` - use ``matplotlib.colormaps.register`` instead

The `matplotlib.pyplot.get_cmap` function will stay available for backward compatibility.
1 change: 1 addition & 0 deletions doc/api/pyplot_summary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Plotting commands
gcf
gci
get
get_cmap
get_figlabels
get_fignums
getp
Expand Down
13 changes: 13 additions & 0 deletions doc/users/next_whats_new/resample_colormaps.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Colormap method for creating a different lookup table size
----------------------------------------------------------
The new method `.Colormap.resampled` creates a new `.Colormap` instance
with the specified lookup table size. This is a replacement for manipulating
the lookup table size via ``get_cmap``.

Use::

get_cmap(name).resampled(N)

instead of::

get_cmap(name, lut=N)
2 changes: 1 addition & 1 deletion examples/images_contours_and_fields/contour_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
axs = _axs.flatten()

cset1 = axs[0].contourf(X, Y, Z, levels, norm=norm,
cmap=cm.get_cmap(cmap, len(levels) - 1))
cmap=cmap.resampled(len(levels) - 1))
# It is not necessary, but for the colormap, we need only the
# number of levels minus 1. To avoid discretization error, use
# either this number or a large number such as the default (256).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

import numpy as np
import matplotlib
import matplotlib as mpl
import matplotlib.pyplot as plt
# sphinx_gallery_thumbnail_number = 2

Expand Down Expand Up @@ -272,7 +273,7 @@ def annotate_heatmap(im, data=None, valfmt="{x:.2f}",
fmt = matplotlib.ticker.FuncFormatter(lambda x, pos: qrates[::-1][norm(x)])

im, _ = heatmap(data, y, x, ax=ax3,
cmap=plt.get_cmap("PiYG", 7), norm=norm,
cmap=mpl.colormaps["PiYG"].resampled(7), norm=norm,
cbar_kw=dict(ticks=np.arange(-3, 4), format=fmt),
cbarlabel="Quality Rating")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
positions = np.random.normal(size=(N, 2)) * 5
data = zip(skills, takeoff_angles, thrusts, successful, positions)

cmap = plt.cm.get_cmap("plasma")
cmap = plt.colormaps["plasma"]
fig, ax = plt.subplots()
fig.suptitle("Throwing success", size=14)
for skill, takeoff, thrust, mood, pos in data:
Expand Down
54 changes: 47 additions & 7 deletions lib/matplotlib/cm.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ def unregister(self, name):
globals().update(_colormaps)


@_api.deprecated(
'3.6',
pending=True,
alternative="``matplotlib.colormaps.register_cmap(name)``"
)
def register_cmap(name=None, cmap=None, *, override_builtin=False):
"""
Add a colormap to the set recognized by :func:`get_cmap`.
Expand Down Expand Up @@ -244,13 +249,10 @@ def register_cmap(name=None, cmap=None, *, override_builtin=False):
_colormaps._allow_override_builtin = False


def get_cmap(name=None, lut=None):
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.

Parameters
----------
name : `matplotlib.colors.Colormap` or str or None, default: None
Expand All @@ -260,6 +262,10 @@ def get_cmap(name=None, lut=None):
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']
Expand All @@ -269,9 +275,20 @@ def get_cmap(name=None, lut=None):
if lut is None:
return _colormaps[name]
else:
return _colormaps[name]._resample(lut)
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.6', pending=True, alternative="``matplotlib.colormaps[name]``"
)(_get_cmap)


@_api.deprecated(
'3.6',
pending=True,
alternative="``matplotlib.colormaps.unregister_cmap(name)``"
)
def unregister_cmap(name):
"""
Remove a colormap recognized by :func:`get_cmap`.
Expand Down Expand Up @@ -550,8 +567,8 @@ def set_cmap(self, cmap):
cmap : `.Colormap` or str or None
"""
in_init = self.cmap is None
cmap = get_cmap(cmap)
self.cmap = cmap

self.cmap = _ensure_cmap(cmap)
if not in_init:
self.changed() # Things are not set up properly yet.

Expand Down Expand Up @@ -663,3 +680,26 @@ def changed(self):
*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.

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
return mpl.colormaps[
cmap if cmap is not None else mpl.rcParams['image.cmap']
]
15 changes: 12 additions & 3 deletions lib/matplotlib/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -854,8 +854,17 @@ def is_gray(self):
return (np.all(self._lut[:, 0] == self._lut[:, 1]) and
np.all(self._lut[:, 0] == self._lut[:, 2]))

def _resample(self, lutsize):
def resampled(self, lutsize):
"""Return a new colormap with *lutsize* entries."""
if hasattr(self, '_resample'):
_api.warn_external(
"The ability to resample a color map is now public API "
f"However the class {type(self)} still only implements "
"the previous private _resample method. Please update "
"your class."
)
return self._resample(lutsize)

raise NotImplementedError()

def reversed(self, name=None):
Expand Down Expand Up @@ -1050,7 +1059,7 @@ def from_list(name, colors, N=256, gamma=1.0):

return LinearSegmentedColormap(name, cdict, N, gamma)

def _resample(self, lutsize):
def resampled(self, lutsize):
"""Return a new colormap with *lutsize* entries."""
new_cmap = LinearSegmentedColormap(self.name, self._segmentdata,
lutsize)
Expand Down Expand Up @@ -1154,7 +1163,7 @@ def _init(self):
self._isinit = True
self._set_extremes()

def _resample(self, lutsize):
def resampled(self, lutsize):
"""Return a new colormap with *lutsize* entries."""
colors = self(np.linspace(0, 1, lutsize))
new_cmap = ListedColormap(colors, name=self.name)
Expand Down
11 changes: 9 additions & 2 deletions lib/matplotlib/pyplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
from matplotlib.scale import get_scale_names

from matplotlib import cm
from matplotlib.cm import _colormaps as colormaps, get_cmap, register_cmap
from matplotlib.cm import _colormaps as colormaps, register_cmap
from matplotlib.colors import _color_sequences as color_sequences

import numpy as np
Expand Down Expand Up @@ -2069,6 +2069,13 @@ def clim(vmin=None, vmax=None):
im.set_clim(vmin, vmax)


# eventually this implementation should move here, use indirection for now to
# avoid having two copies of the code floating around.
def get_cmap(name=None, lut=None):
return cm._get_cmap(name=name, lut=lut)
get_cmap.__doc__ = cm._get_cmap.__doc__


def set_cmap(cmap):
"""
Set the default colormap, and applies it to the current image if any.
Expand All @@ -2084,7 +2091,7 @@ def set_cmap(cmap):
matplotlib.cm.register_cmap
matplotlib.cm.get_cmap
"""
cmap = cm.get_cmap(cmap)
cmap = get_cmap(cmap)

rc('image', cmap=cmap.name)
im = gci()
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/streamplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
if use_multicolor_lines:
if norm is None:
norm = mcolors.Normalize(color.min(), color.max())
cmap = cm.get_cmap(cmap)
cmap = cm._ensure_cmap(cmap)

streamlines = []
arrows = []
Expand Down
6 changes: 3 additions & 3 deletions lib/matplotlib/tests/test_artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import pytest

from matplotlib import cm
import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
Expand All @@ -14,6 +13,7 @@
import matplotlib.transforms as mtransforms
import matplotlib.collections as mcollections
import matplotlib.artist as martist
import matplotlib as mpl
from matplotlib.testing.decorators import check_figures_equal, image_comparison


Expand Down Expand Up @@ -415,7 +415,7 @@ def test_format_cursor_data_BoundaryNorm():
# map range -1..1 to 0..256 in 0.01 steps
fig, ax = plt.subplots()
fig.suptitle("-1..1 to 0..256 in 0.01")
cmap = cm.get_cmap('RdBu_r', 200)
cmap = mpl.colormaps['RdBu_r'].resampled(200)
norm = mcolors.BoundaryNorm(np.linspace(-1, 1, 200), 200)
img = ax.imshow(X, cmap=cmap, norm=norm)

Expand All @@ -439,7 +439,7 @@ def test_format_cursor_data_BoundaryNorm():
# map range -1..1 to 0..256 in 0.01 steps
fig, ax = plt.subplots()
fig.suptitle("-1..1 to 0..256 in 0.001")
cmap = cm.get_cmap('RdBu_r', 2000)
cmap = mpl.colormaps['RdBu_r'].resampled(2000)
norm = mcolors.BoundaryNorm(np.linspace(-1, 1, 2000), 2000)
img = ax.imshow(X, cmap=cmap, norm=norm)

Expand Down
12 changes: 6 additions & 6 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1220,7 +1220,7 @@ def test_pcolormesh_alpha():
Qy = Y + np.sin(X)
Z = np.hypot(X, Y) / 5
Z = (Z - Z.min()) / Z.ptp()
vir = plt.get_cmap("viridis", 16)
vir = mpl.colormaps["viridis"].resampled(16)
# make another colormap with varying alpha
colors = vir(np.arange(16))
colors[:, 3] = 0.5 + 0.5*np.sin(np.arange(16))
Expand Down Expand Up @@ -2250,7 +2250,7 @@ def test_contour_hatching():
x, y, z = contour_dat()
fig, ax = plt.subplots()
ax.contourf(x, y, z, 7, hatches=['/', '\\', '//', '-'],
cmap=plt.get_cmap('gray'),
cmap=mpl.colormaps['gray'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just use strings if we aren't using resample or using the Colormap for anything?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can, but I was just mechanically changing (a lot of this was done with search-and-relpace)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've decided to leave this. It makes the test a bit more complicated, but I do not know if we are definitely exercising the ability to pass Colormap objects through this top level API else where.

extend='both', alpha=0.5)


Expand All @@ -2260,7 +2260,7 @@ def test_contour_colorbar():

fig, ax = plt.subplots()
cs = ax.contourf(x, y, z, levels=np.arange(-1.8, 1.801, 0.2),
cmap=plt.get_cmap('RdBu'),
cmap=mpl.colormaps['RdBu'],
vmin=-0.6,
vmax=0.6,
extend='both')
Expand Down Expand Up @@ -2444,7 +2444,7 @@ def test_scatter_edgecolor_RGB(self):
@check_figures_equal(extensions=["png"])
def test_scatter_invalid_color(self, fig_test, fig_ref):
ax = fig_test.subplots()
cmap = plt.get_cmap("viridis", 16)
cmap = mpl.colormaps["viridis"].resampled(16)
cmap.set_bad("k", 1)
# Set a nonuniform size to prevent the last call to `scatter` (plotting
# the invalid points separately in fig_ref) from using the marker
Expand All @@ -2453,15 +2453,15 @@ def test_scatter_invalid_color(self, fig_test, fig_ref):
c=[1, np.nan, 2, np.nan], s=[1, 2, 3, 4],
cmap=cmap, plotnonfinite=True)
ax = fig_ref.subplots()
cmap = plt.get_cmap("viridis", 16)
cmap = mpl.colormaps["viridis"].resampled(16)
ax.scatter([0, 2], [0, 2], c=[1, 2], s=[1, 3], cmap=cmap)
ax.scatter([1, 3], [1, 3], s=[2, 4], color="k")

@check_figures_equal(extensions=["png"])
def test_scatter_no_invalid_color(self, fig_test, fig_ref):
# With plotnonfinite=False we plot only 2 points.
ax = fig_test.subplots()
cmap = plt.get_cmap("viridis", 16)
cmap = mpl.colormaps["viridis"].resampled(16)
cmap.set_bad("k", 1)
ax.scatter(range(4), range(4),
c=[1, np.nan, 2, np.nan], s=[1, 2, 3, 4],
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/tests/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,7 @@ def test_quadmesh_set_array():
def test_quadmesh_vmin_vmax():
# test when vmin/vmax on the norm changes, the quadmesh gets updated
fig, ax = plt.subplots()
cmap = mpl.cm.get_cmap('plasma')
cmap = mpl.colormaps['plasma']
norm = mpl.colors.Normalize(vmin=0, vmax=1)
coll = ax.pcolormesh([[1]], cmap=cmap, norm=norm)
fig.canvas.draw()
Expand Down
Loading