Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1a9dacf

Browse files
tacaswelldstansby
andcommitted
ENH/API: be more defensive in cm.register_cmap + deregister
- raise if you try to over write a default colormap - warn if you try to over write a user-added colormap - add method to de-register a color map - add escape hatch to force re-registering builtin colormaps Co-Authored-By: David Stansby <[email protected]>
1 parent d28cbf2 commit 1a9dacf

File tree

4 files changed

+106
-7
lines changed

4 files changed

+106
-7
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Raise or warn on registering a colormap twice
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
When using `matplotlib.cm.register_cmap` to register a user provided
5+
or third-party colormap it will now raise a `ValueError` if trying to
6+
over-write one of the built in colormaps and warn if trying to over
7+
write a user registered colormap. This may raise for user-registered
8+
colormaps in the future.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
Add ``cm.unregister_cmap`` function
3+
-----------------------------------
4+
5+
`.cm.unregister_cmap` allows users to remove a colormap that they
6+
have previously registered.

lib/matplotlib/cm.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from matplotlib import _api, colors, cbook
2525
from matplotlib._cm import datad
2626
from matplotlib._cm_listed import cmaps as cmaps_listed
27+
from matplotlib.cbook import _warn_external
2728

2829

2930
LUTSIZE = mpl.rcParams['image.lut']
@@ -95,12 +96,12 @@ def _warn_deprecated(self):
9596
locals().update(_cmap_registry)
9697
# This is no longer considered public API
9798
cmap_d = _DeprecatedCmapDictWrapper(_cmap_registry)
98-
99+
__builtin_cmaps = tuple(_cmap_registry)
99100

100101
# Continue with definitions ...
101102

102103

103-
def register_cmap(name=None, cmap=None):
104+
def register_cmap(name=None, cmap=None, *, override_builtin=False):
104105
"""
105106
Add a colormap to the set recognized by :func:`get_cmap`.
106107
@@ -121,13 +122,20 @@ def register_cmap(name=None, cmap=None):
121122
Despite being the second argument and having a default value, this
122123
is a required argument.
123124
125+
override_builtin : bool
126+
127+
Allow built-in colormaps to be overridden by a user-supplied
128+
colormap.
129+
130+
Please do not use this unless you are sure you need it.
124131
125132
Notes
126133
-----
127134
Registering a colormap stores a reference to the colormap object
128135
which can currently be modified and inadvertantly change the global
129136
colormap state. This behavior is deprecated and in Matplotlib 3.5
130137
the registered colormap will be immutable.
138+
131139
"""
132140
cbook._check_isinstance((str, None), name=name)
133141
if name is None:
@@ -136,10 +144,18 @@ def register_cmap(name=None, cmap=None):
136144
except AttributeError as err:
137145
raise ValueError("Arguments must include a name or a "
138146
"Colormap") from err
147+
if name in _cmap_registry:
148+
if not override_builtin and name in __builtin_cmaps:
149+
msg = f"Trying to re-register the builtin cmap {name!r}."
150+
raise ValueError(msg)
151+
else:
152+
msg = f"Trying to register the cmap {name!r} which already exists."
153+
_warn_external(msg)
139154

140155
if not isinstance(cmap, colors.Colormap):
141156
raise ValueError("You must pass a Colormap instance. "
142157
f"You passed {cmap} a {type(cmap)} object.")
158+
143159
cmap._global = True
144160
_cmap_registry[name] = cmap
145161
return
@@ -179,6 +195,47 @@ def get_cmap(name=None, lut=None):
179195
return _cmap_registry[name]._resample(lut)
180196

181197

198+
def unregister_cmap(name):
199+
"""
200+
Remove a colormap recognized by :func:`get_cmap`.
201+
202+
You may not remove built-in colormaps.
203+
204+
If the named colormap is not registered, returns with no error, raises
205+
if you try to de-register a default colormap.
206+
207+
.. warning ::
208+
209+
Colormap names are currently a shared namespace that may be used
210+
by multiple packages. Use `unregister_cmap` only if you know you
211+
have registered that name before. In particular, do not
212+
unregister just in case to clean the name before registering a
213+
new colormap.
214+
215+
Parameters
216+
----------
217+
name : str
218+
The name of the colormap to be un-registered
219+
220+
Returns
221+
-------
222+
ColorMap or None
223+
If the colormap was registered, return it if not return `None`
224+
225+
Raises
226+
------
227+
ValueError
228+
If you try to de-register a default built-in colormap.
229+
230+
"""
231+
if name not in _cmap_registry:
232+
return
233+
if name in __builtin_cmaps:
234+
raise ValueError(f"cannot unregister {name!r} which is a builtin "
235+
"colormap.")
236+
return _cmap_registry.pop(name)
237+
238+
182239
class ScalarMappable:
183240
"""
184241
A mixin class to map scalar data to RGBA.

lib/matplotlib/tests/test_colors.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,45 @@ def test_resample():
6464

6565

6666
def test_register_cmap():
67-
new_cm = copy.copy(plt.cm.viridis)
68-
cm.register_cmap('viridis2', new_cm)
69-
assert plt.get_cmap('viridis2') == new_cm
67+
new_cm = copy.copy(cm.get_cmap("viridis"))
68+
target = "viridis2"
69+
cm.register_cmap(target, new_cm)
70+
assert plt.get_cmap(target) == new_cm
7071

7172
with pytest.raises(ValueError,
72-
match='Arguments must include a name or a Colormap'):
73+
match="Arguments must include a name or a Colormap"):
7374
cm.register_cmap()
7475

76+
with pytest.warns(UserWarning):
77+
cm.register_cmap(target, new_cm)
78+
79+
cm.unregister_cmap(target)
80+
with pytest.raises(ValueError,
81+
match=f'{target!r} is not a valid value for name;'):
82+
cm.get_cmap(target)
83+
# test that second time is error free
84+
cm.unregister_cmap(target)
85+
7586
with pytest.raises(ValueError, match="You must pass a Colormap instance."):
7687
cm.register_cmap('nome', cmap='not a cmap')
7788

7889

90+
def test_double_register_builtin_cmap():
91+
name = "viridis"
92+
match = f"Trying to re-register the builtin cmap {name!r}."
93+
with pytest.raises(ValueError, match=match):
94+
cm.register_cmap(name, cm.get_cmap(name))
95+
with pytest.warns(UserWarning):
96+
cm.register_cmap(name, cm.get_cmap(name), override_builtin=True)
97+
98+
99+
def test_unregister_builtin_cmap():
100+
name = "viridis"
101+
match = f'cannot unregister {name!r} which is a builtin colormap.'
102+
with pytest.raises(ValueError, match=match):
103+
cm.unregister_cmap(name)
104+
105+
79106
def test_colormap_global_set_warn():
80107
new_cm = plt.get_cmap('viridis')
81108
# Store the old value so we don't override the state later on.
@@ -97,7 +124,8 @@ def test_colormap_global_set_warn():
97124
new_cm.set_under('k')
98125

99126
# Re-register the original
100-
plt.register_cmap(cmap=orig_cmap)
127+
with pytest.warns(UserWarning):
128+
plt.register_cmap(cmap=orig_cmap, override_builtin=True)
101129

102130

103131
def test_colormap_dict_deprecate():

0 commit comments

Comments
 (0)