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

Skip to content

Commit 98184f4

Browse files
authored
Merge pull request #21178 from rwpenney/feature/asinh-scale
Add asinh axis scaling (*smooth* symmetric logscale)
2 parents daaa231 + e43cbfd commit 98184f4

File tree

11 files changed

+684
-20
lines changed

11 files changed

+684
-20
lines changed

doc/api/colors_api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Classes
2121
:toctree: _as_gen/
2222
:template: autosummary.rst
2323

24+
AsinhNorm
2425
BoundaryNorm
2526
Colormap
2627
CenteredNorm
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
New axis scale ``asinh`` (experimental)
2+
---------------------------------------
3+
4+
The new ``asinh`` axis scale offers an alternative to ``symlog`` that
5+
smoothly transitions between the quasi-linear and asymptotically logarithmic
6+
regions of the scale. This is based on an arcsinh transformation that
7+
allows plotting both positive and negative values that span many orders
8+
of magnitude.
9+
10+
.. plot::
11+
12+
import matplotlib.pyplot as plt
13+
import numpy as np
14+
15+
fig, (ax0, ax1) = plt.subplots(1, 2, sharex=True)
16+
x = np.linspace(-3, 6, 100)
17+
18+
ax0.plot(x, x)
19+
ax0.set_yscale('symlog')
20+
ax0.grid()
21+
ax0.set_title('symlog')
22+
23+
ax1.plot(x, x)
24+
ax1.set_yscale('asinh')
25+
ax1.grid()
26+
ax1.set_title(r'$sinh^{-1}$')
27+
28+
for p in (-2, 2):
29+
for ax in (ax0, ax1):
30+
c = plt.Circle((p, p), radius=0.5, fill=False,
31+
color='red', alpha=0.8, lw=3)
32+
ax.add_patch(c)
Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,84 @@
11
"""
22
==================================
3-
Colormap Normalizations Symlognorm
3+
Colormap Normalizations SymLogNorm
44
==================================
55
66
Demonstration of using norm to map colormaps onto data in non-linear ways.
77
88
.. redirect-from:: /gallery/userdemo/colormap_normalization_symlognorm
99
"""
1010

11+
###############################################################################
12+
# Synthetic dataset consisting of two humps, one negative and one positive,
13+
# the positive with 8-times the amplitude.
14+
# Linearly, the negative hump is almost invisible,
15+
# and it is very difficult to see any detail of its profile.
16+
# With the logarithmic scaling applied to both positive and negative values,
17+
# it is much easier to see the shape of each hump.
18+
#
19+
# See `~.colors.SymLogNorm`.
20+
1121
import numpy as np
1222
import matplotlib.pyplot as plt
1323
import matplotlib.colors as colors
1424

15-
"""
16-
SymLogNorm: two humps, one negative and one positive, The positive
17-
with 5-times the amplitude. Linearly, you cannot see detail in the
18-
negative hump. Here we logarithmically scale the positive and
19-
negative data separately.
2025

21-
Note that colorbar labels do not come out looking very good.
22-
"""
26+
def rbf(x, y):
27+
return 1.0 / (1 + 5 * ((x ** 2) + (y ** 2)))
2328

24-
N = 100
29+
N = 200
30+
gain = 8
2531
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
26-
Z1 = np.exp(-X**2 - Y**2)
27-
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
28-
Z = (Z1 - Z2) * 2
32+
Z1 = rbf(X + 0.5, Y + 0.5)
33+
Z2 = rbf(X - 0.5, Y - 0.5)
34+
Z = gain * Z1 - Z2
35+
36+
shadeopts = {'cmap': 'PRGn', 'shading': 'gouraud'}
37+
colormap = 'PRGn'
38+
lnrwidth = 0.5
2939

30-
fig, ax = plt.subplots(2, 1)
40+
fig, ax = plt.subplots(2, 1, sharex=True, sharey=True)
3141

3242
pcm = ax[0].pcolormesh(X, Y, Z,
33-
norm=colors.SymLogNorm(linthresh=0.03, linscale=0.03,
34-
vmin=-1.0, vmax=1.0, base=10),
35-
cmap='RdBu_r', shading='nearest')
43+
norm=colors.SymLogNorm(linthresh=lnrwidth, linscale=1,
44+
vmin=-gain, vmax=gain, base=10),
45+
**shadeopts)
3646
fig.colorbar(pcm, ax=ax[0], extend='both')
47+
ax[0].text(-2.5, 1.5, 'symlog')
3748

