8000 Merge pull request #27589 from dstansby/extend-power-norm · matplotlib/matplotlib@6f635b4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 6f635b4

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

File tree

4 files changed

+44
-18
lines changed

4 files changed

+44
-18
lines changed
Lines 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

Lines 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

Lines 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

Lines 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