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 6f635b4

Browse filesBrowse files
authored
Merge pull request #27589 from dstansby/extend-power-norm
Don't clip PowerNorm inputs < vmin
2 parents c62b3c4 + 8b6748e commit 6f635b4
Copy full SHA for 6f635b4

File tree

Expand file treeCollapse file tree

4 files changed

+44
-18
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+44
-18
lines changed
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
PowerNorm no longer clips values below vmin
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
When ``clip=False`` is set (the default) on `~matplotlib.colors.PowerNorm`,
4+
values below ``vmin`` are now linearly normalised. Previously they were clipped
5+
to zero. This fixes issues with the display of colorbars associated with a power norm.

‎lib/matplotlib/colors.py

Copy file name to clipboardExpand all lines: lib/matplotlib/colors.py
+20-12Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1953,10 +1953,10 @@ class PowerNorm(Normalize):
19531953
Determines the behavior for mapping values outside the range
19541954
``[vmin, vmax]``.
19551955
1956-
If clipping is off, values outside the range ``[vmin, vmax]`` are also
1957-
transformed by the power function, resulting in values outside ``[0, 1]``.
1958-
This behavior is usually desirable, as colormaps can mark these *under*
1959-
and *over* values with specific colors.
1956+
If clipping is off, values above *vmax* are transformed by the power
1957+
function, resulting in values above 1, and values below *vmin* are linearly
1958+
transformed resulting in values below 0. This behavior is usually desirable, as
1959+
colormaps can mark these *under* and *over* values with specific colors.
19601960
19611961
If clipping is on, values below *vmin* are mapped to 0 and values above
19621962
*vmax* are mapped to 1. Such values become indistinguishable from
@@ -1969,6 +1969,8 @@ class PowerNorm(Normalize):
19691969
.. math::
19701970
19711971
\left ( \frac{x - v_{min}}{v_{max} - v_{min}} \right )^{\gamma}
1972+
1973+
For input values below *vmin*, gamma is set to one.
19721974
"""
19731975
def __init__(self, gamma, vmin=None, vmax=None, clip=False):
19741976
super().__init__(vmin, vmax, clip)
@@ -1994,9 +1996,8 @@ def __call__(self, value, clip=None):
19941996
mask=mask)
19951997
resdat = result.data
19961998
resdat -= vmin
1997-
resdat[resdat < 0] = 0
1998-
np.power(resdat, gamma, resdat)
1999-
resdat /= (vmax - vmin) ** gamma
1999+
resdat /= (vmax - vmin)
2000+
resdat[resdat > 0] = np.power(resdat[resdat > 0], gamma)
20002001

20012002
result = np.ma.array(resdat, mask=result.mask, copy=False)
20022003
if is_scalar:
@@ -2006,14 +2007,21 @@ def __call__(self, value, clip=None):
20062007
def inverse(self, value):
20072008
if not self.scaled():
20082009
raise ValueError("Not invertible until scaled")
2010+
2011+
result, is_scalar = self.process_value(value)
2012+
20092013
gamma = self.gamma
20102014
vmin, vmax = self.vmin, self.vmax
20112015

2012-
if np.iterable(value):
2013-
val = np.ma.asarray(value)
2014-
return np.ma.power(val, 1. / gamma) * (vmax - vmin) + vmin
2015-
else:
2016-
return pow(value, 1. / gamma) * (vmax - vmin) + vmin
2016+
resdat = result.data
2017+
resdat[resdat > 0] = np.power(resdat[resdat > 0], 1 / gamma)
2018+
resdat *= (vmax - vmin)
2019+
resdat += vmin
2020+
2021+
result = np.ma.array(resdat, mask=result.mask, copy=False)
2022+
if is_scalar:
2023+
result = result[0]
2024+
return result
20172025

20182026

20192027
class BoundaryNorm(Normalize):

‎lib/matplotlib/tests/test_colorbar.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_colorbar.py
+4-4Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -552,10 +552,10 @@ def test_colorbar_lognorm_extension(extend):
552552

553553
def test_colorbar_powernorm_extension():
554554
# Test that colorbar with powernorm is extended correctly
555-
f, ax = plt.subplots()
556-
cb = Colorbar(ax, norm=PowerNorm(gamma=0.5, vmin=0.0, vmax=1.0),
557-
orientation='vertical', extend='both')
558-
assert cb._values[0] >= 0.0
555+
# Just a smoke test that adding the colorbar doesn't raise an error or warning
556+
fig, ax = plt.subplots()
557+
Colorbar(ax, norm=PowerNorm(gamma=0.5, vmin=0.0, vmax=1.0),
558+
orientation='vertical', extend='both')
559559

560560

561561
def test_colorbar_axes_kw():

‎lib/matplotlib/tests/test_colors.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_colors.py
+15-2Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -555,12 +555,16 @@ def test_PowerNorm():
555555
assert_array_almost_equal(norm(a), pnorm(a))
556556

557557
a = np.array([-0.5, 0, 2, 4, 8], dtype=float)
558-
expected = [0, 0, 1/16, 1/4, 1]
558+
expected = [-1/16, 0, 1/16, 1/4, 1]
559559
pnorm = mcolors.PowerNorm(2, vmin=0, vmax=8)
560560
assert_array_almost_equal(pnorm(a), expected)
561561
assert pnorm(a[0]) == expected[0]
562562
assert pnorm(a[2]) == expected[2]
563-
assert_array_almost_equal(a[1:], pnorm.inverse(pnorm(a))[1:])
563+
# Check inverse
564+
a_roundtrip = pnorm.inverse(pnorm(a))
565+
assert_array_almost_equal(a, a_roundtrip)
566+
# PowerNorm inverse adds a mask, so check that is correct too
567+
assert_array_equal(a_roundtrip.mask, np.zeros(a.shape, dtype=bool))
564568

565569
# Clip = True
566570
a = np.array([-0.5, 0, 1, 8, 16], dtype=float)
@@ -591,6 +595,15 @@ def test_PowerNorm_translation_invariance():
591595
assert_array_almost_equal(pnorm(a - 2), expected)
592596

593597

598+
def test_powernorm_cbar_limits():
599+
fig, ax = plt.subplots()
600+
vmin, vmax = 300, 1000
601+
data = np.arange(10*10).reshape(10, 10) + vmin
602+
im = ax.imshow(data, norm=mcolors.PowerNorm(gamma=0.2, vmin=vmin, vmax=vmax))
603+
cbar = fig.colorbar(im)
604+
assert cbar.ax.get_ylim() == (vmin, vmax)
605+
606+
594607
def test_Normalize():
595608
norm = mcolors.Normalize()
596609
vals = np.arange(-10, 10, 1, dtype=float)

0 commit comments

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