38-
pcm = ax[1].pcolormesh(X, Y, Z, cmap='RdBu_r', vmin=-np.max(Z),
39-
shading='nearest')
49+
pcm = ax[1].pcolormesh(X, Y, Z, vmin=-gain, vmax=gain,
50+
**shadeopts)
4051
fig.colorbar(pcm, ax=ax[1], extend='both')
52+
ax[1].text(-2.5, 1.5, 'linear')
53+
54+
55+
###############################################################################
56+
# In order to find the best visualization for any particular dataset,
57+
# it may be necessary to experiment with multiple different color scales.
58+
# As well as the `~.colors.SymLogNorm` scaling, there is also
59+
# the option of using `~.colors.AsinhNorm` (experimental), which has a smoother
60+
# transition between the linear and logarithmic regions of the transformation
61+
# applied to the data values, "Z".
62+
# In the plots below, it may be possible to see contour-like artifacts
63+
# around each hump despite there being no sharp features
64+
# in the dataset itself. The ``asinh`` scaling shows a smoother shading
65+
# of each hump.
66+
67+
fig, ax = plt.subplots(2, 1, sharex=True, sharey=True)
68+
69+
pcm = ax[0].pcolormesh(X, Y, Z,
70+
norm=colors.SymLogNorm(linthresh=lnrwidth, linscale=1,
71+
vmin=-gain, vmax=gain, base=10),
72+
**shadeopts)
73+
fig.colorbar(pcm, ax=ax[0], extend='both')
74+
ax[0].text(-2.5, 1.5, 'symlog')
75+
76+
pcm = ax[1].pcolormesh(X, Y, Z,
77+
norm=colors.AsinhNorm(linear_width=lnrwidth,
78+
vmin=-gain, vmax=gain),
79+
**shadeopts)
80+
fig.colorbar(pcm, ax=ax[1], extend='both')
81+
ax[1].text(-2.5, 1.5, 'asinh')
82+
4183

4284
plt.show()

