Skip to content

Commit 9b1fcf6

Browse files
authored
Merge pull request #23668 from tacaswell/api_deprecate_cmap_functions
Api deprecate cmap functions
2 parents e17b602 + 014c1ea commit 9b1fcf6

File tree

21 files changed

+217
-96
lines changed

21 files changed

+217
-96
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Pending deprecation top-level cmap registration and access functions in ``mpl.cm``
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
As part of a `multi-step process
5+
<https://github.com/matplotlib/matplotlib/issues/20853>`_ we are refactoring
6+
the global state for managing the registered colormaps.
7+
8+
In Matplotlib 3.5 we added a `.ColormapRegistry` class and exposed an
9+
instance at the top level as ``matplotlib.colormaps``. The existing
10+
top level functions in `matplotlib.cm` (``get_cmap``, ``register_cmap``,
11+
``unregister_cmap``) were changed to be aliases around the same instance.
12+
13+
In Matplotlib 3.6 we have marked those top level functions as pending
14+
deprecation with the intention of deprecation in Matplotlib 3.7. The
15+
following functions have been marked for pending deprecation:
16+
17+
- `matplotlib.cm.get_cmap` - use ``matplotlib.colormaps[name]`` instead
18+
- `matplotlib.cm.register_cmap` - use ``matplotlib.colormaps.register`` instead
19+
- `matplotlib.cm.unregister_cmap` - use ``matplotlib.colormaps.unregister`` instead
20+
- ``matplotlib.pyplot.register_cmap`` - use ``matplotlib.colormaps.register`` instead
21+
22+
The `matplotlib.pyplot.get_cmap` function will stay available for backward compatibility.

doc/api/pyplot_summary.rst

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Plotting commands
6464
gcf
6565
gci
6666
get
67+
get_cmap
6768
get_figlabels
6869
get_fignums
6970
getp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Colormap method for creating a different lookup table size
2+
----------------------------------------------------------
3+
The new method `.Colormap.resampled` creates a new `.Colormap` instance
4+
with the specified lookup table size. This is a replacement for manipulating
5+
the lookup table size via ``get_cmap``.
6+
7+
Use::
8+
9+
get_cmap(name).resampled(N)
10+
11+
instead of::
12+
13+
get_cmap(name, lut=N)

examples/images_contours_and_fields/contour_image.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
axs = _axs.flatten()
4242

4343
cset1 = axs[0].contourf(X, Y, Z, levels, norm=norm,
44-
cmap=cm.get_cmap(cmap, len(levels) - 1))
44+
cmap=cmap.resampled(len(levels) - 1))
4545
# It is not necessary, but for the colormap, we need only the
4646
# number of levels minus 1. To avoid discretization error, use
4747
# either this number or a large number such as the default (256).

examples/images_contours_and_fields/image_annotated_heatmap.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
import numpy as np
4141
import matplotlib
42+
import matplotlib as mpl
4243
import matplotlib.pyplot as plt
4344
# sphinx_gallery_thumbnail_number = 2
4445

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

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

examples/lines_bars_and_markers/multivariate_marker_plot.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
positions = np.random.normal(size=(N, 2)) * 5
3232
data = zip(skills, takeoff_angles, thrusts, successful, positions)
3333

34-
cmap = plt.cm.get_cmap("plasma")
34+
cmap = plt.colormaps["plasma"]
3535
fig, ax = plt.subplots()
3636
fig.suptitle("Throwing success", size=14)
3737
for skill, takeoff, thrust, mood, pos in data:

lib/matplotlib/cm.py

+47-7
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,11 @@ def unregister(self, name):
201201
globals().update(_colormaps)
202202

203203

