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

Skip to content

Commit 9b81d13

Browse files
committed
Re-write sym-log-norm
1 parent 20805b8 commit 9b81d13

File tree

2 files changed

+41
-39
lines changed

2 files changed

+41
-39
lines changed

lib/matplotlib/colors.py

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,7 +1194,7 @@ class SymLogNorm(Normalize):
11941194
*linthresh* allows the user to specify the size of this range
11951195
(-*linthresh*, *linthresh*).
11961196
"""
1197-
def __init__(self, linthresh, linscale=1.0,
1197+
def __init__(self, linthresh, linscale=1.0,
11981198
vmin=None, vmax=None, clip=False):
11991199
"""
12001200
*linthresh*:
@@ -1212,9 +1212,10 @@ def __init__(self, linthresh, linscale=1.0,
12121212
"""
12131213
Normalize.__init__(self, vmin, vmax, clip)
12141214
self.linthresh = float(linthresh)
1215-
self._linscale_adj = (linscale / (1.0 - np.e ** -1))
1216-
if vmin is not None and vmax is not None:
1217-
self._transform_vmin_vmax()
1215+
# Number of decades in the log region
1216+
ndec = np.log10(vmax / linthresh)
1217+
# Size of the linear region from 0 to linthresh in the transformed space
1218+
self.linear_size = 1 / (1 + linscale / ndec)
12181219

12191220
def __call__(self, value, clip=None):
12201221
if clip is None:
@@ -1235,57 +1236,52 @@ def __call__(self, value, clip=None):
12351236
mask=mask)
12361237
# in-place equivalent of above can be much faster
12371238
resdat = self._transform(result.data)
1238-
resdat -= self._lower
1239-
resdat /= (self._upper - self._lower)
12401239

12411240
if is_scalar:
12421241
result = result[0]
12431242
return result
12441243

12451244
def _transform(self, a):
1246-
"""Inplace transformation."""
1245+
"""In-place mapping from *a* to [0, 1]"""
12471246
with np.errstate(invalid="ignore"):
1248-
masked = np.abs(a) > self.linthresh
1249-
sign = np.sign(a[masked])
1250-
log = (self._linscale_adj + np.log(np.abs(a[masked]) / self.linthresh))
1251-
log *= sign * self.linthresh
1252-
a[masked] = log
1253-
a[~masked] *= self._linscale_adj
1247+
logregion = np.abs(a) > self.linthresh
1248+
1249+
# Transform log value
1250+
sign = np.sign(a[logregion])
1251+
log = (1 - self.linear_size) * np.log10(np.abs(a[logregion])) + self.linear_size
1252+
a[logregion] = log * sign
1253+
1254+
# Transform linear values
1255+
a[~logregion] *= self.linear_size / self.linthresh
1256+
1257+
# Transform from [-1, 1] to [0, 1]
1258+
a += 1
1259+
a /= 2
12541260
return a
12551261

12561262
def _inv_transform(self, a):
12571263
"""Inverse inplace Transformation."""
1258-
masked = np.abs(a) > (self.linthresh * self._linscale_adj)
1259-
sign = np.sign(a[masked])
1260-
exp = np.exp(sign * a[masked] / self.linthresh - self._linscale_adj)
1261-
exp *= sign * self.linthresh
1262-
a[masked] = exp
1263-
a[~masked] /= self._linscale_adj
1264+
# Transform from [0, 1] to [-1, 1]
1265+
a *= 2
1266+
a -= 1
1267+
1268+
# Transform back log values
1269+
logregion = np.abs(a) > self.linear_size
1270+
sign = np.sign(a[logregion])
1271+
exp = 10**((np.abs(a[logregion]) - self.linear_size) /
1272+
(1 - self.linear_size))
1273+
a[logregion] = exp * sign
1274+
1275+
# Transform back linear values
1276+
a[~logregion] /= self.linear_size / self.linthresh
12641277
return a
12651278

1266-
def _transform_vmin_vmax(self):
1267-
"""Calculates vmin and vmax in the transformed system."""
1268-
vmin, vmax = self.vmin, self.vmax
1269-
arr = np.array([vmax, vmin]).astype(float)
1270-
self._upper, self._lower = self._transform(arr)
1271-
12721279
def inverse(self, value):
12731280
if not self.scaled():
12741281
raise ValueError("Not invertible until scaled")
12751282
val = np.ma.asarray(value)
1276-
val = val * (self._upper - self._lower) + self._lower
12771283
return self._inv_transform(val)
12781284

1279-
def autoscale(self, A):
1280-
# docstring inherited.
1281-
super().autoscale(A)
1282-
self._transform_vmin_vmax()
1283-
1284-
def autoscale_None(self, A):
1285-
# docstring inherited.
1286-
super().autoscale_None(A)
1287-
self._transform_vmin_vmax()
1288-
12891285

12901286
class PowerNorm(Normalize):
12911287
"""

lib/matplotlib/tests/test_colors.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -395,9 +395,7 @@ def test_TwoSlopeNorm_premature_scaling():
395395

396396

397397
def test_SymLogNorm():
398-
"""
399-
Test SymLogNorm behavior
400-
"""
398+
# Test SymLogNorm behavior
401399
norm = mcolors.SymLogNorm(3, vmax=5, linscale=1.2)
402400
vals = np.array([-30, -1, 2, 6], dtype=float)
403401
normed_vals = norm(vals)
@@ -413,6 +411,14 @@ def test_SymLogNorm():
413411
assert_array_almost_equal(normed_vals, expected)
414412

415413

414+
@pytest.mark.parametrize('val,normed',
415+
([10, 1], [1, 0.75], [0, 0.5], [-1, 0.25], [-10, 0]))
416+
def test_symlognorm_vals(val, normed):
417+
norm = mcolors.SymLogNorm(linthresh=1, vmin=-10, vmax=10, linscale=1)
418+
assert_array_almost_equal(norm(val), normed)
419+
assert_array_almost_equal(norm.inverse(norm(val)), val)
420+
421+
416422
def test_SymLogNorm_colorbar():
417423
"""
418424
Test un-called SymLogNorm in a colorbar.

0 commit comments

Comments
 (0)