examples/scales/asinh_demo.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""
2+
============
3+
Asinh Demo
4+
============
5+
6+
Illustration of the `asinh <.scale.AsinhScale>` axis scaling,
7+
which uses the transformation
8+
9+
.. math::
10+
11+
a \\rightarrow a_0 \\sinh^{-1} (a / a_0)
12+
13+
For coordinate values close to zero (i.e. much smaller than
14+
the "linear width" :math:`a_0`), this leaves values essentially unchanged:
15+
16+
.. math::
17+
18+
a \\rightarrow a + {\\cal O}(a^3)
19+
20+
but for larger values (i.e. :math:`|a| \\gg a_0`, this is asymptotically
21+
22+
.. math::
23+
24+
a \\rightarrow a_0 \\, {\\rm sgn}(a) \\ln |a| + {\\cal O}(1)
25+
26+
As with the `symlog <.scale.SymmetricalLogScale>` scaling,
27+
this allows one to plot quantities
28+
that cover a very wide dynamic range that includes both positive
29+
and negative values. However, ``symlog`` involves a transformation
30+
that has discontinuities in its gradient because it is built
31+
from *separate* linear and logarithmic transformations.
32+
The ``asinh`` scaling uses a transformation that is smooth
33+
for all (finite) values, which is both mathematically cleaner
34+
and reduces visual artifacts associated with an abrupt
35+
transition between linear and logarithmic regions of the plot.
36+
37+
.. note::
38+
`.scale.AsinhScale` is experimental, and the API may change.
39+
40+
See `~.scale.AsinhScale`, `~.scale.SymmetricalLogScale`.
41+
"""
42+
43+
import numpy as np
44+
import matplotlib.pyplot as plt
45+
46+
# Prepare sample values for variations on y=x graph:
47+
x = np.linspace(-3, 6, 500)
48+
49+
###############################################################################
50+
# Compare "symlog" and "asinh" behaviour on sample y=x graph,
51+
# where there is a discontinuous gradient in "symlog" near y=2:
52+
fig1 = plt.figure()
53+
ax0, ax1 = fig1.subplots(1, 2, sharex=True)
54+
55+
ax0.plot(x, x)
56+
ax0.set_yscale('symlog')
57+
ax0.grid()
58+
ax0.set_title('symlog')
59+
60+
ax1.plot(x, x)
61+
ax1.set_yscale('asinh')
62+
ax1.grid()
63+
ax1.set_title('asinh')
64+
65+
66+
###############################################################################
67+
# Compare "asinh" graphs with different scale parameter "linear_width":
68+
fig2 = plt.figure(constrained_layout=True)
69+
axs = fig2.subplots(1, 3, sharex=True)
70+
for ax, (a0, base) in zip(axs, ((0.2, 2), (1.0, 0), (5.0, 10))):
71+
ax.set_title('linear_width={:.3g}'.format(a0))
72+
ax.plot(x, x, label='y=x')
73+
ax.plot(x, 10*x, label='y=10x')
74+
ax.plot(x, 100*x, label='y=100x')
75+
ax.set_yscale('asinh', linear_width=a0, base=base)
76+
ax.grid()
77+
ax.legend(loc='best', fontsize='small')
78+
79+
80+
###############################################################################
81+
# Compare "symlog" and "asinh" scalings
82+
# on 2D Cauchy-distributed random numbers,
83+
# where one may be able to see more subtle artifacts near y=2
84+
# due to the gradient-discontinuity in "symlog":
85+
fig3 = plt.figure()
86+
ax = fig3.subplots(1, 1)
87+
r = 3 * np.tan(np.random.uniform(-np.pi / 2.02, np.pi / 2.02,
88+
size=(5000,)))
89+
th = np.random.uniform(0, 2*np.pi, size=r.shape)
90+
91+
ax.scatter(r * np.cos(th), r * np.sin(th), s=4, alpha=0.5)
92+
ax.set_xscale('asinh')
93+
ax.set_yscale('symlog')
94+
ax.set_xlabel('asinh')
95+
ax.set_ylabel('symlog')
96+
ax.set_title('2D Cauchy random deviates')
97+
ax.set_xlim(-50, 50)
98+
ax.set_ylim(-50, 50)
99+
ax.grid()
100+
101+
plt.show()
102+
103+
###############################################################################
104+
#
105+
# .. admonition:: References
106+
#
107+
# - `matplotlib.scale.AsinhScale`
108+
# - `matplotlib.ticker.AsinhLocator`
109+
# - `matplotlib.scale.SymmetricalLogScale`

examples/scales/symlog_demo.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,17 @@
3333

3434
fig.tight_layout()
3535
plt.show()
36+
37+
###############################################################################
38+
# It should be noted that the coordinate transform used by ``symlog``
39+
# has a discontinuous gradient at the transition between its linear
40+
# and logarithmic regions. The ``asinh`` axis scale is an alternative
41+
# technique that may avoid visual artifacts caused by these disconinuities.
42+
43+
###############################################################################
44+
#
45+
# .. admonition:: References
46+
#
47+
# - `matplotlib.scale.SymmetricalLogScale`
48+
# - `matplotlib.ticker.SymmetricalLogLocator`
49+
# - `matplotlib.scale.AsinhScale`

lib/matplotlib/colors.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,6 +1686,38 @@ def linthresh(self, value):
16861686
self._scale.linthresh = value
16871687

16881688

1689+
@make_norm_from_scale(
1690+
scale.AsinhScale,
1691+
init=lambda linear_width=1, vmin=None, vmax=None, clip=False: None)
1692+
class AsinhNorm(Normalize):
1693+
"""
1694+
The inverse hyperbolic sine scale is approximately linear near
1695+
the origin, but becomes logarithmic for larger positive
1696+
or negative values. Unlike the `SymLogNorm`, the transition between
1697+
these linear and logarithmic regions is smooth, which may reduce
1698+
the risk of visual artifacts.
1699+
1700+
.. note::
1701+
1702+
This API is provisional and may be revised in the future
1703+
based on early user feedback.
1704+
1705+
Parameters
1706+
----------
1707+
linear_width : float, default: 1
1708+
The effective width of the linear region, beyond which
1709+
the transformation becomes asymptotically logarithmic
1710+
"""
1711+
1712+
@property
1713+
def linear_width(self):
1714+
return self._scale.linear_width
1715+
1716+
@linear_width.setter
1717+
def linear_width(self, value):
1718+
self._scale.linear_width = value
1719+
1720+
16891721
class PowerNorm(Normalize):
16901722
"""
16911723
Linearly map a given value to the 0-1 range and then apply

0 commit comments

Comments
 (0)