204+
@_api.deprecated(
205+
'3.6',
206+
pending=True,
207+
alternative="``matplotlib.colormaps.register_cmap(name)``"
208+
)
204209
def register_cmap(name=None, cmap=None, *, override_builtin=False):
205210
"""
206211
Add a colormap to the set recognized by :func:`get_cmap`.
@@ -244,13 +249,10 @@ def register_cmap(name=None, cmap=None, *, override_builtin=False):
244249
_colormaps._allow_override_builtin = False
245250

246251

247-
def get_cmap(name=None, lut=None):
252+
def _get_cmap(name=None, lut=None):
248253
"""
249254
Get a colormap instance, defaulting to rc values if *name* is None.
250255
251-
Colormaps added with :func:`register_cmap` take precedence over
252-
built-in colormaps.
253-
254256
Parameters
255257
----------
256258
name : `matplotlib.colors.Colormap` or str or None, default: None
@@ -260,6 +262,10 @@ def get_cmap(name=None, lut=None):
260262
lut : int or None, default: None
261263
If *name* is not already a Colormap instance and *lut* is not None, the
262264
colormap will be resampled to have *lut* entries in the lookup table.
265+
266+
Returns
267+
-------
268+
Colormap
263269
"""
264270
if name is None:
265271
name = mpl.rcParams['image.cmap']
@@ -269,9 +275,20 @@ def get_cmap(name=None, lut=None):
269275
if lut is None:
270276
return _colormaps[name]
271277
else:
272-
return _colormaps[name]._resample(lut)
278+
return _colormaps[name].resampled(lut)
273279

280+
# do it in two steps like this so we can have an un-deprecated version in
281+
# pyplot.
282+
get_cmap = _api.deprecated(
283+
'3.6', pending=True, alternative="``matplotlib.colormaps[name]``"
284+
)(_get_cmap)
274285

286+
287+
@_api.deprecated(
288+
'3.6',
289+
pending=True,
290+
alternative="``matplotlib.colormaps.unregister_cmap(name)``"
291+
)
275292
def unregister_cmap(name):
276293
"""
277294
Remove a colormap recognized by :func:`get_cmap`.
@@ -550,8 +567,8 @@ def set_cmap(self, cmap):
550567
cmap : `.Colormap` or str or None
551568
"""
552569
in_init = self.cmap is None
553-
cmap = get_cmap(cmap)
554-
self.cmap = cmap
570+
571+
self.cmap = _ensure_cmap(cmap)
555572
if not in_init:
556573
self.changed() # Things are not set up properly yet.
557574

@@ -663,3 +680,26 @@ def changed(self):
663680
*vmin*/*vmax* when a *norm* instance is given (but using a `str` *norm*
664681
name together with *vmin*/*vmax* is acceptable).""",
665682
)
683+
684+
685+
def _ensure_cmap(cmap):
686+
"""
687+
Ensure that we have a `.Colormap` object.
688+
689+
Parameters
690+
----------
691+
cmap : None, str, Colormap
692+
693+
- if a `Colormap`, return it
694+
- if a string, look it up in mpl.colormaps
695+
- if None, look up the default color map in mpl.colormaps
696+
697+
Returns
698+
-------
699+
Colormap
700+
"""
701+
if isinstance(cmap, colors.Colormap):
702+
return cmap
703+
return mpl.colormaps[
704+
cmap if cmap is not None else mpl.rcParams['image.cmap']
705+
]

lib/matplotlib/colors.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -853,8 +853,17 @@ def is_gray(self):
853853
return (np.all(self._lut[:, 0] == self._lut[:, 1]) and
854854
np.all(self._lut[:, 0] == self._lut[:, 2]))
855855

856-
def _resample(self, lutsize):
856+
def resampled(self, lutsize):
857857
"""Return a new colormap with *lutsize* entries."""
858+
if hasattr(self, '_resample'):
859+
_api.warn_external(
860+
"The ability to resample a color map is now public API "
861+
f"However the class {type(self)} still only implements "
862+
"the previous private _resample method. Please update "
863+
"your class."
864+
)
865+
return self._resample(lutsize)
866+
858867
raise NotImplementedError()
859868

860869
def reversed(self, name=None):
@@ -1049,7 +1058,7 @@ def from_list(name, colors, N=256, gamma=1.0):
10491058

10501059
return LinearSegmentedColormap(name, cdict, N, gamma)
10511060

1052-
def _resample(self, lutsize):
1061+
def resampled(self, lutsize):
10531062
"""Return a new colormap with *lutsize* entries."""
10541063
new_cmap = LinearSegmentedColormap(self.name, self._segmentdata,
10551064
lutsize)
@@ -1153,7 +1162,7 @@ def _init(self):
11531162
self._isinit = True
11541163
self._set_extremes()
11551164

1156-
def _resample(self, lutsize):
1165+
def resampled(self, lutsize):
11571166
"""Return a new colormap with *lutsize* entries."""
11581167
colors = self(np.linspace(0, 1, lutsize))
11591168
new_cmap = ListedColormap(colors, name=self.name)

lib/matplotlib/pyplot.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
from matplotlib.scale import get_scale_names
6969

7070
from matplotlib import cm
71-
from matplotlib.cm import _colormaps as colormaps, get_cmap, register_cmap
71+
from matplotlib.cm import _colormaps as colormaps, register_cmap
7272
from matplotlib.colors import _color_sequences as color_sequences
7373

7474
import numpy as np
@@ -2075,6 +2075,13 @@ def clim(vmin=None, vmax=None):
20752075
im.set_clim(vmin, vmax)
20762076

20772077

2078+
# eventually this implementation should move here, use indirection for now to
2079+
# avoid having two copies of the code floating around.
2080+
def get_cmap(name=None, lut=None):
2081+
return cm._get_cmap(name=name, lut=lut)
2082+
get_cmap.__doc__ = cm._get_cmap.__doc__
2083+
2084+
20782085
def set_cmap(cmap):
20792086
"""
20802087
Set the default colormap, and applies it to the current image if any.
@@ -2090,7 +2097,7 @@ def set_cmap(cmap):
20902097
matplotlib.cm.register_cmap
20912098
matplotlib.cm.get_cmap
20922099
"""
2093-
cmap = cm.get_cmap(cmap)
2100+
cmap = get_cmap(cmap)
20942101

