Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 50ef5d9

Browse filesBrowse files
authored
Merge pull request #16991 from greglucas/get_cmap_warn
Begin warning on modifying global state of colormaps
2 parents b94812c + 692b83c commit 50ef5d9
Copy full SHA for 50ef5d9

File tree

Expand file treeCollapse file tree

7 files changed

+127
-16
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+127
-16
lines changed

‎examples/images_contours_and_fields/demo_bboximage.py

Copy file name to clipboardExpand all lines: examples/images_contours_and_fields/demo_bboximage.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
a = np.vstack((a, a))
3939

4040
# List of all colormaps; skip reversed colormaps.
41-
maps = sorted(m for m in plt.cm.cmap_d if not m.endswith("_r"))
41+
maps = sorted(m for m in plt.colormaps() if not m.endswith("_r"))
4242

4343
ncol = 2
4444
nrow = len(maps)//ncol + 1

‎lib/matplotlib/backends/qt_editor/figureoptions.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/qt_editor/figureoptions.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,11 @@ def prepare_data(d, init):
140140
mappabledict[label] = mappable
141141
mappablelabels = sorted(mappabledict, key=cmp_key)
142142
mappables = []
143-
cmaps = [(cmap, name) for name, cmap in sorted(cm.cmap_d.items())]
143+
cmaps = [(cmap, name) for name, cmap in sorted(cm._cmap_registry.items())]
144144
for label in mappablelabels:
145145
mappable = mappabledict[label]
146146
cmap = mappable.get_cmap()
147-
if cmap not in cm.cmap_d.values():
147+
if cmap not in cm._cmap_registry.values():
148148
cmaps = [(cmap, cmap.name), *cmaps]
149149
low, high = mappable.get_clim()
150150
mappabledata = [

‎lib/matplotlib/cm.py

Copy file name to clipboardExpand all lines: lib/matplotlib/cm.py
+70-10Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
normalization.
1616
"""
1717

18+
from collections.abc import MutableMapping
1819
import functools
1920

2021
import numpy as np
@@ -49,7 +50,7 @@ def revcmap(data):
4950
LUTSIZE = mpl.rcParams['image.lut']
5051

5152

52-
def _gen_cmap_d():
53+
def _gen_cmap_registry():
5354
"""
5455
Generate a dict mapping standard colormap names to standard colormaps, as
5556
well as the reversed colormaps.
@@ -65,12 +66,56 @@ def _gen_cmap_d():
6566
# Generate reversed cmaps.
6667
for cmap in list(cmap_d.values()):
6768
rmap = cmap.reversed()
69+
cmap._global = True
70+
rmap._global = True
6871
cmap_d[rmap.name] = rmap
6972
return cmap_d
7073

7174

72-
cmap_d = _gen_cmap_d()
73-
locals().update(cmap_d)
75+
class _DeprecatedCmapDictWrapper(MutableMapping):
76+
"""Dictionary mapping for deprecated _cmap_d access."""
77+
78+
def __init__(self, cmap_registry):
79+
self._cmap_registry = cmap_registry
80+
81+
def __delitem__(self, key):
82+
self._warn_deprecated()
83+
self._cmap_registry.__delitem__(key)
84+
85+
def __getitem__(self, key):
86+
self._warn_deprecated()
87+
return self._cmap_registry.__getitem__(key)
88+
89+
def __iter__(self):
90+
self._warn_deprecated()
91+
return self._cmap_registry.__iter__()
92+
93+
def __len__(self):
94+
self._warn_deprecated()
95+
return self._cmap_registry.__len__()
96+
97+
def __setitem__(self, key, val):
98+
self._warn_deprecated()
99+
self._cmap_registry.__setitem__(key, val)
100+
101+
def get(self, key, default=None):
102+
self._warn_deprecated()
103+
return self._cmap_registry.get(key, default)
104+
105+
def _warn_deprecated(self):
106+
cbook.warn_deprecated(
107+
"3.3",
108+
message="The global colormaps dictionary is no longer "
109+
"considered public API.",
110+
alternative="Please use register_cmap() and get_cmap() to "
111+
"access the contents of the dictionary."
112+
)
113+
114+
115+
_cmap_registry = _gen_cmap_registry()
116+
locals().update(_cmap_registry)
117+
# This is no longer considered public API
118+
cmap_d = _DeprecatedCmapDictWrapper(_cmap_registry)
74119

75120

76121
# Continue with definitions ...
@@ -95,6 +140,13 @@ def register_cmap(name=None, cmap=None, data=None, lut=None):
95140
and the resulting colormap is registered. Instead of this implicit
96141
colormap creation, create a `.LinearSegmentedColormap` and use the first
97142
case: ``register_cmap(cmap=LinearSegmentedColormap(name, data, lut))``.
143+
144+
Notes
145+
-----
146+
Registering a colormap stores a reference to the colormap object
147+
which can currently be modified and inadvertantly change the global
148+
colormap state. This behavior is deprecated and in Matplotlib 3.5
149+
the registered colormap will be immutable.
98150
"""
99151
cbook._check_isinstance((str, None), name=name)
100152
if name is None:
@@ -104,7 +156,8 @@ def register_cmap(name=None, cmap=None, data=None, lut=None):
104156
raise ValueError("Arguments must include a name or a "
105157
"Colormap") from err
106158
if isinstance(cmap, colors.Colormap):
107-
cmap_d[name] = cmap
159+
cmap._global = True
160+
_cmap_registry[name] = cmap
108161
return
109162
if lut is not None or data is not None:
110163
cbook.warn_deprecated(
@@ -117,7 +170,8 @@ def register_cmap(name=None, cmap=None, data=None, lut=None):
117170
if lut is None:
118171
lut = mpl.rcParams['image.lut']
119172
cmap = colors.LinearSegmentedColormap(name, data, lut)
120-
cmap_d[name] = cmap
173+
cmap._global = True
174+
_cmap_registry[name] = cmap
121175

122176

123177
def get_cmap(name=None, lut=None):
@@ -127,11 +181,17 @@ def get_cmap(name=None, lut=None):
127181
Colormaps added with :func:`register_cmap` take precedence over
128182
built-in colormaps.
129183
184+
Notes
185+
-----
186+
Currently, this returns the global colormap object, which is deprecated.
187+
In Matplotlib 3.5, you will no longer be able to modify the global
188+
colormaps in-place.
189+
130190
Parameters
131191
----------
132192
name : `matplotlib.colors.Colormap` or str or None, default: None
133-
If a `.Colormap` instance, it will be returned. Otherwise, the name of
134-
a colormap known to Matplotlib, which will be resampled by *lut*. The
193+
If a `.Colormap` instance, it will be returned. Otherwise, the name of
194+
a colormap known to Matplotlib, which will be resampled by *lut*. The
135195
default, None, means :rc:`image.cmap`.
136196
lut : int or None, default: None
137197
If *name* is not already a Colormap instance and *lut* is not None, the
@@ -141,11 +201,11 @@ def get_cmap(name=None, lut=None):
141201
name = mpl.rcParams['image.cmap']
142202
if isinstance(name, colors.Colormap):
143203
return name
144-
cbook._check_in_list(sorted(cmap_d), name=name)
204+
cbook._check_in_list(sorted(_cmap_registry), name=name)
145205
if lut is None:
146-
return cmap_d[name]
206+
return _cmap_registry[name]
147207
else:
148-
return cmap_d[name]._resample(lut)
208+
return _cmap_registry[name]._resample(lut)
149209

150210

151211
class ScalarMappable:

‎lib/matplotlib/colors.py

Copy file name to clipboardExpand all lines: lib/matplotlib/colors.py
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,18 @@ def makeMappingArray(N, data, gamma=1.0):
484484
return _create_lookup_table(N, data, gamma)
485485

486486

487+
def _warn_if_global_cmap_modified(cmap):
488+
if getattr(cmap, '_global', False):
489+
cbook.warn_deprecated(
490+
"3.3",
491+
message="You are modifying the state of a globally registered "
492+
"colormap. In future versions, you will not be able to "
493+
"modify a registered colormap in-place. To remove this "
494+
"warning, you can make a copy of the colormap first. "
495+
f"cmap = mpl.cm.get_cmap({cmap.name}).copy()"
496+
)
497+
498+
487499
class Colormap:
488500
"""
489501
Baseclass for all scalar to RGBA mappings.
@@ -599,10 +611,12 @@ def __copy__(self):
599611
cmapobject.__dict__.update(self.__dict__)
600612
if self._isinit:
601613
cmapobject._lut = np.copy(self._lut)
614+
cmapobject._global = False
602615
return cmapobject
603616

604617
def set_bad(self, color='k', alpha=None):
605618
"""Set the color for masked values."""
619+
_warn_if_global_cmap_modified(self)
606620
self._rgba_bad = to_rgba(color, alpha)
607621
if self._isinit:
608622
self._set_extremes()
@@ -611,6 +625,7 @@ def set_under(self, color='k', alpha=None):
611625
"""
612626
Set the color for low out-of-range values when ``norm.clip = False``.
613627
"""
628+
_warn_if_global_cmap_modified(self)
614629
self._rgba_under = to_rgba(color, alpha)
615630
if self._isinit:
616631
self._set_extremes()
@@ -619,6 +634,7 @@ def set_over(self, color='k', alpha=None):
619634
"""
620635
Set the color for high out-of-range values when ``norm.clip = False``.
621636
"""
637+
_warn_if_global_cmap_modified(self)
622638
self._rgba_over = to_rgba(color, alpha)
623639
if self._isinit:
624640
self._set_extremes()

‎lib/matplotlib/pyplot.py

Copy file name to clipboardExpand all lines: lib/matplotlib/pyplot.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1948,7 +1948,7 @@ def colormaps():
19481948
<https://www.mathworks.com/matlabcentral/fileexchange/2662-cmrmap-m>`_
19491949
by Carey Rappaport
19501950
"""
1951-
return sorted(cm.cmap_d)
1951+
return sorted(cm._cmap_registry)
19521952

19531953

19541954
def _setup_pyplot_info_docstrings():

‎lib/matplotlib/tests/test_colors.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_colors.py
+36-1Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,41 @@ def test_register_cmap():
7070
cm.register_cmap()
7171

7272

73+
def test_colormap_global_set_warn():
74+
new_cm = plt.get_cmap('viridis')
75+
# Store the old value so we don't override the state later on.
76+
orig_cmap = copy.copy(new_cm)
77+
with pytest.warns(cbook.MatplotlibDeprecationWarning,
78+
match="You are modifying the state of a globally"):
79+
# This should warn now because we've modified the global state
80+
new_cm.set_under('k')
81+
82+
# This shouldn't warn because it is a copy
83+
copy.copy(new_cm).set_under('b')
84+
85+
# Test that registering and then modifying warns
86+
plt.register_cmap(name='test_cm', cmap=copy.copy(orig_cmap))
87+
new_cm = plt.get_cmap('test_cm')
88+
with pytest.warns(cbook.MatplotlibDeprecationWarning,
89+
match="You are modifying the state of a globally"):
90+
# This should warn now because we've modified the global state
91+
new_cm.set_under('k')
92+
93+
# Re-register the original
94+
plt.register_cmap(cmap=orig_cmap)
95+
96+
97+
def test_colormap_dict_deprecate():
98+
# Make sure we warn on get and set access into cmap_d
99+
with pytest.warns(cbook.MatplotlibDeprecationWarning,
100+
match="The global colormaps dictionary is no longer"):
101+
cm = plt.cm.cmap_d['viridis']
102+
103+
with pytest.warns(cbook.MatplotlibDeprecationWarning,
104+
match="The global colormaps dictionary is no longer"):
105+
plt.cm.cmap_d['test'] = cm
106+
107+
73108
def test_colormap_copy():
74109
cm = plt.cm.Reds
75110
cm_copy = copy.copy(cm)
@@ -818,7 +853,7 @@ def test_pandas_iterable(pd):
818853
assert_array_equal(cm1.colors, cm2.colors)
819854

820855

821-
@pytest.mark.parametrize('name', sorted(cm.cmap_d))
856+
@pytest.mark.parametrize('name', sorted(plt.colormaps()))
822857
def test_colormap_reversing(name):
823858
"""
824859
Check the generated _lut data of a colormap and corresponding reversed

‎lib/matplotlib/tests/test_pickle.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_pickle.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def test_shared():
190190
assert fig.axes[1].get_xlim() == (10, 20)
191191

192192

193-
@pytest.mark.parametrize("cmap", cm.cmap_d.values())
193+
@pytest.mark.parametrize("cmap", cm._cmap_registry.values())
194194
def test_cmap(cmap):
195195
pickle.dumps(cmap)
196196

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.