| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 922dea2 commit cc17eb1
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,12 +25,14 @@ Classes | |||
| 25 | 25 | ||
| 26 | 26 | BoundaryNorm | |
| 27 | 27 | Colormap | |
| 28 | + DivergingNorm | ||
| 28 | 29 | LightSource | |
| 29 | 30 | LinearSegmentedColormap | |
| 30 | 31 | ListedColormap | |
| 31 | 32 | LogNorm | |
| 32 | 33 | NoNorm | |
| 33 | 34 | Normalize | |
| 35 | + PiecewiseLinearNorm | ||
| 34 | 36 | PowerNorm | |
| 35 | 37 | SymLogNorm | |
| 36 | 38 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,7 +10,6 @@ revision, see the :ref:`github-stats`. | |||
| 10 | 10 | .. contents:: Table of Contents | |
| 11 | 11 | :depth: 4 | |
| 12 | 12 | ||
| 13 | - | ||
| 14 | 13 | .. | |
| 15 | 14 | For a release, add a new section after this, then comment out the include | |
| 16 | 15 | and toctree below by indenting them. Uncomment them after the release. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -873,8 +873,8 @@ def _process_values(self, b=None): | |||
| 873 | 873 | + self._boundaries[1:]) | |
| 874 | 874 | if isinstance(self.norm, colors.NoNorm): | |
| 875 | 875 | self._values = (self._values + 0.00001).astype(np.int16) | |
| 876 | - return | ||
| 877 | - self._values = np.array(self.values) | ||
| 876 | + else: | ||
| 877 | + self._values = np.array(self.values) | ||
| 878 | 878 | return | |
| 879 | 879 | if self.values is not None: | |
| 880 | 880 | self._values = np.array(self.values) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -955,6 +955,176 @@ def scaled(self): | |||
| 955 | 955 | return self.vmin is not None and self.vmax is not None | |
| 956 | 956 | ||
| 957 | 957 | ||
| 958 | + class PiecewiseLinearNorm(Normalize): | ||
| 959 | + """ | ||
| 960 | + Normalizes data into the ``[0.0, 1.0]`` interval over linear segments. | ||
| 961 | + """ | ||
| 962 | + def __init__(self, data_points=None, norm_points=None): | ||
| 963 | + """Normalize data linearly between the defined stop points. | ||
| 964 | + | ||
| 965 | + Parameters | ||
| 966 | + ---------- | ||
| 967 | + data_points : tuple of floats | ||
| 968 | + Floats spanning the data to be mapped between 0-1 | ||
| 969 | + | ||
| 970 | + norm_points : tuple of floats or None (default) | ||
| 971 | + Floats spanning [0, 1] that the data points will map to. If | ||
| 972 | + *None*, the data points will be mapped equally into [0, 1]. | ||
| 973 | + | ||
| 974 | + Examples | ||
| 975 | + -------- | ||
| 976 | + Note this example is equivalent to the `.DivergingNorm` example. | ||
| 977 | + >>> import matplotlib.colors as mcolors | ||
| 978 | + >>> offset = mcolors.PiecewiseLinearNorm([-2, 0, 4]) | ||
| 979 | + >>> data = [-2., -1., 0., 1., 2., 3., 4.] | ||
| 980 | + >>> offset(data) | ||
| 981 | + array([0., 0.25, 0.5, 0.625, 0.75, 0.875, 1.0]) | ||
| 982 | + """ | ||
| 983 | + self._data_points = (np.asarray(data_points) | ||
| 984 | + if data_points is not None | ||
| 985 | + else None) | ||
| 986 | + self._norm_points = (np.asarray(norm_points) | ||
| 987 | + if norm_points is not None | ||
| 988 | + else None) | ||
| 989 | + | ||
| 990 | + if not np.all(np.diff(self._data_points) > 0): | ||
| 991 | + raise ValueError("data points must increase monotonically") | ||
| 992 | + | ||
| 993 | + self.vmin = data_points[0] | ||
| 994 | + self.vmax = data_points[-1] | ||
| 995 | + | ||
| 996 | + def __call__(self, value, clip=None): | ||
| 997 | + """ | ||
| 998 | + Map value to the interval [0, 1]. The clip argument is unused. | ||
| 999 | + """ | ||
| 1000 | + | ||
| 1001 | + result, is_scalar = self.process_value(value) | ||
| 1002 | + self.autoscale_None(result) | ||
| 1003 | + vmin, vmax = self._data_points[0], self._data_points[-1] | ||
| 1004 | + | ||
| 1005 | + # its possible some of the data points are less than vmin | ||
| 1006 | + # or vmax. We could error out if some stop points are | ||
| 1007 | + # overrun, but this lets things keep going... | ||
| 1008 | + if not np.all(np.diff(self._data_points) > 0): | ||
| 1009 | + raise ValueError("data points must increase monotonically") | ||
| 1010 | + ind = np.where((self._data_points >= vmin) & | ||
| 1011 | + (self._data_points <= vmax))[0] | ||
| 1012 | + result = np.ma.masked_array(np.interp(result, | ||
| 1013 | + self._data_points[ind], | ||
| 1014 | + self._norm_points[ind]), | ||
| 1015 | + mask=np.ma.getmask(result)) | ||
| 1016 | + if is_scalar: | ||
| 1017 | + result = np.atleast_1d(result)[0] | ||
| 1018 | + return result | ||
| 1019 | + | ||
| 1020 | + def autoscale_None(self, A): | ||
| 1021 | + """ | ||
| 1022 | + Parameters: | ||
| 1023 | + ----------- | ||
| 1024 | + | ||
| 1025 | + A : tuple | ||
| 1026 | + data used for autoscaling, if appropriate. | ||
| 1027 | + | ||
| 1028 | + If ``norm.vmin` is None, or ``norm.vmax`` are none, use | ||
| 1029 | + the min and max of ``A`` for the endpoints of the linear mapping. | ||
| 1030 | + """ | ||
| 1031 | + | ||
| 1032 | + # allow autoscaling with the data if the user specifies | ||
| 1033 | + # vmin or vmax is None. Note we never set vmax/vmin in the class | ||
| 1034 | + # except at init. | ||
| 1035 | + vmin = self.vmin | ||
| 1036 | + if vmin is None: | ||
| 1037 | + vmin = np.ma.min(A) | ||
| 1038 | + vmax = self.vmax | ||
| 1039 | + if vmax is None: | ||
| 1040 | + vmax = np.ma.max(A) | ||
| 1041 | + | ||
| 1042 | + if vmin > vmax: | ||
| 1043 | + raise ValueError('vmin must be less than or equal to vmax') | ||
| 1044 | + | ||
| 1045 | + if self._data_points is None: | ||
| 1046 | + self._data_points = np.asarray([vmin, vmax]) | ||
| 1047 | + if self._norm_points is None: | ||
| 1048 | + N = len(self._data_points) | ||
| 1049 | + self._norm_points = np.linspace(0, 1, N) | ||
| 1050 | + | ||
| 1051 | + self._data_points[0] = vmin | ||
| 1052 | + self._data_points[-1] = vmax | ||
| 1053 | + self.vmin = vmin | ||
| 1054 | + self.vmax = vmax | ||
| 1055 | + | ||
| 1056 | + | ||
| 1057 | + class DivergingNorm(PiecewiseLinearNorm): | ||
| 1058 | + def __init__(self, vmin=None, vcenter=None, vmax=None): | ||
| 1059 | + """ | ||
| 1060 | + Normalize data with a set center. | ||
| 1061 | + | ||
| 1062 | + Useful when mapping data with an unequal rates of change around a | ||
| 1063 | + conceptual center, e.g., data that range from -2 to 4, with 0 as | ||
| 1064 | + the midpoint. | ||
| 1065 | + | ||
| 1066 | + Parameters | ||
| 1067 | + ---------- | ||
| 1068 | + vmin : float, optional | ||
| 1069 | + The data value that defines ``0.0`` in the normalization. | ||
| 1070 | + Defaults to the min value of the dataset. | ||
| 1071 | + vcenter : float, optional | ||
| 1072 | + The data value that defines ``0.5`` in the normalization. | ||
| 1073 | + If not defined, the normalization just linearly maps between | ||
| 1074 | + *vmin* and *vmax*. | ||
| 1075 | + vmax : float, optional | ||
| 1076 | + The data value that defines ``1.0`` in the normalization. | ||
| 1077 | + Defaults to the the max value of the dataset. | ||
| 1078 | + | ||
| 1079 | + Examples | ||
| 1080 | + -------- | ||
| 1081 | + This maps data value -4000 to 0., 0 to 0.5, and +10000 to 1.0; data | ||
| 1082 | + between is linearly interpolated:: | ||
| 1083 | + | ||
| 1084 | + >>> import matplotlib.colors as mcolors | ||
| 1085 | + >>> offset = mcolors.DivergingNorm(vmin=-4000., | ||
| 1086 | + vcenter=0., vmax=10000) | ||
| 1087 | + >>> data = [-4000., -2000., 0., 2500., 5000., 7500., 10000.] | ||
| 1088 | + >>> offset(data) | ||
| 1089 | + array([0., 0.25, 0.5, 0.625, 0.75, 0.875, 1.0]) | ||
| 1090 | + """ | ||
| 1091 | + | ||
| 1092 | + # vmin/vmax will set the first and last value, so they don't | ||
| 1093 | + # matter very much. | ||
| 1094 | + if vcenter is None: | ||
| 1095 | + stops = [0, 1] | ||
| 1096 | + else: | ||
| 1097 | + if not vcenter == 0: | ||
| 1098 | + stops = [vcenter * 0.75, vcenter, vcenter * 1.5] | ||
| 1099 | + else: | ||
| 1100 | + stops = [-1, vcenter, 1] | ||
| 1101 | + | ||
| 1102 | + super(DivergingNorm, self).__init__(stops) | ||
| 1103 | + # if these have been specified we need to set them here so | ||
| 1104 | + # DivergingNorm knows they are user-set limits. Otherwise it | ||
| 1105 | + # will autoscale to data. | ||
| 1106 | + self.vmin = vmin | ||
| 1107 | + self.vmax = vmax | ||
| 1108 | + if vcenter is not None and vmax is not None and vcenter > vmax: | ||
| 1109 | + raise ValueError('vmin, vcenter, and vmax must be in ' | ||
| 1110 | + 'ascending order') | ||
| 1111 | + if vcenter is not None and vmin is not None and vcenter < vmin: | ||
| 1112 | + raise ValueError('vmin, vcenter, and vmax must be in ' | ||
| 1113 | + 'ascending order') | ||
| 1114 | + | ||
| 1115 | + def __call__(value, clip=None): | ||
| 1116 | + """ | ||
| 1117 | + Map value to the interval [0, 1]. The clip argument is unused. | ||
| 1118 | + """ | ||
| 1119 | + try: | ||
| 1120 | + super().__call(value, clip=clip) | ||
| 1121 | + except ValueError: | ||
| 1122 | + # improve the more general error message. | ||
| 1123 | + raise ValueError('vmin, vcenter, and vmax must ' | ||
| 1124 | + 'increment monotonically for DivergingNorm ' | ||
| 1125 | + 'to work.') | ||
| 1126 | + | ||
| 1127 | + | ||
| 958 | 1128 | class LogNorm(Normalize): | |
| 959 | 1129 | """Normalize a given value to the 0-1 range on a log scale.""" | |
| 960 | 1130 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,10 +1,9 @@ | |||
| 1 | 1 | import copy | |
| 2 | 2 | import itertools | |
| 3 | - | ||
| 4 | 3 | import numpy as np | |
| 5 | 4 | import pytest | |
| 6 | 5 | ||
| 7 | - from numpy.testing import assert_array_equal, assert_array_almost_equal | ||
| 6 | + from numpy.testing import (assert_array_equal, assert_array_almost_equal) | ||
| 8 | 7 | ||
| 9 | 8 | from matplotlib import cycler | |
| 10 | 9 | import matplotlib | |
@@ -221,6 +220,107 @@ def test_Normalize(): | |||
| 221 | 220 | assert 0 < norm(1 + 50 * eps) < 1 | |
| 222 | 221 | ||
| 223 | 222 | ||
| 223 | + def test_DivergingNorm_autoscale(): | ||
| 224 | + norm = mcolors.DivergingNorm() | ||
| 225 | + norm.autoscale([10, 20, 30, 40]) | ||
| 226 | + assert norm.vmin == 10. | ||
| 227 | + assert norm.vmax == 40. | ||
| 228 | + | ||
| 229 | + | ||
| 230 | + def test_DivergingNorm_autoscale_None_vmin(): | ||
| 231 | + norm = mcolors.DivergingNorm(vmin=0, vmax=None) | ||
| 232 | + norm.autoscale_None([1, 2, 3, 4, 5]) | ||
| 233 | + assert norm(5) == 1 | ||
| 234 | + assert norm.vmax == 5 | ||
| 235 | + | ||
| 236 | + | ||
| 237 | + def test_DivergingNorm_autoscale_None_vmax(): | ||
| 238 | + norm = mcolors.DivergingNorm(vmin=None, vmax=10) | ||
| 239 | + norm.autoscale_None([1, 2, 3, 4, 5]) | ||
| 240 | + assert norm(1) == 0 | ||
| 241 | + assert norm.vmin == 1 | ||
| 242 | + | ||
| 243 | + | ||
| 244 | + def test_DivergingNorm_scale(): | ||
| 245 | + norm = mcolors.DivergingNorm() | ||
| 246 | + assert norm.scaled() is False | ||
| 247 | + norm([1, 2, 3, 4]) | ||
| 248 | + assert norm.scaled() is True | ||
| 249 | + | ||
| 250 | + | ||
| 251 | + def test_DivergingNorm_scaleout_center(): | ||
| 252 | + norm = mcolors.DivergingNorm(vcenter=0) | ||
| 253 | + # if the center is defined outside the data limits, should raise an err. | ||
| 254 | + with pytest.raises(ValueError): | ||
| 255 | + norm([1, 2, 3, 5]) | ||
| 256 | + | ||
| 257 | + | ||
| 258 | + def test_DivergingNorm_Even(): | ||
| 259 | + norm = mcolors.DivergingNorm(vmin=-1, vcenter=0, vmax=4) | ||
| 260 | + vals = np.array([-1.0, -0.5, 0.0, 1.0, 2.0, 3.0, 4.0]) | ||
| 261 | + expected = np.array([0.0, 0.25, 0.5, 0.625, 0.75, 0.875, 1.0]) | ||
| 262 | + assert_array_equal(norm(vals), expected) | ||
| 263 | + | ||
| 264 | + | ||
| 265 | + def test_DivergingNorm_Odd(): | ||
| 266 | + norm = mcolors.DivergingNorm(vmin=-2, vcenter=0, vmax=5) | ||
| 267 | + vals = np.array([-2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0]) | ||
| 268 | + expected = np.array([0.0, 0.25, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]) | ||
| 269 | + assert_array_equal(norm(vals), expected) | ||
| 270 | + | ||
| 271 | + | ||
| 272 | + def test_DivergingNorm_VminEqualsVcenter(): | ||
| 273 | + with pytest.raises(ValueError): | ||
| 274 | + norm = mcolors.DivergingNorm(vmin=-2, vcenter=-2, vmax=2) | ||
| 275 | + | ||
| 276 | + | ||
| 277 | + def test_DivergingNorm_VmaxEqualsVcenter(): | ||
| 278 | + norm = mcolors.DivergingNorm(vmin=-2, vcenter=2, vmax=2) | ||
| 279 | + with pytest.raises(ValueError): | ||
| 280 | + norm(1.5) | ||
| 281 | + | ||
| 282 | + | ||
| 283 | + def test_DivergingNorm_VminGTVcenter(): | ||
| 284 | + with pytest.raises(ValueError): | ||
| 285 | + norm = mcolors.DivergingNorm(vmin=10, vcenter=0, vmax=20) | ||
| 286 | + | ||
| 287 | + | ||
| 288 | + def test_DivergingNorm_DivergingNorm_VminGTVmax(): | ||
| 289 | + with pytest.raises(ValueError): | ||
| 290 | + norm = mcolors.DivergingNorm(vmin=10, vcenter=0, vmax=5) | ||
| 291 | + | ||
| 292 | + | ||
| 293 | + def test_DivergingNorm_VcenterGTVmax(): | ||
| 294 | + vals = np.arange(50) | ||
| 295 | + with pytest.raises(ValueError): | ||
| 296 | + norm = mcolors.DivergingNorm(vmin=10, vcenter=25, vmax=20) | ||
| 297 | + | ||
| 298 | + | ||
| 299 | + def test_DivergingNorm_premature_scaling(): | ||
| 300 | + with pytest.raises(ValueError): | ||
| 301 | + norm = mcolors.DivergingNorm() | ||
| 302 | + norm.inverse(np.array([0.1, 0.5, 0.9])) | ||
| 303 | + | ||
| 304 | + | ||
| 305 | + @image_comparison(baseline_images=['test_offset_norm'], extensions=['png'], | ||
| 306 | + style='mpl20') | ||
| 307 | + def test_offset_norm_img(): | ||
| 308 | + x = np.linspace(-2, 7) | ||
| 309 | + y = np.linspace(-1*np.pi, np.pi) | ||
| 310 | + X, Y = np.meshgrid(x, y) | ||
| 311 | + Z = x * np.sin(Y)**2 | ||
| 312 | + | ||
| 313 | + fig, (ax1, ax2) = plt.subplots(ncols=2) | ||
| 314 | + cmap = plt.cm.coolwarm | ||
| 315 | + norm = mcolors.DivergingNorm(vmin=-2, vcenter=0, vmax=7) | ||
| 316 | + | ||
| 317 | + img1 = ax1.pcolormesh(Z, cmap=cmap, norm=None) | ||
| 318 | + cbar1 = fig.colorbar(img1, ax=ax1) | ||
| 319 | + | ||
| 320 | + img2 = ax2.pcolormesh(Z, cmap=cmap, norm=norm) | ||
| 321 | + cbar2 = fig.colorbar(img2, ax=ax2) | ||
| 322 | + | ||
| 323 | + | ||
| 224 | 324 | def test_SymLogNorm(): | |
| 225 | 325 | """ | |
| 226 | 326 | Test SymLogNorm behavior | |
| Back | FazBrowse Home | New Git URL |
0 commit comments