20952102
rc('image', cmap=cmap.name)
20962103
im = gci()

lib/matplotlib/streamplot.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
187187
if use_multicolor_lines:
188188
if norm is None:
189189
norm = mcolors.Normalize(color.min(), color.max())
190-
cmap = cm.get_cmap(cmap)
190+
cmap = cm._ensure_cmap(cmap)
191191

192192
streamlines = []
193193
arrows = []

lib/matplotlib/tests/test_artist.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import pytest
77

8-
from matplotlib import cm
98
import matplotlib.colors as mcolors
109
import matplotlib.pyplot as plt
1110
import matplotlib.patches as mpatches
@@ -14,6 +13,7 @@
1413
import matplotlib.transforms as mtransforms
1514
import matplotlib.collections as mcollections
1615
import matplotlib.artist as martist
16+
import matplotlib as mpl
1717
from matplotlib.testing.decorators import check_figures_equal, image_comparison
1818

1919

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

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

lib/matplotlib/tests/test_axes.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -1220,7 +1220,7 @@ def test_pcolormesh_alpha():
12201220
Qy = Y + np.sin(X)
12211221
Z = np.hypot(X, Y) / 5
12221222
Z = (Z - Z.min()) / Z.ptp()
1223-
vir = plt.get_cmap("viridis", 16)
1223+
vir = mpl.colormaps["viridis"].resampled(16)
12241224
# make another colormap with varying alpha
12251225
colors = vir(np.arange(16))
12261226
colors[:, 3] = 0.5 + 0.5*np.sin(np.arange(16))
@@ -2279,7 +2279,7 @@ def test_contour_hatching():
22792279
x, y, z = contour_dat()
22802280
fig, ax = plt.subplots()
22812281
ax.contourf(x, y, z, 7, hatches=['/', '\\', '//', '-'],
2282-
cmap=plt.get_cmap('gray'),
2282+
cmap=mpl.colormaps['gray'],
22832283
extend='both', alpha=0.5)
22842284

22852285

@@ -2289,7 +2289,7 @@ def test_contour_colorbar():
22892289

22902290
fig, ax = plt.subplots()
22912291
cs = ax.contourf(x, y, z, levels=np.arange(-1.8, 1.801, 0.2),
2292-
cmap=plt.get_cmap('RdBu'),
2292+
cmap=mpl.colormaps['RdBu'],
22932293
vmin=-0.6,
22942294
vmax=0.6,
22952295
extend='both')
@@ -2473,7 +2473,7 @@ def test_scatter_edgecolor_RGB(self):
24732473
@check_figures_equal(extensions=["png"])
24742474
def test_scatter_invalid_color(self, fig_test, fig_ref):
24752475
ax = fig_test.subplots()
2476-
cmap = plt.get_cmap("viridis", 16)
2476+
cmap = mpl.colormaps["viridis"].resampled(16)
24772477
cmap.set_bad("k", 1)
24782478
# Set a nonuniform size to prevent the last call to `scatter` (plotting
24792479
# the invalid points separately in fig_ref) from using the marker
@@ -2482,15 +2482,15 @@ def test_scatter_invalid_color(self, fig_test, fig_ref):
24822482
c=[1, np.nan, 2, np.nan], s=[1, 2, 3, 4],
24832483
cmap=cmap, plotnonfinite=True)
24842484
ax = fig_ref.subplots()
2485-
cmap = plt.get_cmap("viridis", 16)
2485+
cmap = mpl.colormaps["viridis"].resampled(16)
24862486
ax.scatter([0, 2], [0, 2], c=[1, 2], s=[1, 3], cmap=cmap)
24872487
ax.scatter([1, 3], [1, 3], s=[2, 4], color="k")
24882488

24892489
@check_figures_equal(extensions=["png"])
24902490
def test_scatter_no_invalid_color(self, fig_test, fig_ref):
24912491
# With plotnonfinite=False we plot only 2 points.
24922492
ax = fig_test.subplots()
2493-
cmap = plt.get_cmap("viridis", 16)
2493+
cmap = mpl.colormaps["viridis"].resampled(16)
24942494
cmap.set_bad("k", 1)
24952495
ax.scatter(range(4), range(4),
24962496
c=[1, np.nan, 2, np.nan], s=[1, 2, 3, 4],

lib/matplotlib/tests/test_collections.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,7 @@ def test_quadmesh_set_array():
955955
def test_quadmesh_vmin_vmax():
956956
# test when vmin/vmax on the norm changes, the quadmesh gets updated
957957
fig, ax = plt.subplots()
958-
cmap = mpl.cm.get_cmap('plasma')
958+
cmap = mpl.colormaps['plasma']
959959
norm = mpl.colors.Normalize(vmin=0, vmax=1)
960960
coll = ax.pcolormesh([[1]], cmap=cmap, norm=norm)
961961
fig.canvas.draw()

0 commit comments

Comments
 (0)