From c803091c7ed44307247767c3c860219fe1ac3409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Tue, 25 Jun 2024 15:53:56 +0200 Subject: [PATCH 01/18] MultivarColormap and BivarColormap Creation and tests for classes containing multivariate and bivariate colormaps. --- lib/matplotlib/__init__.py | 4 + lib/matplotlib/_cm_bivar.py | 1312 +++++++++++++++++ lib/matplotlib/_cm_multivar.py | 166 +++ lib/matplotlib/cm.py | 8 + lib/matplotlib/colors.py | 613 +++++++- lib/matplotlib/colors.pyi | 68 +- lib/matplotlib/meson.build | 2 + .../bivariate_cmap_shapes.png | Bin 0 -> 5157 bytes .../multivar_alpha_mixing.png | Bin 0 -> 4917 bytes lib/matplotlib/tests/meson.build | 1 + .../tests/test_multivariate_colormaps.py | 440 ++++++ 11 files changed, 2608 insertions(+), 6 deletions(-) create mode 100644 lib/matplotlib/_cm_bivar.py create mode 100644 lib/matplotlib/_cm_multivar.py create mode 100644 lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/bivariate_cmap_shapes.png create mode 100644 lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/multivar_alpha_mixing.png create mode 100644 lib/matplotlib/tests/test_multivariate_colormaps.py diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index dc8c004a598b..13bfa81d9ffa 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -129,6 +129,8 @@ "interactive", "is_interactive", "colormaps", + "multivar_colormaps", + "bivar_colormaps", "color_sequences", ] @@ -1543,4 +1545,6 @@ def validate_backend(s): # workaround: we must defer colormaps import to after loading rcParams, because # colormap creation depends on rcParams from matplotlib.cm import _colormaps as colormaps # noqa: E402 +from matplotlib.cm import _multivar_colormaps as multivar_colormaps # noqa: E402 +from matplotlib.cm import _bivar_colormaps as bivar_colormaps # noqa: E402 from matplotlib.colors import _color_sequences as color_sequences # noqa: E402 diff --git a/lib/matplotlib/_cm_bivar.py b/lib/matplotlib/_cm_bivar.py new file mode 100644 index 000000000000..c9ff59930bf3 --- /dev/null +++ b/lib/matplotlib/_cm_bivar.py @@ -0,0 +1,1312 @@ +# auto-genreated by https://github.com/trygvrad/multivariate_colormaps +# date: 2024-05-24 + +import numpy as np +from matplotlib.colors import SegmentedBivarColormap + +BiPeak = np.array( + [0.000, 0.674, 0.931, 0.000, 0.680, 0.922, 0.000, 0.685, 0.914, 0.000, + 0.691, 0.906, 0.000, 0.696, 0.898, 0.000, 0.701, 0.890, 0.000, 0.706, + 0.882, 0.000, 0.711, 0.875, 0.000, 0.715, 0.867, 0.000, 0.720, 0.860, + 0.000, 0.725, 0.853, 0.000, 0.729, 0.845, 0.000, 0.733, 0.838, 0.000, + 0.737, 0.831, 0.000, 0.741, 0.824, 0.000, 0.745, 0.816, 0.000, 0.749, + 0.809, 0.000, 0.752, 0.802, 0.000, 0.756, 0.794, 0.000, 0.759, 0.787, + 0.000, 0.762, 0.779, 0.000, 0.765, 0.771, 0.000, 0.767, 0.764, 0.000, + 0.770, 0.755, 0.000, 0.772, 0.747, 0.000, 0.774, 0.739, 0.000, 0.776, + 0.730, 0.000, 0.777, 0.721, 0.000, 0.779, 0.712, 0.021, 0.780, 0.702, + 0.055, 0.781, 0.693, 0.079, 0.782, 0.682, 0.097, 0.782, 0.672, 0.111, + 0.782, 0.661, 0.122, 0.782, 0.650, 0.132, 0.782, 0.639, 0.140, 0.781, + 0.627, 0.147, 0.781, 0.615, 0.154, 0.780, 0.602, 0.159, 0.778, 0.589, + 0.164, 0.777, 0.576, 0.169, 0.775, 0.563, 0.173, 0.773, 0.549, 0.177, + 0.771, 0.535, 0.180, 0.768, 0.520, 0.184, 0.766, 0.505, 0.187, 0.763, + 0.490, 0.190, 0.760, 0.474, 0.193, 0.756, 0.458, 0.196, 0.753, 0.442, + 0.200, 0.749, 0.425, 0.203, 0.745, 0.408, 0.206, 0.741, 0.391, 0.210, + 0.736, 0.373, 0.213, 0.732, 0.355, 0.216, 0.727, 0.337, 0.220, 0.722, + 0.318, 0.224, 0.717, 0.298, 0.227, 0.712, 0.278, 0.231, 0.707, 0.258, + 0.235, 0.701, 0.236, 0.239, 0.696, 0.214, 0.242, 0.690, 0.190, 0.246, + 0.684, 0.165, 0.250, 0.678, 0.136, 0.000, 0.675, 0.934, 0.000, 0.681, + 0.925, 0.000, 0.687, 0.917, 0.000, 0.692, 0.909, 0.000, 0.697, 0.901, + 0.000, 0.703, 0.894, 0.000, 0.708, 0.886, 0.000, 0.713, 0.879, 0.000, + 0.718, 0.872, 0.000, 0.722, 0.864, 0.000, 0.727, 0.857, 0.000, 0.731, + 0.850, 0.000, 0.736, 0.843, 0.000, 0.740, 0.836, 0.000, 0.744, 0.829, + 0.000, 0.748, 0.822, 0.000, 0.752, 0.815, 0.000, 0.755, 0.808, 0.000, + 0.759, 0.800, 0.000, 0.762, 0.793, 0.000, 0.765, 0.786, 0.000, 0.768, + 0.778, 0.000, 0.771, 0.770, 0.000, 0.773, 0.762, 0.051, 0.776, 0.754, + 0.087, 0.778, 0.746, 0.111, 0.780, 0.737, 0.131, 0.782, 0.728, 0.146, + 0.783, 0.719, 0.159, 0.784, 0.710, 0.171, 0.785, 0.700, 0.180, 0.786, + 0.690, 0.189, 0.786, 0.680, 0.196, 0.787, 0.669, 0.202, 0.787, 0.658, + 0.208, 0.786, 0.647, 0.213, 0.786, 0.635, 0.217, 0.785, 0.623, 0.221, + 0.784, 0.610, 0.224, 0.782, 0.597, 0.227, 0.781, 0.584, 0.230, 0.779, + 0.570, 0.232, 0.777, 0.556, 0.234, 0.775, 0.542, 0.236, 0.772, 0.527, + 0.238, 0.769, 0.512, 0.240, 0.766, 0.497, 0.242, 0.763, 0.481, 0.244, + 0.760, 0.465, 0.246, 0.756, 0.448, 0.248, 0.752, 0.432, 0.250, 0.748, + 0.415, 0.252, 0.744, 0.397, 0.254, 0.739, 0.379, 0.256, 0.735, 0.361, + 0.259, 0.730, 0.343, 0.261, 0.725, 0.324, 0.264, 0.720, 0.304, 0.266, + 0.715, 0.284, 0.269, 0.709, 0.263, 0.271, 0.704, 0.242, 0.274, 0.698, + 0.220, 0.277, 0.692, 0.196, 0.280, 0.686, 0.170, 0.283, 0.680, 0.143, + 0.000, 0.676, 0.937, 0.000, 0.682, 0.928, 0.000, 0.688, 0.920, 0.000, + 0.694, 0.913, 0.000, 0.699, 0.905, 0.000, 0.704, 0.897, 0.000, 0.710, + 0.890, 0.000, 0.715, 0.883, 0.000, 0.720, 0.876, 0.000, 0.724, 0.869, + 0.000, 0.729, 0.862, 0.000, 0.734, 0.855, 0.000, 0.738, 0.848, 0.000, + 0.743, 0.841, 0.000, 0.747, 0.834, 0.000, 0.751, 0.827, 0.000, 0.755, + 0.820, 0.000, 0.759, 0.813, 0.000, 0.762, 0.806, 0.003, 0.766, 0.799, + 0.066, 0.769, 0.792, 0.104, 0.772, 0.784, 0.131, 0.775, 0.777, 0.152, + 0.777, 0.769, 0.170, 0.780, 0.761, 0.185, 0.782, 0.753, 0.198, 0.784, + 0.744, 0.209, 0.786, 0.736, 0.219, 0.787, 0.727, 0.228, 0.788, 0.717, + 0.236, 0.789, 0.708, 0.243, 0.790, 0.698, 0.249, 0.791, 0.688, 0.254, + 0.791, 0.677, 0.259, 0.791, 0.666, 0.263, 0.791, 0.654, 0.266, 0.790, + 0.643, 0.269, 0.789, 0.631, 0.272, 0.788, 0.618, 0.274, 0.787, 0.605, + 0.276, 0.785, 0.592, 0.278, 0.783, 0.578, 0.279, 0.781, 0.564, 0.280, + 0.779, 0.549, 0.282, 0.776, 0.535, 0.283, 0.773, 0.519, 0.284, 0.770, + 0.504, 0.285, 0.767, 0.488, 0.286, 0.763, 0.472, 0.287, 0.759, 0.455, + 0.288, 0.756, 0.438, 0.289, 0.751, 0.421, 0.291, 0.747, 0.403, 0.292, + 0.742, 0.385, 0.293, 0.738, 0.367, 0.295, 0.733, 0.348, 0.296, 0.728, + 0.329, 0.298, 0.723, 0.310, 0.300, 0.717, 0.290, 0.302, 0.712, 0.269, + 0.304, 0.706, 0.247, 0.306, 0.700, 0.225, 0.308, 0.694, 0.201, 0.310, + 0.688, 0.176, 0.312, 0.682, 0.149, 0.000, 0.678, 0.939, 0.000, 0.683, + 0.931, 0.000, 0.689, 0.923, 0.000, 0.695, 0.916, 0.000, 0.701, 0.908, + 0.000, 0.706, 0.901, 0.000, 0.711, 0.894, 0.000, 0.717, 0.887, 0.000, + 0.722, 0.880, 0.000, 0.727, 0.873, 0.000, 0.732, 0.866, 0.000, 0.736, + 0.859, 0.000, 0.741, 0.853, 0.000, 0.745, 0.846, 0.000, 0.750, 0.839, + 0.000, 0.754, 0.833, 0.035, 0.758, 0.826, 0.091, 0.762, 0.819, 0.126, + 0.765, 0.812, 0.153, 0.769, 0.805, 0.174, 0.772, 0.798, 0.193, 0.775, + 0.791, 0.209, 0.778, 0.783, 0.223, 0.781, 0.776, 0.236, 0.784, 0.768, + 0.247, 0.786, 0.760, 0.257, 0.788, 0.752, 0.266, 0.790, 0.743, 0.273, + 0.791, 0.734, 0.280, 0.793, 0.725, 0.287, 0.794, 0.715, 0.292, 0.794, + 0.706, 0.297, 0.795, 0.695, 0.301, 0.795, 0.685, 0.305, 0.795, 0.674, + 0.308, 0.795, 0.662, 0.310, 0.794, 0.651, 0.312, 0.794, 0.638, 0.314, + 0.792, 0.626, 0.316, 0.791, 0.613, 0.317, 0.789, 0.599, 0.318, 0.787, + 0.586, 0.319, 0.785, 0.571, 0.320, 0.783, 0.557, 0.320, 0.780, 0.542, + 0.321, 0.777, 0.527, 0.321, 0.774, 0.511, 0.322, 0.770, 0.495, 0.322, + 0.767, 0.478, 0.323, 0.763, 0.462, 0.323, 0.759, 0.445, 0.324, 0.755, + 0.427, 0.325, 0.750, 0.410, 0.325, 0.745, 0.391, 0.326, 0.741, 0.373, + 0.327, 0.736, 0.354, 0.328, 0.730, 0.335, 0.329, 0.725, 0.315, 0.330, + 0.720, 0.295, 0.331, 0.714, 0.274, 0.333, 0.708, 0.253, 0.334, 0.702, + 0.230, 0.336, 0.696, 0.207, 0.337, 0.690, 0.182, 0.339, 0.684, 0.154, + 0.000, 0.679, 0.942, 0.000, 0.685, 0.934, 0.000, 0.691, 0.927, 0.000, + 0.696, 0.919, 0.000, 0.702, 0.912, 0.000, 0.708, 0.905, 0.000, 0.713, + 0.898, 0.000, 0.718, 0.891, 0.000, 0.724, 0.884, 0.000, 0.729, 0.877, + 0.000, 0.734, 0.871, 0.000, 0.739, 0.864, 0.000, 0.743, 0.857, 0.035, + 0.748, 0.851, 0.096, 0.752, 0.844, 0.133, 0.757, 0.838, 0.161, 0.761, + 0.831, 0.185, 0.765, 0.825, 0.205, 0.769, 0.818, 0.223, 0.772, 0.811, + 0.238, 0.776, 0.804, 0.252, 0.779, 0.797, 0.265, 0.782, 0.790, 0.276, + 0.785, 0.783, 0.286, 0.788, 0.775, 0.296, 0.790, 0.767, 0.304, 0.792, + 0.759, 0.311, 0.794, 0.751, 0.318, 0.796, 0.742, 0.324, 0.797, 0.733, + 0.329, 0.798, 0.723, 0.334, 0.799, 0.714, 0.338, 0.799, 0.703, 0.341, + 0.800, 0.693, 0.344, 0.800, 0.682, 0.347, 0.799, 0.670, 0.349, 0.799, + 0.659, 0.351, 0.798, 0.646, 0.352, 0.797, 0.634, 0.353, 0.795, 0.621, + 0.354, 0.794, 0.607, 0.354, 0.792, 0.593, 0.355, 0.789, 0.579, 0.355, + 0.787, 0.564, 0.355, 0.784, 0.549, 0.355, 0.781, 0.534, 0.355, 0.778, + 0.518, 0.355, 0.774, 0.502, 0.355, 0.770, 0.485, 0.355, 0.766, 0.468, + 0.355, 0.762, 0.451, 0.355, 0.758, 0.434, 0.355, 0.753, 0.416, 0.356, + 0.748, 0.397, 0.356, 0.743, 0.379, 0.356, 0.738, 0.360, 0.357, 0.733, + 0.340, 0.357, 0.728, 0.321, 0.358, 0.722, 0.300, 0.359, 0.716, 0.279, + 0.360, 0.710, 0.258, 0.361, 0.704, 0.235, 0.361, 0.698, 0.212, 0.362, + 0.692, 0.187, 0.363, 0.686, 0.160, 0.000, 0.680, 0.945, 0.000, 0.686, + 0.937, 0.000, 0.692, 0.930, 0.000, 0.698, 0.922, 0.000, 0.703, 0.915, + 0.000, 0.709, 0.908, 0.000, 0.715, 0.901, 0.000, 0.720, 0.894, 0.000, + 0.726, 0.888, 0.000, 0.731, 0.881, 0.007, 0.736, 0.875, 0.084, 0.741, + 0.869, 0.127, 0.746, 0.862, 0.159, 0.751, 0.856, 0.185, 0.755, 0.850, + 0.208, 0.760, 0.843, 0.227, 0.764, 0.837, 0.245, 0.768, 0.830, 0.260, + 0.772, 0.824, 0.275, 0.776, 0.817, 0.288, 0.779, 0.811, 0.300, 0.783, + 0.804, 0.310, 0.786, 0.797, 0.320, 0.789, 0.789, 0.329, 0.792, 0.782, + 0.337, 0.794, 0.774, 0.345, 0.796, 0.766, 0.351, 0.798, 0.758, 0.357, + 0.800, 0.749, 0.363, 0.801, 0.740, 0.367, 0.803, 0.731, 0.371, 0.803, + 0.721, 0.375, 0.804, 0.711, 0.378, 0.804, 0.701, 0.380, 0.804, 0.690, + 0.382, 0.804, 0.679, 0.384, 0.803, 0.667, 0.385, 0.802, 0.654, 0.386, + 0.801, 0.642, 0.386, 0.800, 0.629, 0.387, 0.798, 0.615, 0.387, 0.796, + 0.601, 0.387, 0.793, 0.587, 0.387, 0.791, 0.572, 0.387, 0.788, 0.557, + 0.386, 0.785, 0.541, 0.386, 0.781, 0.525, 0.385, 0.778, 0.509, 0.385, + 0.774, 0.492, 0.385, 0.770, 0.475, 0.384, 0.765, 0.457, 0.384, 0.761, + 0.440, 0.384, 0.756, 0.422, 0.384, 0.751, 0.403, 0.384, 0.746, 0.384, + 0.384, 0.741, 0.365, 0.384, 0.735, 0.346, 0.384, 0.730, 0.326, 0.384, + 0.724, 0.305, 0.384, 0.718, 0.284, 0.385, 0.712, 0.263, 0.385, 0.706, + 0.240, 0.386, 0.700, 0.217, 0.386, 0.694, 0.192, 0.387, 0.687, 0.165, + 0.000, 0.680, 0.948, 0.000, 0.687, 0.940, 0.000, 0.693, 0.933, 0.000, + 0.699, 0.925, 0.000, 0.705, 0.918, 0.000, 0.711, 0.912, 0.000, 0.716, + 0.905, 0.000, 0.722, 0.898, 0.050, 0.728, 0.892, 0.109, 0.733, 0.886, + 0.147, 0.738, 0.879, 0.177, 0.743, 0.873, 0.202, 0.748, 0.867, 0.224, + 0.753, 0.861, 0.243, 0.758, 0.855, 0.261, 0.763, 0.849, 0.277, 0.767, + 0.842, 0.292, 0.771, 0.836, 0.305, 0.775, 0.830, 0.318, 0.779, 0.823, + 0.329, 0.783, 0.817, 0.340, 0.787, 0.810, 0.350, 0.790, 0.803, 0.359, + 0.793, 0.796, 0.367, 0.796, 0.789, 0.374, 0.798, 0.782, 0.381, 0.801, + 0.774, 0.387, 0.803, 0.766, 0.393, 0.804, 0.757, 0.397, 0.806, 0.748, + 0.402, 0.807, 0.739, 0.405, 0.808, 0.729, 0.408, 0.809, 0.719, 0.411, + 0.809, 0.709, 0.413, 0.809, 0.698, 0.415, 0.808, 0.687, 0.416, 0.808, + 0.675, 0.417, 0.807, 0.663, 0.417, 0.806, 0.650, 0.417, 0.804, 0.637, + 0.418, 0.802, 0.623, 0.417, 0.800, 0.609, 0.417, 0.798, 0.594, 0.416, + 0.795, 0.579, 0.416, 0.792, 0.564, 0.415, 0.789, 0.548, 0.414, 0.785, + 0.532, 0.414, 0.781, 0.515, 0.413, 0.777, 0.499, 0.412, 0.773, 0.481, + 0.412, 0.769, 0.464, 0.411, 0.764, 0.446, 0.410, 0.759, 0.428, 0.410, + 0.754, 0.409, 0.409, 0.749, 0.390, 0.409, 0.743, 0.371, 0.409, 0.738, + 0.351, 0.409, 0.732, 0.331, 0.408, 0.726, 0.310, 0.408, 0.720, 0.289, + 0.408, 0.714, 0.268, 0.408, 0.708, 0.245, 0.409, 0.702, 0.222, 0.409, + 0.695, 0.197, 0.409, 0.689, 0.170, 0.000, 0.681, 0.950, 0.000, 0.688, + 0.943, 0.000, 0.694, 0.936, 0.000, 0.700, 0.929, 0.000, 0.706, 0.922, + 0.000, 0.712, 0.915, 0.074, 0.718, 0.908, 0.124, 0.724, 0.902, 0.159, + 0.730, 0.896, 0.188, 0.735, 0.890, 0.213, 0.740, 0.884, 0.235, 0.746, + 0.878, 0.255, 0.751, 0.872, 0.273, 0.756, 0.866, 0.289, 0.761, 0.860, + 0.305, 0.766, 0.854, 0.319, 0.770, 0.848, 0.332, 0.775, 0.842, 0.344, + 0.779, 0.836, 0.356, 0.783, 0.830, 0.366, 0.787, 0.823, 0.376, 0.790, + 0.817, 0.385, 0.794, 0.810, 0.394, 0.797, 0.803, 0.401, 0.800, 0.796, + 0.408, 0.802, 0.789, 0.414, 0.805, 0.781, 0.420, 0.807, 0.773, 0.425, + 0.809, 0.765, 0.430, 0.810, 0.756, 0.433, 0.812, 0.747, 0.437, 0.813, + 0.738, 0.440, 0.813, 0.728, 0.442, 0.814, 0.717, 0.444, 0.813, 0.706, + 0.445, 0.813, 0.695, 0.446, 0.812, 0.683, 0.446, 0.811, 0.671, 0.447, + 0.810, 0.658, 0.447, 0.809, 0.645, 0.446, 0.807, 0.631, 0.446, 0.804, + 0.617, 0.445, 0.802, 0.602, 0.444, 0.799, 0.587, 0.443, 0.796, 0.571, + 0.442, 0.792, 0.555, 0.441, 0.789, 0.539, 0.440, 0.785, 0.522, 0.439, + 0.781, 0.505, 0.438, 0.776, 0.488, 0.437, 0.772, 0.470, 0.436, 0.767, + 0.452, 0.435, 0.762, 0.433, 0.435, 0.757, 0.415, 0.434, 0.751, 0.396, + 0.433, 0.746, 0.376, 0.432, 0.740, 0.356, 0.432, 0.734, 0.336, 0.431, + 0.728, 0.315, 0.431, 0.722, 0.294, 0.431, 0.716, 0.272, 0.430, 0.710, + 0.250, 0.430, 0.703, 0.226, 0.430, 0.697, 0.201, 0.430, 0.690, 0.175, + 0.000, 0.682, 0.953, 0.000, 0.689, 0.946, 0.000, 0.695, 0.938, 0.002, + 0.701, 0.932, 0.086, 0.708, 0.925, 0.133, 0.714, 0.918, 0.167, 0.720, + 0.912, 0.196, 0.726, 0.906, 0.221, 0.731, 0.900, 0.243, 0.737, 0.894, + 0.263, 0.743, 0.888, 0.281, 0.748, 0.882, 0.298, 0.753, 0.876, 0.314, + 0.759, 0.870, 0.329, 0.764, 0.865, 0.342, 0.768, 0.859, 0.355, 0.773, + 0.853, 0.368, 0.778, 0.847, 0.379, 0.782, 0.842, 0.390, 0.786, 0.836, + 0.400, 0.790, 0.830, 0.409, 0.794, 0.823, 0.417, 0.798, 0.817, 0.425, + 0.801, 0.810, 0.433, 0.804, 0.803, 0.439, 0.807, 0.796, 0.445, 0.809, + 0.789, 0.451, 0.811, 0.781, 0.456, 0.813, 0.773, 0.460, 0.815, 0.764, + 0.463, 0.816, 0.755, 0.466, 0.817, 0.746, 0.469, 0.818, 0.736, 0.471, + 0.818, 0.725, 0.472, 0.818, 0.715, 0.473, 0.818, 0.703, 0.474, 0.817, + 0.691, 0.474, 0.816, 0.679, 0.474, 0.815, 0.666, 0.474, 0.813, 0.653, + 0.473, 0.811, 0.639, 0.473, 0.809, 0.624, 0.472, 0.806, 0.610, 0.471, + 0.803, 0.594, 0.469, 0.800, 0.579, 0.468, 0.796, 0.562, 0.467, 0.792, + 0.546, 0.466, 0.788, 0.529, 0.464, 0.784, 0.512, 0.463, 0.780, 0.494, + 0.462, 0.775, 0.476, 0.460, 0.770, 0.458, 0.459, 0.765, 0.439, 0.458, + 0.759, 0.420, 0.457, 0.754, 0.401, 0.456, 0.748, 0.381, 0.455, 0.742, + 0.361, 0.454, 0.736, 0.341, 0.453, 0.730, 0.320, 0.453, 0.724, 0.299, + 0.452, 0.718, 0.277, 0.452, 0.711, 0.254, 0.451, 0.705, 0.231, 0.451, + 0.698, 0.206, 0.450, 0.691, 0.179, 0.000, 0.683, 0.955, 0.013, 0.689, + 0.948, 0.092, 0.696, 0.941, 0.137, 0.702, 0.935, 0.171, 0.709, 0.928, + 0.200, 0.715, 0.922, 0.225, 0.721, 0.916, 0.247, 0.727, 0.909, 0.267, + 0.733, 0.904, 0.286, 0.739, 0.898, 0.303, 0.745, 0.892, 0.320, 0.750, + 0.886, 0.335, 0.756, 0.881, 0.350, 0.761, 0.875, 0.363, 0.766, 0.870, + 0.376, 0.771, 0.864, 0.388, 0.776, 0.859, 0.400, 0.781, 0.853, 0.411, + 0.785, 0.847, 0.421, 0.790, 0.842, 0.430, 0.794, 0.836, 0.439, 0.798, + 0.830, 0.448, 0.802, 0.824, 0.455, 0.805, 0.817, 0.462, 0.808, 0.810, + 0.469, 0.811, 0.804, 0.475, 0.814, 0.796, 0.480, 0.816, 0.789, 0.484, + 0.818, 0.781, 0.488, 0.820, 0.772, 0.492, 0.821, 0.763, 0.495, 0.822, + 0.754, 0.497, 0.823, 0.744, 0.499, 0.823, 0.734, 0.500, 0.823, 0.723, + 0.501, 0.823, 0.712, 0.501, 0.822, 0.700, 0.501, 0.821, 0.687, 0.501, + 0.819, 0.674, 0.500, 0.818, 0.661, 0.499, 0.815, 0.647, 0.498, 0.813, + 0.632, 0.497, 0.810, 0.617, 0.496, 0.807, 0.602, 0.494, 0.804, 0.586, + 0.493, 0.800, 0.569, 0.491, 0.796, 0.553, 0.490, 0.792, 0.536, 0.488, + 0.787, 0.518, 0.486, 0.783, 0.500, 0.485, 0.778, 0.482, 0.483, 0.773, + 0.463, 0.482, 0.767, 0.445, 0.480, 0.762, 0.425, 0.479, 0.756, 0.406, + 0.478, 0.750, 0.386, 0.477, 0.744, 0.366, 0.476, 0.738, 0.345, 0.475, + 0.732, 0.325, 0.474, 0.726, 0.303, 0.473, 0.719, 0.281, 0.472, 0.713, + 0.258, 0.471, 0.706, 0.235, 0.470, 0.699, 0.210, 0.469, 0.692, 0.184, + 0.095, 0.683, 0.958, 0.139, 0.690, 0.951, 0.173, 0.697, 0.944, 0.201, + 0.703, 0.938, 0.226, 0.710, 0.931, 0.249, 0.716, 0.925, 0.269, 0.723, + 0.919, 0.288, 0.729, 0.913, 0.306, 0.735, 0.907, 0.323, 0.741, 0.902, + 0.339, 0.747, 0.896, 0.354, 0.752, 0.891, 0.368, 0.758, 0.885, 0.382, + 0.764, 0.880, 0.394, 0.769, 0.875, 0.407, 0.774, 0.869, 0.418, 0.779, + 0.864, 0.429, 0.784, 0.859, 0.440, 0.789, 0.853, 0.450, 0.793, 0.848, + 0.459, 0.798, 0.842, 0.468, 0.802, 0.836, 0.476, 0.806, 0.830, 0.483, + 0.809, 0.824, 0.490, 0.812, 0.818, 0.496, 0.815, 0.811, 0.502, 0.818, + 0.804, 0.507, 0.821, 0.796, 0.512, 0.823, 0.789, 0.515, 0.825, 0.780, + 0.519, 0.826, 0.772, 0.521, 0.827, 0.762, 0.524, 0.828, 0.753, 0.525, + 0.828, 0.742, 0.526, 0.828, 0.732, 0.527, 0.828, 0.720, 0.527, 0.827, + 0.708, 0.527, 0.826, 0.696, 0.526, 0.824, 0.683, 0.525, 0.822, 0.669, + 0.524, 0.820, 0.655, 0.523, 0.817, 0.640, 0.522, 0.814, 0.625, 0.520, + 0.811, 0.609, 0.518, 0.808, 0.593, 0.516, 0.804, 0.576, 0.515, 0.800, + 0.559, 0.513, 0.795, 0.542, 0.511, 0.791, 0.524, 0.509, 0.786, 0.506, + 0.507, 0.781, 0.488, 0.505, 0.775, 0.469, 0.504, 0.770, 0.450, 0.502, + 0.764, 0.431, 0.500, 0.759, 0.411, 0.499, 0.753, 0.391, 0.497, 0.746, + 0.371, 0.496, 0.740, 0.350, 0.495, 0.734, 0.329, 0.494, 0.727, 0.307, + 0.492, 0.721, 0.285, 0.491, 0.714, 0.262, 0.490, 0.707, 0.239, 0.489, + 0.700, 0.214, 0.488, 0.693, 0.188, 0.172, 0.684, 0.961, 0.201, 0.691, + 0.954, 0.226, 0.698, 0.947, 0.248, 0.704, 0.941, 0.269, 0.711, 0.934, + 0.289, 0.717, 0.928, 0.307, 0.724, 0.922, 0.324, 0.730, 0.917, 0.340, + 0.736, 0.911, 0.356, 0.743, 0.906, 0.370, 0.749, 0.900, 0.384, 0.755, + 0.895, 0.398, 0.760, 0.890, 0.411, 0.766, 0.885, 0.423, 0.772, 0.880, + 0.435, 0.777, 0.874, 0.446, 0.782, 0.869, 0.457, 0.787, 0.864, 0.467, + 0.792, 0.859, 0.477, 0.797, 0.854, 0.486, 0.801, 0.848, 0.494, 0.806, + 0.843, 0.502, 0.810, 0.837, 0.510, 0.813, 0.831, 0.517, 0.817, 0.825, + 0.523, 0.820, 0.818, 0.528, 0.823, 0.811, 0.533, 0.825, 0.804, 0.538, + 0.828, 0.797, 0.542, 0.829, 0.788, 0.545, 0.831, 0.780, 0.547, 0.832, + 0.771, 0.549, 0.833, 0.761, 0.551, 0.833, 0.751, 0.552, 0.833, 0.740, + 0.552, 0.833, 0.729, 0.552, 0.832, 0.717, 0.551, 0.830, 0.704, 0.551, + 0.829, 0.691, 0.550, 0.827, 0.677, 0.548, 0.824, 0.663, 0.547, 0.822, + 0.648, 0.545, 0.819, 0.632, 0.543, 0.815, 0.617, 0.541, 0.812, 0.600, + 0.539, 0.808, 0.583, 0.537, 0.803, 0.566, 0.535, 0.799, 0.549, 0.533, + 0.794, 0.531, 0.531, 0.789, 0.512, 0.529, 0.784, 0.494, 0.527, 0.778, + 0.475, 0.525, 0.773, 0.455, 0.523, 0.767, 0.436, 0.521, 0.761, 0.416, + 0.519, 0.755, 0.396, 0.517, 0.748, 0.375, 0.516, 0.742, 0.354, 0.514, + 0.735, 0.333, 0.513, 0.729, 0.311, 0.511, 0.722, 0.289, 0.510, 0.715, + 0.266, 0.509, 0.708, 0.242, 0.507, 0.701, 0.218, 0.506, 0.694, 0.191, + 0.224, 0.684, 0.963, 0.247, 0.691, 0.956, 0.268, 0.698, 0.950, 0.287, + 0.705, 0.943, 0.305, 0.712, 0.937, 0.323, 0.719, 0.931, 0.339, 0.725, + 0.926, 0.355, 0.732, 0.920, 0.370, 0.738, 0.915, 0.385, 0.744, 0.909, + 0.399, 0.751, 0.904, 0.412, 0.757, 0.899, 0.425, 0.763, 0.894, 0.438, + 0.768, 0.889, 0.450, 0.774, 0.884, 0.461, 0.780, 0.879, 0.472, 0.785, + 0.875, 0.483, 0.790, 0.870, 0.493, 0.795, 0.865, 0.502, 0.800, 0.860, + 0.511, 0.805, 0.855, 0.520, 0.809, 0.849, 0.528, 0.814, 0.844, 0.535, + 0.818, 0.838, 0.542, 0.821, 0.832, 0.548, 0.824, 0.826, 0.554, 0.827, + 0.819, 0.559, 0.830, 0.812, 0.563, 0.832, 0.805, 0.567, 0.834, 0.797, + 0.570, 0.836, 0.788, 0.572, 0.837, 0.779, 0.574, 0.838, 0.770, 0.575, + 0.838, 0.760, 0.576, 0.838, 0.749, 0.576, 0.838, 0.737, 0.576, 0.837, + 0.725, 0.575, 0.835, 0.713, 0.574, 0.834, 0.699, 0.573, 0.831, 0.685, + 0.571, 0.829, 0.671, 0.570, 0.826, 0.656, 0.568, 0.823, 0.640, 0.566, + 0.819, 0.624, 0.563, 0.815, 0.607, 0.561, 0.811, 0.590, 0.559, 0.807, + 0.573, 0.556, 0.802, 0.555, 0.554, 0.797, 0.537, 0.552, 0.792, 0.518, + 0.549, 0.786, 0.499, 0.547, 0.781, 0.480, 0.545, 0.775, 0.460, 0.543, + 0.769, 0.441, 0.541, 0.763, 0.420, 0.539, 0.756, 0.400, 0.537, 0.750, + 0.379, 0.535, 0.743, 0.358, 0.533, 0.737, 0.337, 0.531, 0.730, 0.315, + 0.530, 0.723, 0.293, 0.528, 0.716, 0.270, 0.527, 0.709, 0.246, 0.525, + 0.702, 0.221, 0.524, 0.694, 0.195, 0.265, 0.685, 0.965, 0.284, 0.692, + 0.959, 0.303, 0.699, 0.952, 0.320, 0.706, 0.946, 0.337, 0.713, 0.940, + 0.353, 0.720, 0.935, 0.369, 0.726, 0.929, 0.384, 0.733, 0.924, 0.398, + 0.739, 0.918, 0.412, 0.746, 0.913, 0.425, 0.752, 0.908, 0.438, 0.759, + 0.903, 0.451, 0.765, 0.899, 0.463, 0.771, 0.894, 0.475, 0.777, 0.889, + 0.486, 0.782, 0.884, 0.497, 0.788, 0.880, 0.507, 0.793, 0.875, 0.517, + 0.799, 0.870, 0.527, 0.804, 0.866, 0.536, 0.809, 0.861, 0.544, 0.813, + 0.856, 0.552, 0.818, 0.850, 0.560, 0.822, 0.845, 0.566, 0.826, 0.839, + 0.573, 0.829, 0.833, 0.578, 0.832, 0.827, 0.583, 0.835, 0.820, 0.587, + 0.837, 0.813, 0.591, 0.839, 0.805, 0.594, 0.841, 0.797, 0.596, 0.842, + 0.788, 0.598, 0.843, 0.778, 0.599, 0.843, 0.768, 0.600, 0.843, 0.758, + 0.600, 0.843, 0.746, 0.599, 0.842, 0.734, 0.599, 0.840, 0.721, 0.597, + 0.838, 0.708, 0.596, 0.836, 0.694, 0.594, 0.834, 0.679, 0.592, 0.831, + 0.663, 0.590, 0.827, 0.648, 0.587, 0.823, 0.631, 0.585, 0.819, 0.614, + 0.582, 0.815, 0.597, 0.580, 0.810, 0.579, 0.577, 0.805, 0.561, 0.575, + 0.800, 0.542, 0.572, 0.795, 0.524, 0.569, 0.789, 0.504, 0.567, 0.783, + 0.485, 0.565, 0.777, 0.465, 0.562, 0.771, 0.445, 0.560, 0.765, 0.425, + 0.558, 0.758, 0.404, 0.556, 0.752, 0.383, 0.554, 0.745, 0.362, 0.552, + 0.738, 0.341, 0.550, 0.731, 0.319, 0.548, 0.724, 0.296, 0.546, 0.717, + 0.273, 0.544, 0.709, 0.249, 0.542, 0.702, 0.224, 0.541, 0.695, 0.198, + 0.299, 0.685, 0.968, 0.317, 0.692, 0.961, 0.334, 0.699, 0.955, 0.350, + 0.706, 0.949, 0.366, 0.713, 0.943, 0.381, 0.720, 0.938, 0.395, 0.727, + 0.932, 0.410, 0.734, 0.927, 0.423, 0.741, 0.922, 0.437, 0.747, 0.917, + 0.450, 0.754, 0.912, 0.463, 0.760, 0.907, 0.475, 0.767, 0.903, 0.487, + 0.773, 0.898, 0.498, 0.779, 0.894, 0.509, 0.785, 0.889, 0.520, 0.791, + 0.885, 0.531, 0.796, 0.880, 0.540, 0.802, 0.876, 0.550, 0.807, 0.871, + 0.559, 0.812, 0.867, 0.568, 0.817, 0.862, 0.576, 0.822, 0.857, 0.583, + 0.826, 0.852, 0.590, 0.830, 0.847, 0.596, 0.834, 0.841, 0.602, 0.837, + 0.835, 0.607, 0.840, 0.828, 0.611, 0.843, 0.821, 0.615, 0.845, 0.814, + 0.618, 0.846, 0.805, 0.620, 0.848, 0.797, 0.622, 0.848, 0.787, 0.623, + 0.849, 0.777, 0.623, 0.849, 0.766, 0.623, 0.848, 0.755, 0.622, 0.847, + 0.743, 0.621, 0.845, 0.730, 0.620, 0.843, 0.716, 0.618, 0.841, 0.702, + 0.616, 0.838, 0.687, 0.613, 0.835, 0.671, 0.611, 0.831, 0.655, 0.608, + 0.827, 0.638, 0.606, 0.823, 0.621, 0.603, 0.818, 0.604, 0.600, 0.814, + 0.585, 0.597, 0.808, 0.567, 0.594, 0.803, 0.548, 0.592, 0.797, 0.529, + 0.589, 0.792, 0.510, 0.586, 0.785, 0.490, 0.584, 0.779, 0.470, 0.581, + 0.773, 0.450, 0.579, 0.766, 0.429, 0.576, 0.760, 0.408, 0.574, 0.753, + 0.387, 0.572, 0.746, 0.366, 0.569, 0.739, 0.344, 0.567, 0.732, 0.322, + 0.565, 0.725, 0.299, 0.563, 0.717, 0.276, 0.561, 0.710, 0.252, 0.559, + 0.703, 0.227, 0.557, 0.695, 0.201, 0.329, 0.685, 0.970, 0.346, 0.692, + 0.964, 0.362, 0.699, 0.958, 0.377, 0.707, 0.952, 0.392, 0.714, 0.946, + 0.406, 0.721, 0.941, 0.420, 0.728, 0.935, 0.434, 0.735, 0.930, 0.447, + 0.742, 0.925, 0.460, 0.749, 0.920, 0.473, 0.756, 0.916, 0.485, 0.762, + 0.911, 0.497, 0.769, 0.907, 0.509, 0.775, 0.903, 0.521, 0.781, 0.898, + 0.532, 0.788, 0.894, 0.542, 0.794, 0.890, 0.553, 0.799, 0.886, 0.563, + 0.805, 0.882, 0.572, 0.811, 0.877, 0.581, 0.816, 0.873, 0.590, 0.821, + 0.868, 0.598, 0.826, 0.864, 0.606, 0.830, 0.859, 0.613, 0.834, 0.854, + 0.619, 0.838, 0.848, 0.625, 0.842, 0.842, 0.630, 0.845, 0.836, 0.634, + 0.848, 0.829, 0.638, 0.850, 0.822, 0.641, 0.852, 0.814, 0.643, 0.853, + 0.805, 0.645, 0.854, 0.796, 0.645, 0.854, 0.786, 0.646, 0.854, 0.775, + 0.645, 0.853, 0.764, 0.645, 0.852, 0.751, 0.643, 0.851, 0.738, 0.642, + 0.848, 0.725, 0.639, 0.846, 0.710, 0.637, 0.843, 0.695, 0.635, 0.839, + 0.679, 0.632, 0.836, 0.662, 0.629, 0.831, 0.645, 0.626, 0.827, 0.628, + 0.623, 0.822, 0.610, 0.620, 0.817, 0.592, 0.617, 0.811, 0.573, 0.614, + 0.806, 0.554, 0.611, 0.800, 0.534, 0.608, 0.794, 0.515, 0.605, 0.788, + 0.495, 0.602, 0.781, 0.474, 0.599, 0.775, 0.454, 0.597, 0.768, 0.433, + 0.594, 0.761, 0.412, 0.592, 0.754, 0.391, 0.589, 0.747, 0.369, 0.587, + 0.740, 0.347, 0.584, 0.733, 0.325, 0.582, 0.725, 0.302, 0.580, 0.718, + 0.279, 0.577, 0.710, 0.255, 0.575, 0.703, 0.230, 0.573, 0.695, 0.204, + 0.357, 0.685, 0.972, 0.372, 0.692, 0.966, 0.387, 0.700, 0.960, 0.401, + 0.707, 0.954, 0.416, 0.714, 0.949, 0.429, 0.722, 0.943, 0.443, 0.729, + 0.938, 0.456, 0.736, 0.933, 0.469, 0.743, 0.929, 0.482, 0.750, 0.924, + 0.494, 0.757, 0.919, 0.507, 0.764, 0.915, 0.519, 0.771, 0.911, 0.530, + 0.777, 0.907, 0.542, 0.784, 0.903, 0.553, 0.790, 0.899, 0.563, 0.796, + 0.895, 0.574, 0.802, 0.891, 0.584, 0.808, 0.887, 0.593, 0.814, 0.883, + 0.603, 0.820, 0.879, 0.611, 0.825, 0.875, 0.620, 0.830, 0.870, 0.627, + 0.835, 0.866, 0.635, 0.839, 0.861, 0.641, 0.843, 0.856, 0.647, 0.847, + 0.850, 0.652, 0.850, 0.844, 0.657, 0.853, 0.838, 0.660, 0.855, 0.831, + 0.663, 0.857, 0.823, 0.666, 0.859, 0.814, 0.667, 0.859, 0.805, 0.668, + 0.860, 0.795, 0.668, 0.860, 0.784, 0.667, 0.859, 0.773, 0.666, 0.858, + 0.760, 0.665, 0.856, 0.747, 0.663, 0.853, 0.733, 0.661, 0.851, 0.718, + 0.658, 0.847, 0.703, 0.655, 0.844, 0.687, 0.652, 0.840, 0.670, 0.649, + 0.835, 0.652, 0.646, 0.830, 0.635, 0.642, 0.825, 0.616, 0.639, 0.820, + 0.598, 0.636, 0.814, 0.579, 0.633, 0.808, 0.559, 0.629, 0.802, 0.539, + 0.626, 0.796, 0.519, 0.623, 0.790, 0.499, 0.620, 0.783, 0.479, 0.617, + 0.776, 0.458, 0.614, 0.769, 0.437, 0.611, 0.762, 0.416, 0.609, 0.755, + 0.394, 0.606, 0.748, 0.372, 0.603, 0.740, 0.350, 0.601, 0.733, 0.328, + 0.598, 0.726, 0.305, 0.596, 0.718, 0.282, 0.593, 0.710, 0.257, 0.591, + 0.703, 0.232, 0.589, 0.695, 0.206, 0.381, 0.684, 0.974, 0.396, 0.692, + 0.968, 0.410, 0.700, 0.962, 0.424, 0.707, 0.957, 0.438, 0.715, 0.951, + 0.451, 0.722, 0.946, 0.464, 0.729, 0.941, 0.477, 0.737, 0.936, 0.490, + 0.744, 0.932, 0.503, 0.751, 0.927, 0.515, 0.758, 0.923, 0.527, 0.765, + 0.919, 0.539, 0.772, 0.915, 0.550, 0.779, 0.911, 0.562, 0.786, 0.907, + 0.573, 0.792, 0.903, 0.584, 0.799, 0.900, 0.594, 0.805, 0.896, 0.604, + 0.811, 0.892, 0.614, 0.817, 0.889, 0.623, 0.823, 0.885, 0.632, 0.829, + 0.881, 0.641, 0.834, 0.877, 0.649, 0.839, 0.873, 0.656, 0.844, 0.868, + 0.663, 0.848, 0.863, 0.669, 0.852, 0.858, 0.674, 0.855, 0.852, 0.679, + 0.858, 0.846, 0.682, 0.861, 0.839, 0.685, 0.863, 0.832, 0.688, 0.864, + 0.823, 0.689, 0.865, 0.814, 0.690, 0.865, 0.804, 0.690, 0.865, 0.794, + 0.689, 0.864, 0.782, 0.688, 0.863, 0.769, 0.686, 0.861, 0.756, 0.684, + 0.858, 0.742, 0.681, 0.855, 0.726, 0.678, 0.852, 0.711, 0.675, 0.848, + 0.694, 0.672, 0.844, 0.677, 0.668, 0.839, 0.659, 0.665, 0.834, 0.641, + 0.662, 0.829, 0.622, 0.658, 0.823, 0.603, 0.655, 0.817, 0.584, 0.651, + 0.811, 0.564, 0.648, 0.805, 0.544, 0.644, 0.798, 0.524, 0.641, 0.791, + 0.503, 0.638, 0.785, 0.483, 0.635, 0.778, 0.462, 0.631, 0.770, 0.440, + 0.628, 0.763, 0.419, 0.625, 0.756, 0.397, 0.623, 0.748, 0.375, 0.620, + 0.741, 0.353, 0.617, 0.733, 0.330, 0.614, 0.726, 0.307, 0.612, 0.718, + 0.284, 0.609, 0.710, 0.260, 0.606, 0.702, 0.235, 0.604, 0.694, 0.208, + 0.404, 0.684, 0.977, 0.418, 0.692, 0.971, 0.432, 0.699, 0.965, 0.445, + 0.707, 0.959, 0.458, 0.715, 0.954, 0.472, 0.722, 0.949, 0.484, 0.730, + 0.944, 0.497, 0.737, 0.939, 0.510, 0.745, 0.935, 0.522, 0.752, 0.931, + 0.534, 0.759, 0.926, 0.546, 0.767, 0.922, 0.558, 0.774, 0.919, 0.569, + 0.781, 0.915, 0.581, 0.788, 0.911, 0.592, 0.794, 0.908, 0.603, 0.801, + 0.904, 0.613, 0.808, 0.901, 0.624, 0.814, 0.897, 0.633, 0.820, 0.894, + 0.643, 0.826, 0.891, 0.652, 0.832, 0.887, 0.661, 0.838, 0.883, 0.669, + 0.843, 0.879, 0.677, 0.848, 0.875, 0.684, 0.853, 0.871, 0.690, 0.857, + 0.866, 0.695, 0.860, 0.860, 0.700, 0.864, 0.855, 0.704, 0.866, 0.848, + 0.707, 0.869, 0.841, 0.709, 0.870, 0.833, 0.711, 0.871, 0.824, 0.711, + 0.871, 0.814, 0.711, 0.871, 0.803, 0.710, 0.870, 0.791, 0.709, 0.868, + 0.778, 0.707, 0.866, 0.765, 0.704, 0.864, 0.750, 0.701, 0.860, 0.735, + 0.698, 0.857, 0.718, 0.695, 0.852, 0.702, 0.691, 0.848, 0.684, 0.688, + 0.843, 0.666, 0.684, 0.837, 0.647, 0.680, 0.832, 0.628, 0.676, 0.826, + 0.609, 0.673, 0.820, 0.589, 0.669, 0.813, 0.569, 0.665, 0.807, 0.549, + 0.662, 0.800, 0.528, 0.658, 0.793, 0.507, 0.655, 0.786, 0.486, 0.651, + 0.779, 0.465, 0.648, 0.771, 0.444, 0.645, 0.764, 0.422, 0.642, 0.756, + 0.400, 0.639, 0.749, 0.378, 0.636, 0.741, 0.356, 0.633, 0.733, 0.333, + 0.630, 0.726, 0.310, 0.627, 0.718, 0.286, 0.624, 0.710, 0.262, 0.621, + 0.702, 0.237, 0.619, 0.694, 0.210, 0.425, 0.683, 0.979, 0.439, 0.691, + 0.973, 0.452, 0.699, 0.967, 0.465, 0.707, 0.962, 0.478, 0.715, 0.956, + 0.491, 0.722, 0.951, 0.503, 0.730, 0.947, 0.516, 0.738, 0.942, 0.528, + 0.745, 0.938, 0.540, 0.753, 0.934, 0.552, 0.760, 0.930, 0.564, 0.768, + 0.926, 0.576, 0.775, 0.922, 0.588, 0.782, 0.919, 0.599, 0.789, 0.915, + 0.610, 0.797, 0.912, 0.621, 0.803, 0.909, 0.632, 0.810, 0.906, 0.642, + 0.817, 0.902, 0.652, 0.823, 0.899, 0.662, 0.830, 0.896, 0.671, 0.836, + 0.893, 0.680, 0.842, 0.890, 0.689, 0.847, 0.886, 0.697, 0.853, 0.882, + 0.704, 0.857, 0.878, 0.710, 0.862, 0.874, 0.716, 0.866, 0.869, 0.721, + 0.869, 0.863, 0.725, 0.872, 0.857, 0.729, 0.874, 0.850, 0.731, 0.876, + 0.842, 0.732, 0.877, 0.833, 0.733, 0.877, 0.823, 0.732, 0.877, 0.812, + 0.731, 0.876, 0.800, 0.729, 0.874, 0.787, 0.727, 0.872, 0.773, 0.724, + 0.869, 0.759, 0.721, 0.865, 0.743, 0.718, 0.861, 0.726, 0.714, 0.857, + 0.709, 0.710, 0.852, 0.691, 0.706, 0.846, 0.672, 0.702, 0.841, 0.653, + 0.698, 0.835, 0.634, 0.694, 0.828, 0.614, 0.690, 0.822, 0.594, 0.686, + 0.815, 0.574, 0.683, 0.808, 0.553, 0.679, 0.801, 0.532, 0.675, 0.794, + 0.511, 0.672, 0.787, 0.490, 0.668, 0.779, 0.468, 0.665, 0.772, 0.446, + 0.661, 0.764, 0.425, 0.658, 0.757, 0.403, 0.654, 0.749, 0.380, 0.651, + 0.741, 0.358, 0.648, 0.733, 0.335, 0.645, 0.725, 0.312, 0.642, 0.717, + 0.288, 0.639, 0.709, 0.264, 0.636, 0.701, 0.238, 0.633, 0.693, 0.212, + 0.445, 0.682, 0.981, 0.458, 0.691, 0.975, 0.471, 0.699, 0.969, 0.484, + 0.707, 0.964, 0.496, 0.715, 0.959, 0.509, 0.722, 0.954, 0.521, 0.730, + 0.949, 0.534, 0.738, 0.945, 0.546, 0.746, 0.941, 0.558, 0.753, 0.937, + 0.570, 0.761, 0.933, 0.582, 0.769, 0.929, 0.593, 0.776, 0.926, 0.605, + 0.784, 0.922, 0.616, 0.791, 0.919, 0.628, 0.798, 0.916, 0.639, 0.806, + 0.913, 0.649, 0.813, 0.910, 0.660, 0.820, 0.907, 0.670, 0.826, 0.904, + 0.680, 0.833, 0.902, 0.690, 0.839, 0.899, 0.699, 0.846, 0.896, 0.708, + 0.851, 0.893, 0.716, 0.857, 0.889, 0.724, 0.862, 0.885, 0.731, 0.867, + 0.881, 0.737, 0.871, 0.877, 0.742, 0.875, 0.872, 0.746, 0.878, 0.866, + 0.750, 0.880, 0.859, 0.752, 0.882, 0.851, 0.753, 0.883, 0.843, 0.754, + 0.883, 0.833, 0.753, 0.883, 0.822, 0.752, 0.882, 0.810, 0.750, 0.880, + 0.797, 0.747, 0.877, 0.782, 0.744, 0.874, 0.767, 0.740, 0.870, 0.751, + 0.737, 0.866, 0.734, 0.733, 0.861, 0.716, 0.729, 0.855, 0.697, 0.724, + 0.850, 0.678, 0.720, 0.844, 0.659, 0.716, 0.837, 0.639, 0.712, 0.831, + 0.619, 0.708, 0.824, 0.598, 0.704, 0.817, 0.578, 0.699, 0.810, 0.557, + 0.696, 0.803, 0.535, 0.692, 0.795, 0.514, 0.688, 0.788, 0.493, 0.684, + 0.780, 0.471, 0.680, 0.772, 0.449, 0.677, 0.765, 0.427, 0.673, 0.757, + 0.405, 0.670, 0.749, 0.382, 0.666, 0.741, 0.360, 0.663, 0.733, 0.337, + 0.660, 0.725, 0.313, 0.657, 0.716, 0.289, 0.653, 0.708, 0.265, 0.650, + 0.700, 0.240, 0.647, 0.692, 0.213, 0.464, 0.681, 0.982, 0.476, 0.690, + 0.977, 0.489, 0.698, 0.971, 0.501, 0.706, 0.966, 0.514, 0.714, 0.961, + 0.526, 0.722, 0.956, 0.538, 0.730, 0.952, 0.550, 0.738, 0.947, 0.562, + 0.746, 0.943, 0.574, 0.754, 0.939, 0.586, 0.762, 0.936, 0.598, 0.769, + 0.932, 0.610, 0.777, 0.929, 0.621, 0.785, 0.926, 0.633, 0.792, 0.923, + 0.644, 0.800, 0.920, 0.655, 0.807, 0.917, 0.666, 0.815, 0.915, 0.677, + 0.822, 0.912, 0.688, 0.829, 0.909, 0.698, 0.836, 0.907, 0.708, 0.843, + 0.904, 0.717, 0.849, 0.902, 0.727, 0.855, 0.899, 0.735, 0.861, 0.896, + 0.743, 0.867, 0.893, 0.750, 0.872, 0.889, 0.757, 0.877, 0.885, 0.762, + 0.881, 0.880, 0.767, 0.884, 0.875, 0.770, 0.887, 0.868, 0.773, 0.888, + 0.861, 0.774, 0.889, 0.852, 0.774, 0.890, 0.842, 0.774, 0.889, 0.831, + 0.772, 0.888, 0.819, 0.770, 0.885, 0.806, 0.767, 0.883, 0.791, 0.763, + 0.879, 0.775, 0.759, 0.875, 0.759, 0.755, 0.870, 0.741, 0.751, 0.865, + 0.723, 0.747, 0.859, 0.704, 0.742, 0.853, 0.684, 0.738, 0.847, 0.664, + 0.733, 0.840, 0.644, 0.729, 0.833, 0.623, 0.724, 0.826, 0.603, 0.720, + 0.819, 0.581, 0.716, 0.811, 0.560, 0.712, 0.804, 0.539, 0.708, 0.796, + 0.517, 0.704, 0.788, 0.495, 0.700, 0.780, 0.473, 0.696, 0.772, 0.451, + 0.692, 0.764, 0.429, 0.688, 0.756, 0.407, 0.685, 0.748, 0.384, 0.681, + 0.740, 0.361, 0.678, 0.732, 0.338, 0.674, 0.724, 0.315, 0.671, 0.715, + 0.291, 0.667, 0.707, 0.266, 0.664, 0.699, 0.241, 0.661, 0.691, 0.214, + 0.481, 0.680, 0.984, 0.494, 0.689, 0.978, 0.506, 0.697, 0.973, 0.518, + 0.705, 0.968, 0.530, 0.713, 0.963, 0.542, 0.722, 0.958, 0.554, 0.730, + 0.954, 0.566, 0.738, 0.950, 0.578, 0.746, 0.946, 0.590, 0.754, 0.942, + 0.602, 0.762, 0.939, 0.614, 0.770, 0.935, 0.626, 0.778, 0.932, 0.637, + 0.786, 0.929, 0.649, 0.794, 0.926, 0.660, 0.801, 0.924, 0.671, 0.809, + 0.921, 0.683, 0.817, 0.919, 0.694, 0.824, 0.916, 0.704, 0.832, 0.914, + 0.715, 0.839, 0.912, 0.725, 0.846, 0.910, 0.735, 0.853, 0.908, 0.744, + 0.859, 0.905, 0.753, 0.866, 0.903, 0.762, 0.872, 0.900, 0.770, 0.877, + 0.897, 0.776, 0.882, 0.893, 0.782, 0.886, 0.889, 0.787, 0.890, 0.884, + 0.791, 0.893, 0.878, 0.794, 0.895, 0.871, 0.795, 0.896, 0.862, 0.795, + 0.896, 0.852, 0.794, 0.895, 0.841, 0.792, 0.894, 0.829, 0.789, 0.891, + 0.815, 0.786, 0.888, 0.800, 0.782, 0.884, 0.783, 0.778, 0.879, 0.766, + 0.774, 0.874, 0.748, 0.769, 0.868, 0.729, 0.764, 0.862, 0.710, 0.760, + 0.856, 0.690, 0.755, 0.849, 0.669, 0.750, 0.842, 0.649, 0.745, 0.835, + 0.628, 0.741, 0.827, 0.606, 0.736, 0.820, 0.585, 0.732, 0.812, 0.563, + 0.728, 0.804, 0.542, 0.723, 0.796, 0.520, 0.719, 0.788, 0.498, 0.715, + 0.780, 0.475, 0.711, 0.772, 0.453, 0.707, 0.764, 0.431, 0.703, 0.756, + 0.408, 0.699, 0.748, 0.386, 0.696, 0.739, 0.363, 0.692, 0.731, 0.339, + 0.688, 0.723, 0.316, 0.685, 0.714, 0.292, 0.681, 0.706, 0.267, 0.678, + 0.697, 0.242, 0.674, 0.689, 0.215, 0.498, 0.679, 0.986, 0.510, 0.687, + 0.980, 0.522, 0.696, 0.975, 0.534, 0.704, 0.970, 0.546, 0.712, 0.965, + 0.558, 0.721, 0.961, 0.570, 0.729, 0.956, 0.581, 0.737, 0.952, 0.593, + 0.746, 0.948, 0.605, 0.754, 0.945, 0.617, 0.762, 0.941, 0.629, 0.770, + 0.938, 0.640, 0.778, 0.935, 0.652, 0.786, 0.932, 0.664, 0.794, 0.930, + 0.675, 0.802, 0.927, 0.687, 0.810, 0.925, 0.698, 0.818, 0.923, 0.709, + 0.826, 0.921, 0.720, 0.834, 0.919, 0.731, 0.841, 0.917, 0.742, 0.849, + 0.915, 0.752, 0.856, 0.913, 0.762, 0.863, 0.911, 0.771, 0.870, 0.909, + 0.780, 0.876, 0.907, 0.788, 0.882, 0.904, 0.796, 0.887, 0.901, 0.802, + 0.892, 0.897, 0.807, 0.896, 0.893, 0.811, 0.899, 0.887, 0.814, 0.902, + 0.880, 0.815, 0.903, 0.872, 0.815, 0.903, 0.862, 0.814, 0.902, 0.851, + 0.812, 0.900, 0.838, 0.809, 0.897, 0.824, 0.805, 0.893, 0.808, 0.801, + 0.889, 0.791, 0.796, 0.884, 0.774, 0.791, 0.878, 0.755, 0.786, 0.872, + 0.735, 0.781, 0.865, 0.715, 0.776, 0.858, 0.695, 0.771, 0.851, 0.674, + 0.767, 0.844, 0.653, 0.762, 0.836, 0.631, 0.757, 0.829, 0.610, 0.752, + 0.821, 0.588, 0.748, 0.813, 0.566, 0.743, 0.805, 0.544, 0.739, 0.796, + 0.522, 0.734, 0.788, 0.500, 0.730, 0.780, 0.477, 0.726, 0.772, 0.455, + 0.722, 0.763, 0.432, 0.718, 0.755, 0.410, 0.714, 0.746, 0.387, 0.710, + 0.738, 0.364, 0.706, 0.730, 0.340, 0.702, 0.721, 0.317, 0.698, 0.713, + 0.293, 0.694, 0.704, 0.268, 0.691, 0.696, 0.243, 0.687, 0.687, 0.216, + 0.513, 0.677, 0.987, 0.525, 0.686, 0.982, 0.537, 0.694, 0.977, 0.549, + 0.703, 0.972, 0.561, 0.711, 0.967, 0.572, 0.720, 0.962, 0.584, 0.728, + 0.958, 0.596, 0.737, 0.954, 0.608, 0.745, 0.951, 0.619, 0.753, 0.947, + 0.631, 0.762, 0.944, 0.643, 0.770, 0.941, 0.655, 0.778, 0.938, 0.666, + 0.787, 0.935, 0.678, 0.795, 0.933, 0.689, 0.803, 0.930, 0.701, 0.811, + 0.928, 0.713, 0.820, 0.926, 0.724, 0.828, 0.925, 0.735, 0.836, 0.923, + 0.746, 0.844, 0.921, 0.757, 0.852, 0.920, 0.768, 0.859, 0.918, 0.778, + 0.867, 0.917, 0.788, 0.874, 0.915, 0.797, 0.881, 0.913, 0.806, 0.887, + 0.911, 0.814, 0.893, 0.909, 0.821, 0.898, 0.906, 0.827, 0.902, 0.902, + 0.831, 0.906, 0.897, 0.834, 0.908, 0.890, 0.836, 0.910, 0.882, 0.836, + 0.910, 0.873, 0.834, 0.909, 0.861, 0.832, 0.906, 0.848, 0.828, 0.903, + 0.833, 0.824, 0.899, 0.817, 0.819, 0.894, 0.799, 0.814, 0.888, 0.781, + 0.809, 0.882, 0.761, 0.804, 0.875, 0.741, 0.798, 0.868, 0.720, 0.793, + 0.861, 0.699, 0.788, 0.853, 0.678, 0.783, 0.845, 0.656, 0.777, 0.837, + 0.635, 0.772, 0.829, 0.613, 0.768, 0.821, 0.590, 0.763, 0.813, 0.568, + 0.758, 0.804, 0.546, 0.753, 0.796, 0.524, 0.749, 0.788, 0.501, 0.744, + 0.779, 0.479, 0.740, 0.771, 0.456, 0.736, 0.762, 0.433, 0.731, 0.754, + 0.411, 0.727, 0.745, 0.388, 0.723, 0.736, 0.365, 0.719, 0.728, 0.341, + 0.715, 0.719, 0.317, 0.711, 0.711, 0.293, 0.707, 0.702, 0.268, 0.704, + 0.694, 0.243, 0.700, 0.685, 0.216, 0.528, 0.675, 0.989, 0.540, 0.684, + 0.983, 0.551, 0.693, 0.978, 0.563, 0.701, 0.973, 0.575, 0.710, 0.969, + 0.586, 0.718, 0.964, 0.598, 0.727, 0.960, 0.610, 0.736, 0.956, 0.621, + 0.744, 0.953, 0.633, 0.753, 0.949, 0.645, 0.761, 0.946, 0.656, 0.770, + 0.943, 0.668, 0.778, 0.940, 0.680, 0.787, 0.938, 0.691, 0.795, 0.936, + 0.703, 0.804, 0.933, 0.715, 0.812, 0.932, 0.726, 0.821, 0.930, 0.738, + 0.829, 0.928, 0.749, 0.837, 0.927, 0.761, 0.846, 0.926, 0.772, 0.854, + 0.924, 0.783, 0.862, 0.923, 0.794, 0.870, 0.922, 0.804, 0.877, 0.921, + 0.814, 0.885, 0.920, 0.824, 0.892, 0.918, 0.832, 0.898, 0.917, 0.840, + 0.904, 0.914, 0.846, 0.909, 0.911, 0.851, 0.913, 0.906, 0.855, 0.915, + 0.901, 0.856, 0.917, 0.893, 0.856, 0.917, 0.883, 0.854, 0.915, 0.871, + 0.851, 0.913, 0.858, 0.847, 0.909, 0.842, 0.842, 0.904, 0.825, 0.837, + 0.898, 0.806, 0.831, 0.892, 0.787, 0.826, 0.885, 0.767, 0.820, 0.878, + 0.746, 0.814, 0.870, 0.725, 0.809, 0.862, 0.703, 0.803, 0.854, 0.681, + 0.798, 0.846, 0.659, 0.793, 0.838, 0.637, 0.788, 0.829, 0.615, 0.782, + 0.821, 0.592, 0.777, 0.812, 0.570, 0.773, 0.804, 0.548, 0.768, 0.795, + 0.525, 0.763, 0.787, 0.502, 0.758, 0.778, 0.480, 0.754, 0.769, 0.457, + 0.749, 0.761, 0.434, 0.745, 0.752, 0.411, 0.741, 0.743, 0.388, 0.737, + 0.735, 0.365, 0.732, 0.726, 0.342, 0.728, 0.717, 0.318, 0.724, 0.709, + 0.293, 0.720, 0.700, 0.269, 0.716, 0.691, 0.243, 0.712, 0.683, 0.216, + 0.542, 0.673, 0.990, 0.554, 0.682, 0.985, 0.565, 0.691, 0.980, 0.577, + 0.700, 0.975, 0.588, 0.708, 0.970, 0.600, 0.717, 0.966, 0.611, 0.726, + 0.962, 0.623, 0.734, 0.958, 0.634, 0.743, 0.955, 0.646, 0.752, 0.951, + 0.657, 0.760, 0.948, 0.669, 0.769, 0.945, 0.681, 0.778, 0.943, 0.692, + 0.786, 0.940, 0.704, 0.795, 0.938, 0.716, 0.804, 0.936, 0.728, 0.812, + 0.934, 0.739, 0.821, 0.933, 0.751, 0.830, 0.932, 0.763, 0.838, 0.930, + 0.774, 0.847, 0.929, 0.786, 0.856, 0.929, 0.797, 0.864, 0.928, 0.809, + 0.873, 0.927, 0.819, 0.881, 0.927, 0.830, 0.889, 0.926, 0.840, 0.896, + 0.925, 0.850, 0.903, 0.924, 0.858, 0.910, 0.922, 0.865, 0.915, 0.920, + 0.871, 0.920, 0.916, 0.875, 0.923, 0.911, 0.876, 0.924, 0.903, 0.876, + 0.924, 0.894, 0.873, 0.922, 0.882, 0.870, 0.919, 0.867, 0.865, 0.914, + 0.851, 0.860, 0.909, 0.832, 0.854, 0.902, 0.813, 0.848, 0.895, 0.793, + 0.842, 0.888, 0.772, 0.836, 0.880, 0.750, 0.830, 0.872, 0.729, 0.824, + 0.864, 0.707, 0.819, 0.855, 0.684, 0.813, 0.847, 0.662, 0.808, 0.838, + 0.639, 0.802, 0.829, 0.617, 0.797, 0.820, 0.594, 0.792, 0.812, 0.571, + 0.787, 0.803, 0.549, 0.782, 0.794, 0.526, 0.777, 0.785, 0.503, 0.772, + 0.776, 0.480, 0.767, 0.768, 0.458, 0.763, 0.759, 0.435, 0.758, 0.750, + 0.412, 0.754, 0.741, 0.388, 0.749, 0.732, 0.365, 0.745, 0.724, 0.342, + 0.741, 0.715, 0.318, 0.737, 0.706, 0.293, 0.732, 0.697, 0.269, 0.728, + 0.689, 0.243, 0.724, 0.680, 0.216, 0.556, 0.671, 0.992, 0.567, 0.680, + 0.986, 0.578, 0.689, 0.981, 0.590, 0.697, 0.976, 0.601, 0.706, 0.972, + 0.612, 0.715, 0.968, 0.624, 0.724, 0.964, 0.635, 0.733, 0.960, 0.646, + 0.741, 0.956, 0.658, 0.750, 0.953, 0.670, 0.759, 0.950, 0.681, 0.768, + 0.947, 0.693, 0.777, 0.945, 0.704, 0.786, 0.943, 0.716, 0.794, 0.941, + 0.728, 0.803, 0.939, 0.740, 0.812, 0.937, 0.752, 0.821, 0.936, 0.763, + 0.830, 0.935, 0.775, 0.839, 0.934, 0.787, 0.848, 0.933, 0.799, 0.857, + 0.932, 0.811, 0.866, 0.932, 0.822, 0.875, 0.932, 0.834, 0.883, 0.932, + 0.845, 0.892, 0.931, 0.856, 0.900, 0.931, 0.866, 0.908, 0.931, 0.876, + 0.915, 0.930, 0.884, 0.922, 0.929, 0.890, 0.927, 0.926, 0.895, 0.930, + 0.921, 0.896, 0.932, 0.914, 0.896, 0.932, 0.905, 0.893, 0.929, 0.892, + 0.888, 0.925, 0.876, 0.883, 0.920, 0.859, 0.877, 0.913, 0.840, 0.871, + 0.906, 0.819, 0.864, 0.898, 0.798, 0.858, 0.890, 0.776, 0.852, 0.882, + 0.754, 0.845, 0.873, 0.732, 0.839, 0.864, 0.709, 0.833, 0.855, 0.686, + 0.828, 0.846, 0.664, 0.822, 0.837, 0.641, 0.816, 0.828, 0.618, 0.811, + 0.819, 0.595, 0.806, 0.810, 0.572, 0.800, 0.801, 0.549, 0.795, 0.792, + 0.526, 0.790, 0.783, 0.504, 0.785, 0.774, 0.481, 0.781, 0.765, 0.458, + 0.776, 0.756, 0.435, 0.771, 0.748, 0.412, 0.766, 0.739, 0.388, 0.762, + 0.730, 0.365, 0.757, 0.721, 0.341, 0.753, 0.712, 0.318, 0.749, 0.703, + 0.293, 0.744, 0.695, 0.268, 0.740, 0.686, 0.243, 0.736, 0.677, 0.216, + 0.569, 0.668, 0.993, 0.580, 0.677, 0.987, 0.591, 0.686, 0.982, 0.602, + 0.695, 0.978, 0.613, 0.704, 0.973, 0.624, 0.713, 0.969, 0.635, 0.722, + 0.965, 0.647, 0.731, 0.961, 0.658, 0.740, 0.958, 0.670, 0.748, 0.955, + 0.681, 0.757, 0.952, 0.693, 0.766, 0.949, 0.704, 0.775, 0.947, 0.716, + 0.784, 0.945, 0.728, 0.793, 0.943, 0.739, 0.802, 0.941, 0.751, 0.812, + 0.939, 0.763, 0.821, 0.938, 0.775, 0.830, 0.937, 0.787, 0.839, 0.936, + 0.799, 0.848, 0.936, 0.811, 0.858, 0.936, 0.823, 0.867, 0.935, 0.835, + 0.876, 0.936, 0.847, 0.886, 0.936, 0.859, 0.895, 0.936, 0.870, 0.904, + 0.937, 0.882, 0.912, 0.937, 0.892, 0.920, 0.937, 0.901, 0.928, 0.937, + 0.909, 0.934, 0.936, 0.914, 0.938, 0.932, 0.917, 0.940, 0.926, 0.915, + 0.940, 0.916, 0.912, 0.936, 0.902, 0.906, 0.931, 0.885, 0.900, 0.925, + 0.866, 0.893, 0.917, 0.846, 0.887, 0.909, 0.824, 0.880, 0.900, 0.802, + 0.873, 0.891, 0.780, 0.867, 0.882, 0.757, 0.860, 0.873, 0.734, 0.854, + 0.864, 0.711, 0.848, 0.855, 0.688, 0.842, 0.845, 0.665, 0.836, 0.836, + 0.642, 0.830, 0.827, 0.619, 0.824, 0.818, 0.596, 0.819, 0.808, 0.573, + 0.814, 0.799, 0.549, 0.808, 0.790, 0.527, 0.803, 0.781, 0.504, 0.798, + 0.772, 0.481, 0.793, 0.763, 0.458, 0.788, 0.754, 0.434, 0.783, 0.745, + 0.411, 0.779, 0.736, 0.388, 0.774, 0.727, 0.365, 0.769, 0.718, 0.341, + 0.765, 0.709, 0.317, 0.760, 0.700, 0.293, 0.756, 0.691, 0.268, 0.751, + 0.683, 0.242, 0.747, 0.674, 0.215, 0.581, 0.665, 0.994, 0.592, 0.674, + 0.989, 0.603, 0.683, 0.984, 0.614, 0.692, 0.979, 0.625, 0.701, 0.974, + 0.636, 0.710, 0.970, 0.647, 0.719, 0.966, 0.658, 0.728, 0.963, 0.669, + 0.737, 0.959, 0.681, 0.746, 0.956, 0.692, 0.755, 0.953, 0.703, 0.765, + 0.951, 0.715, 0.774, 0.948, 0.727, 0.783, 0.946, 0.738, 0.792, 0.944, + 0.750, 0.801, 0.943, 0.762, 0.810, 0.941, 0.774, 0.820, 0.940, 0.786, + 0.829, 0.939, 0.798, 0.839, 0.939, 0.810, 0.848, 0.938, 0.822, 0.858, + 0.938, 0.834, 0.867, 0.939, 0.847, 0.877, 0.939, 0.859, 0.887, 0.940, + 0.871, 0.897, 0.940, 0.883, 0.906, 0.942, 0.896, 0.916, 0.943, 0.907, + 0.925, 0.944, 0.918, 0.933, 0.945, 0.927, 0.941, 0.945, 0.934, 0.946, + 0.943, 0.937, 0.949, 0.937, 0.935, 0.948, 0.927, 0.930, 0.943, 0.912, + 0.924, 0.937, 0.893, 0.916, 0.929, 0.872, 0.909, 0.920, 0.850, 0.902, + 0.911, 0.828, 0.895, 0.901, 0.805, 0.888, 0.892, 0.782, 0.881, 0.882, + 0.759, 0.874, 0.872, 0.735, 0.868, 0.863, 0.712, 0.861, 0.853, 0.689, + 0.855, 0.844, 0.665, 0.849, 0.834, 0.642, 0.843, 0.825, 0.619, 0.838, + 0.815, 0.596, 0.832, 0.806, 0.572, 0.826, 0.797, 0.549, 0.821, 0.787, + 0.526, 0.816, 0.778, 0.503, 0.811, 0.769, 0.480, 0.805, 0.760, 0.457, + 0.800, 0.751, 0.434, 0.795, 0.742, 0.411, 0.791, 0.733, 0.387, 0.786, + 0.724, 0.364, 0.781, 0.715, 0.340, 0.776, 0.706, 0.316, 0.772, 0.697, + 0.292, 0.767, 0.688, 0.267, 0.762, 0.679, 0.241, 0.758, 0.670, 0.215, + 0.593, 0.662, 0.995, 0.603, 0.671, 0.990, 0.614, 0.680, 0.985, 0.625, + 0.689, 0.980, 0.636, 0.699, 0.975, 0.647, 0.708, 0.971, 0.658, 0.717, + 0.967, 0.669, 0.726, 0.964, 0.680, 0.735, 0.960, 0.691, 0.744, 0.957, + 0.702, 0.753, 0.955, 0.714, 0.762, 0.952, 0.725, 0.771, 0.950, 0.737, + 0.781, 0.948, 0.748, 0.790, 0.946, 0.760, 0.799, 0.944, 0.772, 0.809, + 0.943, 0.784, 0.818, 0.942, 0.796, 0.828, 0.941, 0.808, 0.837, 0.941, + 0.820, 0.847, 0.940, 0.832, 0.857, 0.941, 0.844, 0.867, 0.941, 0.857, + 0.877, 0.942, 0.869, 0.887, 0.943, 0.882, 0.897, 0.944, 0.895, 0.908, + 0.945, 0.908, 0.918, 0.947, 0.920, 0.928, 0.949, 0.933, 0.938, 0.951, + 0.944, 0.947, 0.953, 0.953, 0.955, 0.954, 0.957, 0.958, 0.950, 0.954, + 0.956, 0.938, 0.948, 0.949, 0.920, 0.940, 0.941, 0.899, 0.932, 0.931, + 0.877, 0.924, 0.921, 0.854, 0.916, 0.911, 0.830, 0.909, 0.901, 0.807, + 0.901, 0.891, 0.783, 0.894, 0.881, 0.759, 0.887, 0.871, 0.736, 0.881, + 0.861, 0.712, 0.874, 0.851, 0.688, 0.868, 0.841, 0.665, 0.862, 0.832, + 0.642, 0.856, 0.822, 0.618, 0.850, 0.812, 0.595, 0.844, 0.803, 0.572, + 0.839, 0.793, 0.549, 0.833, 0.784, 0.525, 0.828, 0.775, 0.502, 0.822, + 0.766, 0.479, 0.817, 0.756, 0.456, 0.812, 0.747, 0.433, 0.807, 0.738, + 0.410, 0.802, 0.729, 0.387, 0.797, 0.720, 0.363, 0.792, 0.711, 0.339, + 0.787, 0.702, 0.315, 0.782, 0.693, 0.291, 0.778, 0.684, 0.266, 0.773, + 0.675, 0.240, 0.768, 0.666, 0.214, 0.604, 0.659, 0.996, 0.614, 0.668, + 0.990, 0.625, 0.677, 0.985, 0.636, 0.686, 0.981, 0.646, 0.695, 0.976, + 0.657, 0.704, 0.972, 0.668, 0.714, 0.968, 0.679, 0.723, 0.965, 0.690, + 0.732, 0.961, 0.701, 0.741, 0.958, 0.712, 0.750, 0.956, 0.723, 0.759, + 0.953, 0.735, 0.769, 0.951, 0.746, 0.778, 0.949, 0.758, 0.787, 0.947, + 0.769, 0.797, 0.945, 0.781, 0.806, 0.944, 0.793, 0.816, 0.943, 0.805, + 0.826, 0.943, 0.817, 0.836, 0.942, 0.829, 0.845, 0.942, 0.841, 0.855, + 0.942, 0.853, 0.866, 0.943, 0.866, 0.876, 0.943, 0.879, 0.886, 0.945, + 0.892, 0.897, 0.946, 0.905, 0.907, 0.948, 0.918, 0.918, 0.950, 0.931, + 0.930, 0.953, 0.945, 0.941, 0.956, 0.958, 0.952, 0.960, 0.971, 0.963, + 0.963, 0.978, 0.968, 0.963, 0.972, 0.963, 0.948, 0.963, 0.954, 0.926, + 0.954, 0.943, 0.903, 0.945, 0.931, 0.879, 0.937, 0.921, 0.855, 0.929, + 0.910, 0.831, 0.921, 0.899, 0.807, 0.914, 0.889, 0.783, 0.907, 0.878, + 0.759, 0.900, 0.868, 0.735, 0.893, 0.858, 0.711, 0.887, 0.848, 0.688, + 0.880, 0.838, 0.664, 0.874, 0.828, 0.641, 0.868, 0.818, 0.617, 0.862, + 0.809, 0.594, 0.856, 0.799, 0.571, 0.850, 0.790, 0.547, 0.845, 0.780, + 0.524, 0.839, 0.771, 0.501, 0.834, 0.762, 0.478, 0.828, 0.752, 0.455, + 0.823, 0.743, 0.432, 0.818, 0.734, 0.409, 0.813, 0.725, 0.385, 0.808, + 0.716, 0.362, 0.803, 0.707, 0.338, 0.798, 0.698, 0.314, 0.793, 0.689, + 0.290, 0.788, 0.680, 0.265, 0.783, 0.671, 0.239, 0.778, 0.662, 0.213, + 0.615, 0.655, 0.996, 0.625, 0.664, 0.991, 0.635, 0.673, 0.986, 0.646, + 0.683, 0.982, 0.656, 0.692, 0.977, 0.667, 0.701, 0.973, 0.678, 0.710, + 0.969, 0.688, 0.719, 0.966, 0.699, 0.729, 0.962, 0.710, 0.738, 0.959, + 0.721, 0.747, 0.956, 0.732, 0.756, 0.954, 0.744, 0.766, 0.952, 0.755, + 0.775, 0.950, 0.766, 0.784, 0.948, 0.778, 0.794, 0.946, 0.789, 0.804, + 0.945, 0.801, 0.813, 0.944, 0.813, 0.823, 0.943, 0.825, 0.833, 0.943, + 0.837, 0.843, 0.943, 0.849, 0.853, 0.943, 0.861, 0.863, 0.944, 0.874, + 0.873, 0.944, 0.886, 0.884, 0.946, 0.899, 0.895, 0.947, 0.912, 0.906, + 0.949, 0.926, 0.917, 0.952, 0.939, 0.928, 0.955, 0.953, 0.940, 0.958, + 0.967, 0.953, 0.963, 0.982, 0.966, 0.969, 0.994, 0.976, 0.972, 0.986, + 0.966, 0.952, 0.976, 0.953, 0.928, 0.966, 0.941, 0.903, 0.957, 0.929, + 0.878, 0.949, 0.918, 0.854, 0.941, 0.906, 0.830, 0.933, 0.896, 0.806, + 0.926, 0.885, 0.782, 0.918, 0.874, 0.758, 0.911, 0.864, 0.734, 0.905, + 0.854, 0.710, 0.898, 0.844, 0.686, 0.892, 0.834, 0.663, 0.885, 0.824, + 0.639, 0.879, 0.814, 0.616, 0.873, 0.804, 0.592, 0.867, 0.795, 0.569, + 0.861, 0.785, 0.546, 0.856, 0.776, 0.523, 0.850, 0.766, 0.500, 0.845, + 0.757, 0.477, 0.839, 0.748, 0.454, 0.834, 0.739, 0.430, 0.829, 0.729, + 0.407, 0.823, 0.720, 0.384, 0.818, 0.711, 0.361, 0.813, 0.702, 0.337, + 0.808, 0.693, 0.313, 0.803, 0.684, 0.289, 0.798, 0.675, 0.264, 0.793, + 0.666, 0.238, 0.788, 0.657, 0.211, 0.625, 0.651, 0.997, 0.635, 0.660, + 0.992, 0.645, 0.669, 0.987, 0.656, 0.679, 0.982, 0.666, 0.688, 0.978, + 0.676, 0.697, 0.974, 0.687, 0.706, 0.970, 0.698, 0.716, 0.966, 0.708, + 0.725, 0.963, 0.719, 0.734, 0.960, 0.730, 0.743, 0.957, 0.741, 0.753, + 0.954, 0.752, 0.762, 0.952, 0.763, 0.771, 0.950, 0.774, 0.781, 0.948, + 0.786, 0.790, 0.947, 0.797, 0.800, 0.945, 0.809, 0.810, 0.945, 0.820, + 0.819, 0.944, 0.832, 0.829, 0.943, 0.844, 0.839, 0.943, 0.856, 0.849, + 0.943, 0.868, 0.860, 0.944, 0.880, 0.870, 0.945, 0.893, 0.880, 0.946, + 0.905, 0.891, 0.947, 0.918, 0.902, 0.949, 0.931, 0.913, 0.951, 0.944, + 0.924, 0.954, 0.957, 0.936, 0.957, 0.971, 0.947, 0.961, 0.983, 0.958, + 0.964, 0.991, 0.963, 0.962, 0.990, 0.957, 0.946, 0.983, 0.946, 0.924, + 0.975, 0.935, 0.900, 0.967, 0.923, 0.876, 0.959, 0.912, 0.851, 0.951, + 0.901, 0.827, 0.943, 0.890, 0.803, 0.936, 0.880, 0.779, 0.929, 0.869, + 0.755, 0.922, 0.859, 0.732, 0.915, 0.849, 0.708, 0.909, 0.839, 0.684, + 0.902, 0.829, 0.661, 0.896, 0.819, 0.637, 0.890, 0.809, 0.614, 0.884, + 0.800, 0.591, 0.878, 0.790, 0.567, 0.872, 0.780, 0.544, 0.866, 0.771, + 0.521, 0.861, 0.762, 0.498, 0.855, 0.752, 0.475, 0.850, 0.743, 0.452, + 0.844, 0.734, 0.429, 0.839, 0.725, 0.406, 0.834, 0.715, 0.382, 0.828, + 0.706, 0.359, 0.823, 0.697, 0.335, 0.818, 0.688, 0.311, 0.813, 0.679, + 0.287, 0.808, 0.670, 0.262, 0.803, 0.662, 0.236, 0.798, 0.653, 0.210, + 0.635, 0.646, 0.998, 0.645, 0.656, 0.992, 0.655, 0.665, 0.987, 0.665, + 0.674, 0.983, 0.675, 0.684, 0.978, 0.685, 0.693, 0.974, 0.696, 0.702, + 0.970, 0.706, 0.711, 0.966, 0.717, 0.721, 0.963, 0.727, 0.730, 0.960, + 0.738, 0.739, 0.957, 0.749, 0.748, 0.955, 0.760, 0.758, 0.952, 0.771, + 0.767, 0.950, 0.782, 0.777, 0.948, 0.793, 0.786, 0.947, 0.804, 0.796, + 0.946, 0.816, 0.805, 0.945, 0.827, 0.815, 0.944, 0.839, 0.825, 0.943, + 0.850, 0.835, 0.943, 0.862, 0.845, 0.943, 0.874, 0.855, 0.943, 0.886, + 0.865, 0.944, 0.898, 0.875, 0.945, 0.910, 0.886, 0.946, 0.923, 0.896, + 0.948, 0.935, 0.907, 0.949, 0.947, 0.917, 0.951, 0.959, 0.927, 0.953, + 0.970, 0.937, 0.955, 0.980, 0.944, 0.955, 0.987, 0.946, 0.949, 0.989, + 0.942, 0.936, 0.985, 0.935, 0.916, 0.980, 0.925, 0.894, 0.973, 0.915, + 0.871, 0.966, 0.904, 0.847, 0.959, 0.894, 0.824, 0.952, 0.883, 0.800, + 0.945, 0.873, 0.776, 0.938, 0.863, 0.752, 0.932, 0.853, 0.729, 0.925, + 0.843, 0.705, 0.919, 0.833, 0.682, 0.912, 0.823, 0.658, 0.906, 0.813, + 0.635, 0.900, 0.803, 0.611, 0.894, 0.794, 0.588, 0.888, 0.784, 0.565, + 0.882, 0.775, 0.542, 0.876, 0.765, 0.519, 0.871, 0.756, 0.496, 0.865, + 0.747, 0.473, 0.859, 0.738, 0.450, 0.854, 0.728, 0.427, 0.848, 0.719, + 0.404, 0.843, 0.710, 0.380, 0.838, 0.701, 0.357, 0.833, 0.692, 0.333, + 0.827, 0.683, 0.310, 0.822, 0.674, 0.285, 0.817, 0.665, 0.260, 0.812, + 0.656, 0.235, 0.807, 0.648, 0.208, 0.644, 0.642, 0.998, 0.654, 0.651, + 0.993, 0.664, 0.660, 0.988, 0.674, 0.670, 0.983, 0.684, 0.679, 0.978, + 0.694, 0.688, 0.974, 0.704, 0.697, 0.970, 0.715, 0.707, 0.967, 0.725, + 0.716, 0.963, 0.735, 0.725, 0.960, 0.746, 0.735, 0.957, 0.757, 0.744, + 0.955, 0.767, 0.753, 0.952, 0.778, 0.763, 0.950, 0.789, 0.772, 0.948, + 0.800, 0.781, 0.947, 0.811, 0.791, 0.945, 0.822, 0.801, 0.944, 0.833, + 0.810, 0.943, 0.845, 0.820, 0.943, 0.856, 0.830, 0.942, 0.868, 0.839, + 0.942, 0.879, 0.849, 0.942, 0.891, 0.859, 0.943, 0.902, 0.869, 0.943, + 0.914, 0.879, 0.944, 0.926, 0.889, 0.945, 0.937, 0.899, 0.946, 0.949, + 0.908, 0.947, 0.959, 0.917, 0.948, 0.969, 0.924, 0.947, 0.978, 0.929, + 0.944, 0.984, 0.930, 0.937, 0.986, 0.927, 0.924, 0.986, 0.921, 0.907, + 0.982, 0.913, 0.887, 0.978, 0.904, 0.865, 0.972, 0.895, 0.842, 0.966, + 0.885, 0.819, 0.959, 0.875, 0.795, 0.953, 0.865, 0.772, 0.946, 0.855, + 0.749, 0.940, 0.846, 0.725, 0.934, 0.836, 0.702, 0.927, 0.826, 0.678, + 0.921, 0.816, 0.655, 0.915, 0.807, 0.632, 0.909, 0.797, 0.609, 0.903, + 0.788, 0.586, 0.897, 0.778, 0.562, 0.891, 0.769, 0.539, 0.886, 0.759, + 0.516, 0.880, 0.750, 0.493, 0.874, 0.741, 0.471, 0.869, 0.732, 0.448, + 0.863, 0.723, 0.425, 0.858, 0.713, 0.402, 0.852, 0.704, 0.378, 0.847, + 0.695, 0.355, 0.842, 0.686, 0.331, 0.836, 0.677, 0.308, 0.831, 0.669, + 0.283, 0.826, 0.660, 0.258, 0.821, 0.651, 0.233, 0.815, 0.642, 0.206, + 0.653, 0.636, 0.998, 0.663, 0.646, 0.993, 0.673, 0.655, 0.988, 0.682, + 0.665, 0.983, 0.692, 0.674, 0.979, 0.702, 0.683, 0.974, 0.712, 0.692, + 0.970, 0.722, 0.702, 0.967, 0.733, 0.711, 0.963, 0.743, 0.720, 0.960, + 0.753, 0.730, 0.957, 0.764, 0.739, 0.954, 0.774, 0.748, 0.952, 0.785, + 0.757, 0.950, 0.796, 0.767, 0.948, 0.806, 0.776, 0.946, 0.817, 0.786, + 0.945, 0.828, 0.795, 0.943, 0.839, 0.804, 0.942, 0.850, 0.814, 0.941, + 0.861, 0.824, 0.941, 0.872, 0.833, 0.940, 0.884, 0.843, 0.940, 0.895, + 0.852, 0.940, 0.906, 0.862, 0.940, 0.917, 0.871, 0.941, 0.928, 0.880, + 0.941, 0.939, 0.889, 0.941, 0.949, 0.897, 0.941, 0.959, 0.904, 0.940, + 0.968, 0.910, 0.938, 0.975, 0.914, 0.933, 0.981, 0.914, 0.925, 0.984, + 0.912, 0.913, 0.985, 0.907, 0.897, 0.983, 0.900, 0.878, 0.980, 0.893, + 0.857, 0.976, 0.884, 0.836, 0.971, 0.875, 0.813, 0.965, 0.866, 0.790, + 0.959, 0.856, 0.767, 0.954, 0.847, 0.744, 0.947, 0.837, 0.721, 0.941, + 0.828, 0.698, 0.935, 0.818, 0.675, 0.929, 0.809, 0.652, 0.923, 0.800, + 0.629, 0.917, 0.790, 0.605, 0.912, 0.781, 0.582, 0.906, 0.771, 0.560, + 0.900, 0.762, 0.537, 0.894, 0.753, 0.514, 0.889, 0.744, 0.491, 0.883, + 0.735, 0.468, 0.877, 0.725, 0.445, 0.872, 0.716, 0.422, 0.866, 0.707, + 0.399, 0.861, 0.698, 0.376, 0.856, 0.689, 0.353, 0.850, 0.680, 0.329, + 0.845, 0.672, 0.305, 0.840, 0.663, 0.281, 0.834, 0.654, 0.256, 0.829, + 0.645, 0.231, 0.824, 0.636, 0.204, 0.662, 0.631, 0.998, 0.672, 0.641, + 0.993, 0.681, 0.650, 0.988, 0.691, 0.659, 0.983, 0.700, 0.669, 0.979, + 0.710, 0.678, 0.974, 0.720, 0.687, 0.970, 0.730, 0.696, 0.966, 0.740, + 0.706, 0.963, 0.750, 0.715, 0.960, 0.760, 0.724, 0.957, 0.771, 0.733, + 0.954, 0.781, 0.742, 0.951, 0.791, 0.752, 0.949, 0.802, 0.761, 0.947, + 0.812, 0.770, 0.945, 0.823, 0.779, 0.943, 0.834, 0.789, 0.942, 0.844, + 0.798, 0.941, 0.855, 0.807, 0.940, 0.866, 0.817, 0.939, 0.877, 0.826, + 0.938, 0.887, 0.835, 0.938, 0.898, 0.844, 0.937, 0.909, 0.853, 0.937, + 0.919, 0.862, 0.937, 0.930, 0.870, 0.936, 0.940, 0.878, 0.936, 0.950, + 0.885, 0.934, 0.958, 0.891, 0.932, 0.967, 0.896, 0.928, 0.974, 0.898, + 0.922, 0.979, 0.899, 0.914, 0.982, 0.897, 0.902, 0.984, 0.893, 0.886, + 0.984, 0.887, 0.869, 0.982, 0.880, 0.849, 0.979, 0.872, 0.828, 0.975, + 0.864, 0.807, 0.970, 0.856, 0.785, 0.965, 0.847, 0.762, 0.959, 0.838, + 0.739, 0.954, 0.829, 0.717, 0.948, 0.819, 0.694, 0.943, 0.810, 0.671, + 0.937, 0.801, 0.648, 0.931, 0.792, 0.625, 0.925, 0.782, 0.602, 0.919, + 0.773, 0.579, 0.914, 0.764, 0.556, 0.908, 0.755, 0.533, 0.902, 0.746, + 0.511, 0.897, 0.737, 0.488, 0.891, 0.728, 0.465, 0.886, 0.719, 0.442, + 0.880, 0.710, 0.420, 0.875, 0.701, 0.397, 0.869, 0.692, 0.374, 0.864, + 0.683, 0.350, 0.858, 0.674, 0.327, 0.853, 0.665, 0.303, 0.848, 0.657, + 0.279, 0.842, 0.648, 0.254, 0.837, 0.639, 0.229, 0.832, 0.630, 0.202, + 0.671, 0.625, 0.998, 0.680, 0.635, 0.993, 0.689, 0.644, 0.988, 0.698, + 0.654, 0.983, 0.708, 0.663, 0.978, 0.718, 0.672, 0.974, 0.727, 0.681, + 0.970, 0.737, 0.691, 0.966, 0.747, 0.700, 0.962, 0.757, 0.709, 0.959, + 0.767, 0.718, 0.956, 0.777, 0.727, 0.953, 0.787, 0.736, 0.950, 0.797, + 0.745, 0.948, 0.807, 0.755, 0.946, 0.818, 0.764, 0.944, 0.828, 0.773, + 0.942, 0.839, 0.782, 0.940, 0.849, 0.791, 0.939, 0.859, 0.800, 0.938, + 0.870, 0.809, 0.937, 0.880, 0.818, 0.936, 0.891, 0.827, 0.935, 0.901, + 0.835, 0.934, 0.911, 0.844, 0.933, 0.921, 0.852, 0.932, 0.931, 0.859, + 0.931, 0.941, 0.866, 0.929, 0.950, 0.873, 0.927, 0.958, 0.878, 0.923, + 0.966, 0.881, 0.919, 0.972, 0.883, 0.912, 0.977, 0.883, 0.903, 0.981, + 0.882, 0.891, 0.983, 0.878, 0.876, 0.984, 0.873, 0.859, 0.983, 0.867, + 0.841, 0.980, 0.860, 0.821, 0.977, 0.853, 0.800, 0.974, 0.845, 0.778, + 0.969, 0.836, 0.756, 0.964, 0.828, 0.734, 0.959, 0.819, 0.712, 0.954, + 0.810, 0.689, 0.949, 0.801, 0.666, 0.943, 0.792, 0.644, 0.938, 0.783, + 0.621, 0.932, 0.774, 0.598, 0.927, 0.765, 0.575, 0.921, 0.756, 0.553, + 0.916, 0.747, 0.530, 0.910, 0.738, 0.507, 0.904, 0.729, 0.485, 0.899, + 0.721, 0.462, 0.893, 0.712, 0.439, 0.888, 0.703, 0.417, 0.882, 0.694, + 0.394, 0.877, 0.685, 0.371, 0.871, 0.676, 0.348, 0.866, 0.668, 0.324, + 0.861, 0.659, 0.301, 0.855, 0.650, 0.277, 0.850, 0.641, 0.252, 0.845, + 0.633, 0.226, 0.839, 0.624, 0.200, 0.679, 0.619, 0.998, 0.688, 0.629, + 0.993, 0.697, 0.638, 0.988, 0.706, 0.648, 0.983, 0.715, 0.657, 0.978, + 0.725, 0.666, 0.974, 0.734, 0.675, 0.969, 0.744, 0.684, 0.965, 0.754, + 0.693, 0.962, 0.763, 0.703, 0.958, 0.773, 0.712, 0.955, 0.783, 0.721, + 0.952, 0.793, 0.730, 0.949, 0.803, 0.739, 0.947, 0.813, 0.748, 0.944, + 0.823, 0.757, 0.942, 0.833, 0.766, 0.940, 0.843, 0.774, 0.938, 0.853, + 0.783, 0.937, 0.863, 0.792, 0.935, 0.874, 0.801, 0.934, 0.884, 0.809, + 0.932, 0.894, 0.818, 0.931, 0.904, 0.826, 0.930, 0.913, 0.833, 0.928, + 0.923, 0.841, 0.927, 0.932, 0.848, 0.925, 0.941, 0.854, 0.922, 0.950, + 0.859, 0.919, 0.957, 0.864, 0.915, 0.965, 0.867, 0.909, 0.971, 0.868, + 0.901, 0.976, 0.868, 0.892, 0.980, 0.867, 0.880, 0.982, 0.863, 0.866, + 0.983, 0.859, 0.849, 0.983, 0.854, 0.832, 0.982, 0.847, 0.812, 0.979, + 0.840, 0.792, 0.976, 0.833, 0.771, 0.973, 0.825, 0.750, 0.969, 0.817, + 0.728, 0.964, 0.809, 0.706, 0.959, 0.800, 0.684, 0.954, 0.792, 0.661, + 0.949, 0.783, 0.639, 0.944, 0.774, 0.617, 0.939, 0.766, 0.594, 0.933, + 0.757, 0.572, 0.928, 0.748, 0.549, 0.922, 0.739, 0.527, 0.917, 0.731, + 0.504, 0.911, 0.722, 0.481, 0.906, 0.713, 0.459, 0.901, 0.704, 0.436, + 0.895, 0.695, 0.414, 0.890, 0.687, 0.391, 0.884, 0.678, 0.368, 0.879, + 0.669, 0.345, 0.873, 0.661, 0.322, 0.868, 0.652, 0.298, 0.863, 0.643, + 0.274, 0.857, 0.635, 0.249, 0.852, 0.626, 0.224, 0.846, 0.617, 0.197, + 0.686, 0.613, 0.998, 0.695, 0.622, 0.993, 0.704, 0.632, 0.987, 0.713, + 0.641, 0.982, 0.722, 0.650, 0.977, 0.732, 0.660, 0.973, 0.741, 0.669, + 0.969, 0.750, 0.678, 0.965, 0.760, 0.687, 0.961, 0.769, 0.696, 0.957, + 0.779, 0.705, 0.954, 0.789, 0.714, 0.951, 0.798, 0.723, 0.948, 0.808, + 0.731, 0.945, 0.818, 0.740, 0.943, 0.828, 0.749, 0.940, 0.838, 0.758, + 0.938, 0.847, 0.766, 0.936, 0.857, 0.775, 0.934, 0.867, 0.783, 0.932, + 0.877, 0.792, 0.930, 0.887, 0.800, 0.929, 0.896, 0.808, 0.927, 0.906, + 0.815, 0.925, 0.915, 0.823, 0.923, 0.924, 0.829, 0.921, 0.933, 0.836, + 0.918, 0.942, 0.841, 0.915, 0.950, 0.846, 0.911, 0.957, 0.850, 0.906, + 0.964, 0.852, 0.899, 0.970, 0.853, 0.891, 0.975, 0.853, 0.881, 0.979, + 0.852, 0.869, 0.981, 0.849, 0.855, 0.983, 0.845, 0.840, 0.983, 0.840, + 0.822, 0.982, 0.834, 0.804, 0.981, 0.828, 0.784, 0.978, 0.821, 0.764, + 0.975, 0.814, 0.743, 0.972, 0.806, 0.722, 0.968, 0.798, 0.700, 0.964, + 0.790, 0.678, 0.959, 0.782, 0.656, 0.954, 0.773, 0.634, 0.949, 0.765, + 0.612, 0.944, 0.757, 0.590, 0.939, 0.748, 0.567, 0.934, 0.739, 0.545, + 0.929, 0.731, 0.523, 0.923, 0.722, 0.500, 0.918, 0.714, 0.478, 0.913, + 0.705, 0.456, 0.907, 0.696, 0.433, 0.902, 0.688, 0.411, 0.896, 0.679, + 0.388, 0.891, 0.670, 0.365, 0.886, 0.662, 0.342, 0.880, 0.653, 0.319, + 0.875, 0.645, 0.295, 0.869, 0.636, 0.271, 0.864, 0.628, 0.247, 0.859, + 0.619, 0.221, 0.853, 0.611, 0.195, 0.694, 0.606, 0.998, 0.703, 0.616, + 0.992, 0.711, 0.625, 0.987, 0.720, 0.634, 0.982, 0.729, 0.644, 0.977, + 0.738, 0.653, 0.972, 0.747, 0.662, 0.968, 0.757, 0.671, 0.964, 0.766, + 0.680, 0.960, 0.775, 0.689, 0.956, 0.785, 0.698, 0.953, 0.794, 0.706, + 0.949, 0.804, 0.715, 0.946, 0.813, 0.724, 0.943, 0.823, 0.732, 0.941, + 0.832, 0.741, 0.938, 0.842, 0.749, 0.936, 0.851, 0.758, 0.933, 0.861, + 0.766, 0.931, 0.870, 0.774, 0.929, 0.880, 0.782, 0.927, 0.889, 0.790, + 0.924, 0.899, 0.797, 0.922, 0.908, 0.805, 0.920, 0.917, 0.811, 0.917, + 0.925, 0.818, 0.914, 0.934, 0.823, 0.911, 0.942, 0.828, 0.907, 0.950, + 0.832, 0.902, 0.957, 0.836, 0.896, 0.963, 0.838, 0.889, 0.969, 0.839, + 0.881, 0.974, 0.838, 0.871, 0.978, 0.837, 0.859, 0.980, 0.834, 0.845, + 0.982, 0.831, 0.830, 0.983, 0.826, 0.813, 0.983, 0.821, 0.795, 0.982, + 0.815, 0.776, 0.980, 0.809, 0.757, 0.978, 0.802, 0.736, 0.975, 0.794, + 0.715, 0.971, 0.787, 0.694, 0.967, 0.779, 0.673, 0.963, 0.771, 0.651, + 0.959, 0.763, 0.629, 0.954, 0.755, 0.607, 0.949, 0.747, 0.585, 0.944, + 0.739, 0.563, 0.939, 0.730, 0.541, 0.934, 0.722, 0.519, 0.929, 0.714, + 0.496, 0.924, 0.705, 0.474, 0.919, 0.697, 0.452, 0.913, 0.688, 0.430, + 0.908, 0.680, 0.407, 0.903, 0.671, 0.385, 0.897, 0.663, 0.362, 0.892, + 0.654, 0.339, 0.887, 0.646, 0.316, 0.881, 0.637, 0.293, 0.876, 0.629, + 0.269, 0.870, 0.620, 0.244, 0.865, 0.612, 0.219, 0.860, 0.604, 0.192, + 0.701, 0.599, 0.997, 0.710, 0.609, 0.992, 0.718, 0.618, 0.986, 0.727, + 0.627, 0.981, 0.736, 0.636, 0.976, 0.745, 0.645, 0.971, 0.754, 0.655, + 0.967, 0.763, 0.663, 0.963, 0.772, 0.672, 0.959, 0.781, 0.681, 0.955, + 0.790, 0.690, 0.951, 0.799, 0.699, 0.948, 0.808, 0.707, 0.944, 0.818, + 0.716, 0.941, 0.827, 0.724, 0.938, 0.836, 0.732, 0.935, 0.846, 0.741, + 0.933, 0.855, 0.749, 0.930, 0.864, 0.757, 0.928, 0.874, 0.765, 0.925, + 0.883, 0.772, 0.923, 0.892, 0.780, 0.920, 0.901, 0.787, 0.917, 0.910, + 0.793, 0.914, 0.918, 0.800, 0.911, 0.927, 0.805, 0.908, 0.935, 0.811, + 0.904, 0.942, 0.815, 0.899, 0.950, 0.819, 0.894, 0.956, 0.821, 0.887, + 0.963, 0.823, 0.880, 0.968, 0.824, 0.871, 0.973, 0.824, 0.860, 0.977, + 0.822, 0.849, 0.980, 0.820, 0.835, 0.982, 0.817, 0.820, 0.983, 0.812, + 0.804, 0.983, 0.808, 0.786, 0.983, 0.802, 0.768, 0.981, 0.796, 0.749, + 0.979, 0.789, 0.729, 0.977, 0.783, 0.709, 0.974, 0.776, 0.688, 0.970, + 0.768, 0.667, 0.967, 0.761, 0.645, 0.963, 0.753, 0.624, 0.958, 0.745, + 0.602, 0.954, 0.737, 0.580, 0.949, 0.729, 0.558, 0.944, 0.721, 0.536, + 0.939, 0.713, 0.514, 0.934, 0.704, 0.492, 0.929, 0.696, 0.470, 0.924, + 0.688, 0.448, 0.919, 0.680, 0.426, 0.914, 0.671, 0.404, 0.908, 0.663, + 0.381, 0.903, 0.655, 0.359, 0.898, 0.646, 0.336, 0.893, 0.638, 0.313, + 0.887, 0.629, 0.290, 0.882, 0.621, 0.266, 0.876, 0.613, 0.241, 0.871, + 0.604, 0.216, 0.866, 0.596, 0.189, 0.708, 0.592, 0.997, 0.716, 0.601, + 0.991, 0.725, 0.611, 0.985, 0.733, 0.620, 0.980, 0.742, 0.629, 0.975, + 0.751, 0.638, 0.970, 0.759, 0.647, 0.966, 0.768, 0.656, 0.961, 0.777, + 0.665, 0.957, 0.786, 0.673, 0.953, 0.795, 0.682, 0.949, 0.804, 0.690, + 0.946, 0.813, 0.699, 0.942, 0.822, 0.707, 0.939, 0.831, 0.715, 0.936, + 0.840, 0.723, 0.933, 0.849, 0.731, 0.930, 0.859, 0.739, 0.927, 0.868, + 0.747, 0.924, 0.876, 0.754, 0.921, 0.885, 0.762, 0.918, 0.894, 0.769, + 0.915, 0.903, 0.775, 0.912, 0.911, 0.782, 0.909, 0.920, 0.787, 0.905, + 0.928, 0.793, 0.901, 0.935, 0.798, 0.896, 0.943, 0.802, 0.891, 0.950, + 0.805, 0.885, 0.956, 0.807, 0.878, 0.962, 0.809, 0.870, 0.967, 0.809, + 0.861, 0.972, 0.809, 0.850, 0.976, 0.808, 0.838, 0.979, 0.805, 0.825, + 0.981, 0.802, 0.810, 0.983, 0.799, 0.795, 0.983, 0.794, 0.778, 0.983, + 0.789, 0.760, 0.982, 0.783, 0.741, 0.981, 0.777, 0.721, 0.979, 0.771, + 0.701, 0.976, 0.764, 0.681, 0.973, 0.757, 0.660, 0.970, 0.750, 0.639, + 0.966, 0.742, 0.618, 0.962, 0.735, 0.597, 0.958, 0.727, 0.575, 0.953, + 0.719, 0.553, 0.949, 0.711, 0.532, 0.944, 0.703, 0.510, 0.939, 0.695, + 0.488, 0.934, 0.687, 0.466, 0.929, 0.679, 0.444, 0.924, 0.671, 0.422, + 0.919, 0.663, 0.400, 0.914, 0.654, 0.378, 0.909, 0.646, 0.355, 0.903, + 0.638, 0.333, 0.898, 0.630, 0.310, 0.893, 0.621, 0.287, 0.887, 0.613, + 0.263, 0.882, 0.605, 0.238, 0.877, 0.597, 0.213, 0.871, 0.589, 0.186, + 0.715, 0.584, 0.996, 0.723, 0.593, 0.990, 0.731, 0.603, 0.984, 0.739, + 0.612, 0.979, 0.748, 0.621, 0.974, 0.756, 0.630, 0.969, 0.765, 0.639, + 0.964, 0.774, 0.648, 0.960, 0.782, 0.656, 0.955, 0.791, 0.665, 0.951, + 0.800, 0.673, 0.947, 0.809, 0.682, 0.943, 0.818, 0.690, 0.940, 0.826, + 0.698, 0.936, 0.835, 0.706, 0.933, 0.844, 0.714, 0.930, 0.853, 0.722, + 0.926, 0.862, 0.729, 0.923, 0.871, 0.737, 0.920, 0.879, 0.744, 0.917, + 0.888, 0.751, 0.913, 0.896, 0.758, 0.910, 0.905, 0.764, 0.906, 0.913, + 0.770, 0.903, 0.921, 0.775, 0.898, 0.929, 0.780, 0.894, 0.936, 0.784, + 0.889, 0.943, 0.788, 0.883, 0.950, 0.791, 0.877, 0.956, 0.793, 0.869, + 0.962, 0.794, 0.861, 0.967, 0.795, 0.851, 0.971, 0.794, 0.841, 0.975, + 0.793, 0.829, 0.978, 0.791, 0.815, 0.981, 0.788, 0.801, 0.982, 0.785, + 0.785, 0.983, 0.780, 0.769, 0.983, 0.776, 0.751, 0.983, 0.770, 0.733, + 0.982, 0.764, 0.714, 0.980, 0.758, 0.694, 0.978, 0.752, 0.674, 0.975, + 0.745, 0.654, 0.972, 0.738, 0.633, 0.969, 0.731, 0.612, 0.965, 0.724, + 0.591, 0.961, 0.716, 0.570, 0.957, 0.709, 0.548, 0.953, 0.701, 0.527, + 0.948, 0.693, 0.505, 0.943, 0.685, 0.484, 0.939, 0.678, 0.462, 0.934, + 0.670, 0.440, 0.929, 0.662, 0.418, 0.924, 0.654, 0.396, 0.919, 0.646, + 0.374, 0.914, 0.637, 0.352, 0.908, 0.629, 0.329, 0.903, 0.621, 0.307, + 0.898, 0.613, 0.283, 0.893, 0.605, 0.260, 0.887, 0.597, 0.235, 0.882, + 0.589, 0.210, 0.876, 0.581, 0.183, 0.721, 0.576, 0.995, 0.729, 0.585, + 0.989, 0.737, 0.595, 0.983, 0.745, 0.604, 0.978, 0.754, 0.613, 0.973, + 0.762, 0.622, 0.968, 0.770, 0.631, 0.963, 0.779, 0.639, 0.958, 0.787, + 0.648, 0.954, 0.796, 0.656, 0.949, 0.805, 0.665, 0.945, 0.813, 0.673, + 0.941, 0.822, 0.681, 0.937, 0.830, 0.689, 0.933, 0.839, 0.697, 0.930, + 0.848, 0.704, 0.926, 0.856, 0.712, 0.923, 0.865, 0.719, 0.919, 0.873, + 0.726, 0.916, 0.882, 0.733, 0.912, 0.890, 0.740, 0.908, 0.898, 0.746, + 0.905, 0.906, 0.752, 0.901, 0.914, 0.757, 0.896, 0.922, 0.762, 0.892, + 0.929, 0.767, 0.887, 0.937, 0.771, 0.881, 0.943, 0.774, 0.875, 0.950, + 0.777, 0.868, 0.956, 0.779, 0.860, 0.961, 0.780, 0.851, 0.966, 0.780, + 0.842, 0.971, 0.780, 0.831, 0.975, 0.779, 0.819, 0.978, 0.777, 0.806, + 0.980, 0.774, 0.791, 0.982, 0.771, 0.776, 0.983, 0.767, 0.760, 0.984, + 0.762, 0.742, 0.983, 0.757, 0.725, 0.983, 0.752, 0.706, 0.981, 0.746, + 0.687, 0.979, 0.740, 0.667, 0.977, 0.733, 0.647, 0.974, 0.727, 0.627, + 0.971, 0.720, 0.606, 0.968, 0.713, 0.585, 0.964, 0.706, 0.564, 0.960, + 0.698, 0.543, 0.956, 0.691, 0.522, 0.952, 0.683, 0.501, 0.947, 0.676, + 0.479, 0.943, 0.668, 0.458, 0.938, 0.660, 0.436, 0.933, 0.652, 0.414, + 0.928, 0.644, 0.392, 0.923, 0.636, 0.370, 0.918, 0.629, 0.348, 0.913, + 0.621, 0.326, 0.908, 0.613, 0.303, 0.903, 0.605, 0.280, 0.897, 0.597, + 0.257, 0.892, 0.589, 0.232, 0.887, 0.581, 0.207, 0.881, 0.573, 0.180, + 0.727, 0.568, 0.994, 0.735, 0.577, 0.988, 0.743, 0.586, 0.982, 0.751, + 0.595, 0.977, 0.759, 0.604, 0.971, 0.767, 0.613, 0.966, 0.776, 0.622, + 0.961, 0.784, 0.630, 0.956, 0.792, 0.639, 0.951, 0.801, 0.647, 0.947, + 0.809, 0.655, 0.943, 0.817, 0.663, 0.938, 0.826, 0.671, 0.934, 0.834, + 0.679, 0.930, 0.843, 0.687, 0.927, 0.851, 0.694, 0.923, 0.859, 0.702, + 0.919, 0.868, 0.709, 0.915, 0.876, 0.715, 0.911, 0.884, 0.722, 0.907, + 0.892, 0.728, 0.903, 0.900, 0.734, 0.899, 0.908, 0.740, 0.895, 0.916, + 0.745, 0.890, 0.923, 0.750, 0.885, 0.930, 0.754, 0.879, 0.937, 0.758, + 0.873, 0.944, 0.761, 0.867, 0.950, 0.763, 0.859, 0.956, 0.765, 0.851, + 0.961, 0.766, 0.842, 0.966, 0.766, 0.832, 0.970, 0.766, 0.821, 0.974, + 0.764, 0.809, 0.977, 0.762, 0.796, 0.980, 0.760, 0.782, 0.982, 0.757, + 0.767, 0.983, 0.753, 0.751, 0.984, 0.749, 0.734, 0.984, 0.744, 0.716, + 0.983, 0.739, 0.698, 0.982, 0.733, 0.679, 0.980, 0.727, 0.660, 0.978, + 0.721, 0.640, 0.976, 0.715, 0.620, 0.973, 0.708, 0.600, 0.970, 0.701, + 0.579, 0.967, 0.694, 0.559, 0.963, 0.687, 0.538, 0.959, 0.680, 0.517, + 0.955, 0.673, 0.496, 0.951, 0.665, 0.474, 0.946, 0.658, 0.453, 0.942, + 0.650, 0.432, 0.937, 0.643, 0.410, 0.932, 0.635, 0.388, 0.927, 0.627, + 0.367, 0.922, 0.619, 0.345, 0.917, 0.612, 0.322, 0.912, 0.604, 0.300, + 0.907, 0.596, 0.277, 0.902, 0.588, 0.253, 0.897, 0.580, 0.229, 0.891, + 0.572, 0.204, 0.886, 0.564, 0.177, 0.733, 0.559, 0.993, 0.741, 0.568, + 0.987, 0.749, 0.577, 0.981, 0.757, 0.587, 0.975, 0.765, 0.595, 0.970, + 0.773, 0.604, 0.964, 0.781, 0.613, 0.959, 0.789, 0.621, 0.954, 0.797, + 0.630, 0.949, 0.805, 0.638, 0.945, 0.813, 0.646, 0.940, 0.821, 0.654, + 0.936, 0.830, 0.662, 0.931, 0.838, 0.669, 0.927, 0.846, 0.677, 0.923, + 0.854, 0.684, 0.919, 0.862, 0.691, 0.915, 0.870, 0.698, 0.911, 0.879, + 0.704, 0.907, 0.886, 0.711, 0.902, 0.894, 0.717, 0.898, 0.902, 0.722, + 0.893, 0.910, 0.727, 0.889, 0.917, 0.732, 0.883, 0.924, 0.737, 0.878, + 0.931, 0.741, 0.872, 0.938, 0.744, 0.866, 0.944, 0.747, 0.859, 0.950, + 0.749, 0.851, 0.956, 0.751, 0.842, 0.961, 0.751, 0.833, 0.966, 0.752, + 0.823, 0.970, 0.751, 0.812, 0.974, 0.750, 0.800, 0.977, 0.748, 0.787, + 0.979, 0.746, 0.773, 0.981, 0.743, 0.758, 0.983, 0.739, 0.742, 0.984, + 0.735, 0.725, 0.984, 0.731, 0.708, 0.983, 0.726, 0.690, 0.983, 0.721, + 0.672, 0.981, 0.715, 0.653, 0.980, 0.709, 0.633, 0.977, 0.703, 0.614, + 0.975, 0.697, 0.594, 0.972, 0.690, 0.573, 0.969, 0.683, 0.553, 0.965, + 0.676, 0.532, 0.962, 0.669, 0.512, 0.958, 0.662, 0.491, 0.954, 0.655, + 0.470, 0.949, 0.648, 0.448, 0.945, 0.640, 0.427, 0.941, 0.633, 0.406, + 0.936, 0.625, 0.384, 0.931, 0.618, 0.363, 0.926, 0.610, 0.341, 0.921, + 0.602, 0.319, 0.916, 0.595, 0.296, 0.911, 0.587, 0.273, 0.906, 0.579, + 0.250, 0.901, 0.571, 0.226, 0.896, 0.563, 0.201, 0.890, 0.556, 0.174, + 0.739, 0.550, 0.992, 0.747, 0.559, 0.986, 0.754, 0.568, 0.980, 0.762, + 0.577, 0.974, 0.770, 0.586, 0.968, 0.778, 0.595, 0.962, 0.785, 0.603, + 0.957, 0.793, 0.612, 0.952, 0.801, 0.620, 0.947, 0.809, 0.628, 0.942, + 0.817, 0.636, 0.937, 0.825, 0.644, 0.933, 0.833, 0.651, 0.928, 0.841, + 0.659, 0.924, 0.849, 0.666, 0.919, 0.857, 0.673, 0.915, 0.865, 0.680, + 0.911, 0.873, 0.686, 0.906, 0.881, 0.693, 0.902, 0.889, 0.699, 0.897, + 0.896, 0.705, 0.892, 0.904, 0.710, 0.887, 0.911, 0.715, 0.882, 0.918, + 0.720, 0.877, 0.925, 0.724, 0.871, 0.932, 0.727, 0.865, 0.938, 0.730, + 0.858, 0.944, 0.733, 0.850, 0.950, 0.735, 0.842, 0.956, 0.736, 0.834, + 0.961, 0.737, 0.824, 0.965, 0.737, 0.814, 0.970, 0.737, 0.802, 0.973, + 0.736, 0.790, 0.976, 0.734, 0.777, 0.979, 0.732, 0.763, 0.981, 0.729, + 0.749, 0.982, 0.726, 0.733, 0.983, 0.722, 0.717, 0.984, 0.717, 0.700, + 0.984, 0.713, 0.682, 0.983, 0.708, 0.664, 0.982, 0.702, 0.645, 0.980, + 0.697, 0.626, 0.979, 0.691, 0.607, 0.976, 0.685, 0.587, 0.974, 0.678, + 0.567, 0.971, 0.672, 0.547, 0.967, 0.665, 0.527, 0.964, 0.658, 0.506, + 0.960, 0.651, 0.485, 0.956, 0.644, 0.465, 0.952, 0.637, 0.444, 0.948, + 0.630, 0.423, 0.944, 0.623, 0.401, 0.939, 0.615, 0.380, 0.934, 0.608, + 0.358, 0.930, 0.600, 0.337, 0.925, 0.593, 0.315, 0.920, 0.585, 0.292, + 0.915, 0.578, 0.270, 0.910, 0.570, 0.246, 0.905, 0.562, 0.222, 0.899, + 0.555, 0.197, 0.894, 0.547, 0.171, 0.745, 0.540, 0.991, 0.752, 0.550, + 0.984, 0.760, 0.559, 0.978, 0.767, 0.568, 0.972, 0.775, 0.577, 0.966, + 0.782, 0.585, 0.960, 0.790, 0.594, 0.955, 0.798, 0.602, 0.950, 0.806, + 0.610, 0.944, 0.813, 0.618, 0.939, 0.821, 0.626, 0.934, 0.829, 0.634, + 0.930, 0.837, 0.641, 0.925, 0.845, 0.648, 0.920, 0.852, 0.655, 0.915, + 0.860, 0.662, 0.911, 0.868, 0.669, 0.906, 0.876, 0.675, 0.901, 0.883, + 0.681, 0.897, 0.891, 0.687, 0.892, 0.898, 0.692, 0.887, 0.905, 0.697, + 0.881, 0.912, 0.702, 0.876, 0.919, 0.707, 0.870, 0.926, 0.710, 0.864, + 0.932, 0.714, 0.857, 0.939, 0.717, 0.850, 0.945, 0.719, 0.842, 0.950, + 0.721, 0.834, 0.956, 0.722, 0.825, 0.961, 0.723, 0.815, 0.965, 0.723, + 0.804, 0.969, 0.723, 0.793, 0.973, 0.722, 0.781, 0.976, 0.720, 0.768, + 0.978, 0.718, 0.754, 0.981, 0.715, 0.739, 0.982, 0.712, 0.724, 0.983, + 0.708, 0.708, 0.984, 0.704, 0.691, 0.984, 0.700, 0.674, 0.983, 0.695, + 0.656, 0.982, 0.690, 0.638, 0.981, 0.684, 0.619, 0.979, 0.679, 0.600, + 0.977, 0.673, 0.581, 0.975, 0.666, 0.561, 0.972, 0.660, 0.541, 0.969, + 0.654, 0.521, 0.966, 0.647, 0.501, 0.962, 0.640, 0.480, 0.959, 0.634, + 0.459, 0.955, 0.627, 0.439, 0.951, 0.620, 0.418, 0.946, 0.612, 0.397, + 0.942, 0.605, 0.376, 0.937, 0.598, 0.354, 0.933, 0.591, 0.333, 0.928, + 0.583, 0.311, 0.923, 0.576, 0.289, 0.918, 0.568, 0.266, 0.913, 0.561, + 0.243, 0.908, 0.553, 0.219, 0.903, 0.546, 0.194, 0.898, 0.538, 0.167, + 0.750, 0.531, 0.989, 0.757, 0.540, 0.983, 0.765, 0.549, 0.976, 0.772, + 0.558, 0.970, 0.779, 0.567, 0.964, 0.787, 0.575, 0.958, 0.794, 0.584, + 0.953, 0.802, 0.592, 0.947, 0.810, 0.600, 0.942, 0.817, 0.608, 0.936, + 0.825, 0.615, 0.931, 0.833, 0.623, 0.926, 0.840, 0.630, 0.921, 0.848, + 0.637, 0.916, 0.855, 0.644, 0.911, 0.863, 0.651, 0.907, 0.870, 0.657, + 0.902, 0.878, 0.663, 0.897, 0.885, 0.669, 0.891, 0.893, 0.675, 0.886, + 0.900, 0.680, 0.881, 0.907, 0.685, 0.875, 0.914, 0.689, 0.869, 0.920, + 0.693, 0.863, 0.927, 0.697, 0.857, 0.933, 0.700, 0.850, 0.939, 0.703, + 0.842, 0.945, 0.705, 0.834, 0.950, 0.707, 0.825, 0.956, 0.708, 0.816, + 0.960, 0.709, 0.806, 0.965, 0.709, 0.795, 0.969, 0.708, 0.784, 0.972, + 0.707, 0.772, 0.975, 0.706, 0.759, 0.978, 0.704, 0.745, 0.980, 0.701, + 0.731, 0.982, 0.698, 0.715, 0.983, 0.694, 0.699, 0.984, 0.691, 0.683, + 0.984, 0.686, 0.666, 0.983, 0.682, 0.648, 0.983, 0.677, 0.630, 0.982, + 0.672, 0.612, 0.980, 0.666, 0.593, 0.978, 0.660, 0.574, 0.976, 0.655, + 0.554, 0.973, 0.648, 0.535, 0.971, 0.642, 0.515, 0.967, 0.636, 0.495, + 0.964, 0.629, 0.475, 0.961, 0.623, 0.454, 0.957, 0.616, 0.434, 0.953, + 0.609, 0.413, 0.949, 0.602, 0.392, 0.944, 0.595, 0.371, 0.940, 0.588, + 0.350, 0.936, 0.580, 0.328, 0.931, 0.573, 0.307, 0.926, 0.566, 0.285, + 0.921, 0.559, 0.262, 0.916, 0.551, 0.239, 0.911, 0.544, 0.215, 0.906, + 0.536, 0.190, 0.901, 0.529, 0.164, 0.755, 0.521, 0.988, 0.762, 0.530, + 0.981, 0.770, 0.539, 0.975, 0.777, 0.548, 0.968, 0.784, 0.557, 0.962, + 0.791, 0.565, 0.956, 0.799, 0.573, 0.950, 0.806, 0.582, 0.944, 0.814, + 0.589, 0.939, 0.821, 0.597, 0.933, 0.828, 0.605, 0.928, 0.836, 0.612, + 0.923, 0.843, 0.619, 0.918, 0.851, 0.626, 0.912, 0.858, 0.633, 0.907, + 0.866, 0.639, 0.902, 0.873, 0.645, 0.897, 0.880, 0.651, 0.892, 0.887, + 0.657, 0.886, 0.894, 0.662, 0.881, 0.901, 0.667, 0.875, 0.908, 0.672, + 0.869, 0.915, 0.676, 0.863, 0.921, 0.680, 0.856, 0.928, 0.684, 0.849, + 0.934, 0.687, 0.842, 0.940, 0.689, 0.834, 0.945, 0.691, 0.826, 0.951, + 0.693, 0.817, 0.956, 0.694, 0.807, 0.960, 0.695, 0.797, 0.964, 0.695, + 0.787, 0.968, 0.694, 0.775, 0.972, 0.693, 0.763, 0.975, 0.692, 0.750, + 0.977, 0.690, 0.736, 0.980, 0.687, 0.722, 0.981, 0.684, 0.707, 0.982, + 0.681, 0.691, 0.983, 0.677, 0.675, 0.984, 0.673, 0.658, 0.983, 0.669, + 0.641, 0.983, 0.664, 0.623, 0.982, 0.659, 0.605, 0.980, 0.654, 0.586, + 0.979, 0.648, 0.567, 0.977, 0.642, 0.548, 0.974, 0.637, 0.529, 0.972, + 0.630, 0.509, 0.969, 0.624, 0.489, 0.966, 0.618, 0.469, 0.962, 0.611, + 0.449, 0.959, 0.605, 0.429, 0.955, 0.598, 0.408, 0.951, 0.591, 0.387, + 0.947, 0.584, 0.367, 0.942, 0.577, 0.346, 0.938, 0.570, 0.324, 0.933, + 0.563, 0.303, 0.929, 0.556, 0.281, 0.924, 0.549, 0.258, 0.919, 0.541, + 0.235, 0.914, 0.534, 0.212, 0.909, 0.527, 0.187, 0.904, 0.519, 0.160, + 0.760, 0.510, 0.986, 0.767, 0.520, 0.979, 0.774, 0.529, 0.973, 0.781, + 0.538, 0.966, 0.788, 0.546, 0.960, 0.796, 0.555, 0.954, 0.803, 0.563, + 0.948, 0.810, 0.571, 0.942, 0.817, 0.579, 0.936, 0.825, 0.586, 0.930, + 0.832, 0.594, 0.925, 0.839, 0.601, 0.919, 0.846, 0.608, 0.914, 0.854, + 0.615, 0.908, 0.861, 0.621, 0.903, 0.868, 0.627, 0.897, 0.875, 0.633, + 0.892, 0.882, 0.639, 0.886, 0.889, 0.645, 0.881, 0.896, 0.650, 0.875, + 0.903, 0.655, 0.869, 0.909, 0.659, 0.863, 0.916, 0.663, 0.856, 0.922, + 0.667, 0.849, 0.928, 0.670, 0.842, 0.934, 0.673, 0.834, 0.940, 0.675, + 0.826, 0.945, 0.677, 0.818, 0.951, 0.679, 0.809, 0.955, 0.680, 0.799, + 0.960, 0.680, 0.789, 0.964, 0.680, 0.778, 0.968, 0.680, 0.766, 0.971, + 0.679, 0.754, 0.974, 0.677, 0.741, 0.977, 0.676, 0.727, 0.979, 0.673, + 0.713, 0.981, 0.670, 0.698, 0.982, 0.667, 0.682, 0.983, 0.664, 0.666, + 0.983, 0.660, 0.650, 0.983, 0.655, 0.633, 0.983, 0.651, 0.615, 0.982, + 0.646, 0.597, 0.981, 0.641, 0.579, 0.979, 0.636, 0.560, 0.977, 0.630, + 0.541, 0.975, 0.625, 0.522, 0.973, 0.619, 0.503, 0.970, 0.613, 0.483, + 0.967, 0.606, 0.463, 0.964, 0.600, 0.443, 0.960, 0.594, 0.423, 0.956, + 0.587, 0.403, 0.953, 0.580, 0.383, 0.949, 0.574, 0.362, 0.944, 0.567, + 0.341, 0.940, 0.560, 0.320, 0.936, 0.553, 0.298, 0.931, 0.546, 0.277, + 0.926, 0.539, 0.254, 0.922, 0.532, 0.232, 0.917, 0.524, 0.208, 0.912, + 0.517, 0.183, 0.906, 0.510, 0.156, 0.765, 0.499, 0.985, 0.772, 0.509, + 0.978, 0.779, 0.518, 0.971, 0.786, 0.527, 0.964, 0.793, 0.535, 0.957, + 0.800, 0.544, 0.951, 0.807, 0.552, 0.945, 0.814, 0.560, 0.939, 0.821, + 0.568, 0.933, 0.828, 0.575, 0.927, 0.835, 0.582, 0.921, 0.842, 0.589, + 0.915, 0.849, 0.596, 0.910, 0.856, 0.603, 0.904, 0.863, 0.609, 0.898, + 0.870, 0.615, 0.893, 0.877, 0.621, 0.887, 0.884, 0.627, 0.881, 0.891, + 0.632, 0.875, 0.898, 0.637, 0.869, 0.904, 0.642, 0.863, 0.911, 0.646, + 0.856, 0.917, 0.650, 0.849, 0.923, 0.653, 0.842, 0.929, 0.657, 0.835, + 0.935, 0.659, 0.827, 0.940, 0.662, 0.818, 0.946, 0.663, 0.810, 0.951, + 0.665, 0.800, 0.955, 0.666, 0.790, 0.960, 0.666, 0.780, 0.964, 0.666, + 0.769, 0.967, 0.666, 0.757, 0.971, 0.665, 0.745, 0.974, 0.663, 0.732, + 0.976, 0.662, 0.718, 0.978, 0.659, 0.704, 0.980, 0.657, 0.689, 0.981, + 0.654, 0.674, 0.982, 0.650, 0.658, 0.983, 0.646, 0.642, 0.983, 0.642, + 0.625, 0.983, 0.638, 0.608, 0.982, 0.633, 0.590, 0.981, 0.628, 0.572, + 0.979, 0.623, 0.553, 0.978, 0.618, 0.535, 0.976, 0.612, 0.516, 0.973, + 0.607, 0.497, 0.971, 0.601, 0.477, 0.968, 0.595, 0.458, 0.965, 0.589, + 0.438, 0.961, 0.582, 0.418, 0.958, 0.576, 0.398, 0.954, 0.569, 0.378, + 0.950, 0.563, 0.357, 0.946, 0.556, 0.336, 0.942, 0.549, 0.315, 0.938, + 0.542, 0.294, 0.933, 0.536, 0.273, 0.928, 0.529, 0.250, 0.924, 0.522, + 0.228, 0.919, 0.514, 0.204, 0.914, 0.507, 0.179, 0.909, 0.500, 0.153, + 0.770, 0.488, 0.983, 0.777, 0.498, 0.976, 0.783, 0.507, 0.969, 0.790, + 0.516, 0.962, 0.797, 0.524, 0.955, 0.804, 0.533, 0.948, 0.811, 0.541, + 0.942, 0.817, 0.549, 0.936, 0.824, 0.556, 0.930, 0.831, 0.564, 0.924, + 0.838, 0.571, 0.918, 0.845, 0.578, 0.912, 0.852, 0.584, 0.906, 0.859, + 0.591, 0.900, 0.866, 0.597, 0.894, 0.873, 0.603, 0.888, 0.879, 0.609, + 0.882, 0.886, 0.614, 0.876, 0.893, 0.619, 0.869, 0.899, 0.624, 0.863, + 0.906, 0.629, 0.856, 0.912, 0.633, 0.850, 0.918, 0.637, 0.843, 0.924, + 0.640, 0.835, 0.930, 0.643, 0.827, 0.935, 0.646, 0.819, 0.941, 0.648, + 0.811, 0.946, 0.649, 0.802, 0.951, 0.651, 0.792, 0.955, 0.652, 0.782, + 0.959, 0.652, 0.771, 0.963, 0.652, 0.760, 0.967, 0.652, 0.749, 0.970, + 0.651, 0.736, 0.973, 0.649, 0.723, 0.976, 0.647, 0.710, 0.978, 0.645, + 0.696, 0.980, 0.643, 0.681, 0.981, 0.640, 0.666, 0.982, 0.637, 0.650, + 0.982, 0.633, 0.634, 0.983, 0.629, 0.617, 0.982, 0.625, 0.600, 0.982, + 0.620, 0.582, 0.981, 0.616, 0.565, 0.979, 0.611, 0.546, 0.978, 0.605, + 0.528, 0.976, 0.600, 0.509, 0.974, 0.595, 0.490, 0.971, 0.589, 0.471, + 0.969, 0.583, 0.452, 0.966, 0.577, 0.432, 0.962, 0.571, 0.413, 0.959, + 0.565, 0.393, 0.955, 0.558, 0.373, 0.952, 0.552, 0.352, 0.948, 0.545, + 0.332, 0.943, 0.539, 0.311, 0.939, 0.532, 0.290, 0.935, 0.525, 0.268, + 0.930, 0.518, 0.246, 0.926, 0.511, 0.224, 0.921, 0.504, 0.200, 0.916, + 0.497, 0.175, 0.911, 0.490, 0.149, 0.775, 0.477, 0.981, 0.781, 0.486, + 0.974, 0.788, 0.495, 0.966, 0.794, 0.504, 0.959, 0.801, 0.513, 0.952, + 0.808, 0.521, 0.946, 0.814, 0.529, 0.939, 0.821, 0.537, 0.933, 0.828, + 0.545, 0.926, 0.835, 0.552, 0.920, 0.841, 0.559, 0.914, 0.848, 0.566, + 0.908, 0.855, 0.572, 0.901, 0.862, 0.579, 0.895, 0.868, 0.585, 0.889, + 0.875, 0.591, 0.883, 0.881, 0.596, 0.877, 0.888, 0.601, 0.870, 0.894, + 0.606, 0.864, 0.901, 0.611, 0.857, 0.907, 0.615, 0.850, 0.913, 0.619, + 0.843, 0.919, 0.623, 0.836, 0.925, 0.626, 0.828, 0.930, 0.629, 0.820, + 0.936, 0.632, 0.812, 0.941, 0.634, 0.803, 0.946, 0.635, 0.794, 0.951, + 0.637, 0.784, 0.955, 0.637, 0.774, 0.959, 0.638, 0.763, 0.963, 0.638, + 0.752, 0.967, 0.637, 0.740, 0.970, 0.636, 0.728, 0.973, 0.635, 0.715, + 0.975, 0.633, 0.701, 0.977, 0.631, 0.687, 0.979, 0.629, 0.672, 0.980, + 0.626, 0.657, 0.981, 0.623, 0.642, 0.982, 0.619, 0.626, 0.982, 0.616, + 0.609, 0.982, 0.612, 0.592, 0.981, 0.607, 0.575, 0.981, 0.603, 0.557, + 0.979, 0.598, 0.540, 0.978, 0.593, 0.521, 0.976, 0.588, 0.503, 0.974, + 0.582, 0.484, 0.972, 0.577, 0.465, 0.969, 0.571, 0.446, 0.966, 0.565, + 0.427, 0.963, 0.559, 0.407, 0.960, 0.553, 0.387, 0.956, 0.547, 0.368, + 0.953, 0.541, 0.347, 0.949, 0.534, 0.327, 0.945, 0.528, 0.306, 0.941, + 0.521, 0.285, 0.936, 0.514, 0.264, 0.932, 0.508, 0.242, 0.927, 0.501, + 0.220, 0.922, 0.494, 0.196, 0.918, 0.487, 0.172, 0.913, 0.480, 0.145, + 0.779, 0.465, 0.979, 0.785, 0.474, 0.971, 0.792, 0.484, 0.964, 0.798, + 0.492, 0.957, 0.805, 0.501, 0.950, 0.811, 0.509, 0.943, 0.818, 0.517, + 0.936, 0.824, 0.525, 0.929, 0.831, 0.533, 0.923, 0.838, 0.540, 0.916, + 0.844, 0.547, 0.910, 0.851, 0.554, 0.904, 0.857, 0.560, 0.897, 0.864, + 0.566, 0.891, 0.870, 0.572, 0.884, 0.877, 0.578, 0.878, 0.883, 0.583, + 0.871, 0.890, 0.589, 0.865, 0.896, 0.593, 0.858, 0.902, 0.598, 0.851, + 0.908, 0.602, 0.844, 0.914, 0.606, 0.837, 0.920, 0.609, 0.829, 0.925, + 0.613, 0.821, 0.931, 0.615, 0.813, 0.936, 0.618, 0.804, 0.941, 0.620, + 0.795, 0.946, 0.621, 0.786, 0.950, 0.622, 0.776, 0.955, 0.623, 0.765, + 0.959, 0.624, 0.755, 0.963, 0.624, 0.743, 0.966, 0.623, 0.731, 0.969, + 0.622, 0.719, 0.972, 0.621, 0.706, 0.974, 0.619, 0.693, 0.976, 0.617, + 0.679, 0.978, 0.615, 0.664, 0.979, 0.612, 0.649, 0.980, 0.609, 0.634, + 0.981, 0.606, 0.618, 0.981, 0.602, 0.601, 0.981, 0.598, 0.585, 0.981, + 0.594, 0.568, 0.980, 0.590, 0.550, 0.979, 0.585, 0.533, 0.978, 0.580, + 0.515, 0.976, 0.575, 0.496, 0.974, 0.570, 0.478, 0.972, 0.565, 0.459, + 0.969, 0.559, 0.440, 0.967, 0.553, 0.421, 0.964, 0.547, 0.402, 0.960, + 0.542, 0.382, 0.957, 0.535, 0.362, 0.953, 0.529, 0.342, 0.950, 0.523, + 0.322, 0.946, 0.517, 0.302, 0.942, 0.510, 0.281, 0.937, 0.504, 0.260, + 0.933, 0.497, 0.238, 0.929, 0.490, 0.216, 0.924, 0.484, 0.192, 0.919, + 0.477, 0.168, 0.914, 0.470, 0.141, 0.783, 0.453, 0.977, 0.789, 0.462, + 0.969, 0.796, 0.471, 0.962, 0.802, 0.480, 0.954, 0.808, 0.489, 0.947, + 0.815, 0.497, 0.940, 0.821, 0.505, 0.933, 0.828, 0.513, 0.926, 0.834, + 0.520, 0.919, 0.840, 0.527, 0.913, 0.847, 0.534, 0.906, 0.853, 0.541, + 0.899, 0.860, 0.548, 0.893, 0.866, 0.554, 0.886, 0.873, 0.560, 0.880, + 0.879, 0.565, 0.873, 0.885, 0.570, 0.866, 0.891, 0.575, 0.859, 0.897, + 0.580, 0.852, 0.903, 0.585, 0.845, 0.909, 0.589, 0.838, 0.915, 0.592, + 0.830, 0.921, 0.596, 0.822, 0.926, 0.599, 0.814, 0.931, 0.601, 0.805, + 0.936, 0.604, 0.797, 0.941, 0.606, 0.787, 0.946, 0.607, 0.778, 0.950, + 0.608, 0.768, 0.955, 0.609, 0.757, 0.958, 0.609, 0.746, 0.962, 0.609, + 0.735, 0.965, 0.609, 0.723, 0.968, 0.608, 0.710, 0.971, 0.607, 0.698, + 0.974, 0.605, 0.684, 0.976, 0.603, 0.670, 0.977, 0.601, 0.656, 0.979, + 0.598, 0.641, 0.980, 0.596, 0.626, 0.980, 0.592, 0.610, 0.981, 0.589, + 0.594, 0.981, 0.585, 0.577, 0.980, 0.581, 0.560, 0.980, 0.577, 0.543, + 0.979, 0.572, 0.526, 0.977, 0.568, 0.508, 0.976, 0.563, 0.490, 0.974, + 0.558, 0.471, 0.972, 0.552, 0.453, 0.969, 0.547, 0.434, 0.967, 0.541, + 0.415, 0.964, 0.536, 0.396, 0.961, 0.530, 0.377, 0.958, 0.524, 0.357, + 0.954, 0.518, 0.337, 0.950, 0.512, 0.317, 0.947, 0.505, 0.297, 0.943, + 0.499, 0.276, 0.938, 0.493, 0.255, 0.934, 0.486, 0.234, 0.930, 0.480, + 0.211, 0.925, 0.473, 0.188, 0.920, 0.466, 0.164, 0.916, 0.459, 0.137, + 0.787, 0.440, 0.975, 0.793, 0.450, 0.967, 0.799, 0.459, 0.959, 0.806, + 0.468, 0.952, 0.812, 0.476, 0.944, 0.818, 0.485, 0.937, 0.824, 0.493, + 0.930, 0.831, 0.500, 0.923, 0.837, 0.508, 0.916, 0.843, 0.515, 0.909, + 0.850, 0.522, 0.902, 0.856, 0.528, 0.895, 0.862, 0.535, 0.888, 0.868, + 0.541, 0.881, 0.874, 0.547, 0.875, 0.881, 0.552, 0.868, 0.887, 0.557, + 0.861, 0.893, 0.562, 0.853, 0.899, 0.567, 0.846, 0.904, 0.571, 0.839, + 0.910, 0.575, 0.831, 0.916, 0.579, 0.823, 0.921, 0.582, 0.815, 0.926, + 0.585, 0.807, 0.932, 0.587, 0.798, 0.937, 0.590, 0.789, 0.941, 0.591, + 0.780, 0.946, 0.593, 0.770, 0.950, 0.594, 0.760, 0.954, 0.595, 0.749, + 0.958, 0.595, 0.738, 0.962, 0.595, 0.727, 0.965, 0.595, 0.715, 0.968, + 0.594, 0.702, 0.970, 0.593, 0.689, 0.973, 0.591, 0.676, 0.975, 0.589, + 0.662, 0.976, 0.587, 0.648, 0.978, 0.585, 0.633, 0.979, 0.582, 0.618, + 0.980, 0.579, 0.602, 0.980, 0.575, 0.586, 0.980, 0.572, 0.570, 0.980, + 0.568, 0.553, 0.979, 0.564, 0.536, 0.978, 0.559, 0.518, 0.977, 0.555, + 0.501, 0.975, 0.550, 0.483, 0.974, 0.545, 0.465, 0.972, 0.540, 0.447, + 0.969, 0.535, 0.428, 0.967, 0.529, 0.409, 0.964, 0.524, 0.390, 0.961, + 0.518, 0.371, 0.958, 0.512, 0.352, 0.954, 0.506, 0.332, 0.951, 0.500, + 0.312, 0.947, 0.494, 0.292, 0.943, 0.488, 0.272, 0.939, 0.481, 0.251, + 0.935, 0.475, 0.229, 0.931, 0.469, 0.207, 0.926, 0.462, 0.184, 0.921, + 0.455, 0.159, 0.917, 0.449, 0.133, 0.791, 0.427, 0.973, 0.797, 0.437, + 0.964, 0.803, 0.446, 0.957, 0.809, 0.455, 0.949, 0.815, 0.464, 0.941, + 0.821, 0.472, 0.934, 0.827, 0.480, 0.926, 0.834, 0.488, 0.919, 0.840, + 0.495, 0.912, 0.846, 0.502, 0.905, 0.852, 0.509, 0.898, 0.858, 0.515, + 0.891, 0.864, 0.522, 0.884, 0.870, 0.528, 0.877, 0.876, 0.533, 0.870, + 0.882, 0.539, 0.862, 0.888, 0.544, 0.855, 0.894, 0.549, 0.848, 0.900, + 0.553, 0.840, 0.906, 0.557, 0.833, 0.911, 0.561, 0.825, 0.916, 0.565, + 0.817, 0.922, 0.568, 0.808, 0.927, 0.571, 0.800, 0.932, 0.573, 0.791, + 0.937, 0.575, 0.782, 0.941, 0.577, 0.772, 0.946, 0.579, 0.762, 0.950, + 0.580, 0.752, 0.954, 0.580, 0.741, 0.958, 0.581, 0.730, 0.961, 0.581, + 0.718, 0.964, 0.580, 0.706, 0.967, 0.580, 0.694, 0.970, 0.578, 0.681, + 0.972, 0.577, 0.667, 0.974, 0.575, 0.654, 0.976, 0.573, 0.639, 0.977, + 0.571, 0.625, 0.978, 0.568, 0.610, 0.979, 0.565, 0.594, 0.979, 0.562, + 0.578, 0.979, 0.558, 0.562, 0.979, 0.554, 0.545, 0.978, 0.550, 0.529, + 0.977, 0.546, 0.511, 0.976, 0.542, 0.494, 0.975, 0.537, 0.476, 0.973, + 0.532, 0.458, 0.971, 0.527, 0.440, 0.969, 0.522, 0.422, 0.967, 0.517, + 0.403, 0.964, 0.511, 0.385, 0.961, 0.506, 0.366, 0.958, 0.500, 0.347, + 0.955, 0.494, 0.327, 0.951, 0.488, 0.307, 0.947, 0.482, 0.287, 0.944, + 0.476, 0.267, 0.940, 0.470, 0.246, 0.935, 0.464, 0.225, 0.931, 0.458, + 0.203, 0.927, 0.451, 0.180, 0.922, 0.445, 0.155, 0.917, 0.438, 0.129, + 0.795, 0.413, 0.970, 0.801, 0.423, 0.962, 0.807, 0.433, 0.954, 0.813, + 0.442, 0.946, 0.818, 0.450, 0.938, 0.824, 0.459, 0.930, 0.830, 0.467, + 0.923, 0.836, 0.474, 0.915, 0.842, 0.482, 0.908, 0.848, 0.489, 0.901, + 0.854, 0.496, 0.894, 0.860, 0.502, 0.886, 0.866, 0.508, 0.879, 0.872, + 0.514, 0.872, 0.878, 0.520, 0.864, 0.884, 0.525, 0.857, 0.890, 0.530, + 0.850, 0.895, 0.535, 0.842, 0.901, 0.539, 0.834, 0.906, 0.543, 0.826, + 0.912, 0.547, 0.818, 0.917, 0.551, 0.810, 0.922, 0.554, 0.801, 0.927, + 0.557, 0.793, 0.932, 0.559, 0.783, 0.937, 0.561, 0.774, 0.941, 0.563, + 0.764, 0.946, 0.564, 0.754, 0.950, 0.565, 0.744, 0.953, 0.566, 0.733, + 0.957, 0.566, 0.722, 0.960, 0.566, 0.710, 0.963, 0.566, 0.698, 0.966, + 0.565, 0.685, 0.969, 0.564, 0.673, 0.971, 0.563, 0.659, 0.973, 0.561, + 0.645, 0.975, 0.559, 0.631, 0.976, 0.557, 0.617, 0.977, 0.554, 0.602, + 0.978, 0.551, 0.586, 0.978, 0.548, 0.571, 0.978, 0.545, 0.554, 0.978, + 0.541, 0.538, 0.977, 0.537, 0.521, 0.977, 0.533, 0.504, 0.976, 0.529, + 0.487, 0.974, 0.524, 0.470, 0.973, 0.519, 0.452, 0.971, 0.515, 0.434, + 0.969, 0.510, 0.416, 0.966, 0.504, 0.398, 0.964, 0.499, 0.379, 0.961, + 0.494, 0.360, 0.958, 0.488, 0.341, 0.955, 0.482, 0.322, 0.951, 0.477, + 0.302, 0.948, 0.471, 0.283, 0.944, 0.465, 0.262, 0.940, 0.459, 0.242, + 0.936, 0.452, 0.220, 0.932, 0.446, 0.198, 0.927, 0.440, 0.175, 0.923, + 0.434, 0.151, 0.918, 0.427, 0.124, 0.799, 0.399, 0.968, 0.804, 0.409, + 0.959, 0.810, 0.419, 0.951, 0.816, 0.428, 0.943, 0.822, 0.437, 0.935, + 0.827, 0.445, 0.927, 0.833, 0.453, 0.919, 0.839, 0.461, 0.912, 0.845, + 0.468, 0.904, 0.851, 0.475, 0.897, 0.857, 0.482, 0.889, 0.862, 0.489, + 0.882, 0.868, 0.495, 0.874, 0.874, 0.501, 0.867, 0.880, 0.506, 0.859, + 0.885, 0.511, 0.852, 0.891, 0.516, 0.844, 0.897, 0.521, 0.836, 0.902, + 0.525, 0.828, 0.907, 0.529, 0.820, 0.913, 0.533, 0.812, 0.918, 0.537, + 0.803, 0.923, 0.540, 0.794, 0.928, 0.542, 0.785, 0.932, 0.545, 0.776, + 0.937, 0.547, 0.767, 0.941, 0.549, 0.757, 0.945, 0.550, 0.747, 0.949, + 0.551, 0.736, 0.953, 0.552, 0.725, 0.956, 0.552, 0.714, 0.960, 0.552, + 0.702, 0.963, 0.552, 0.690, 0.965, 0.551, 0.677, 0.968, 0.550, 0.664, + 0.970, 0.549, 0.651, 0.972, 0.547, 0.637, 0.974, 0.545, 0.623, 0.975, + 0.543, 0.609, 0.976, 0.540, 0.594, 0.977, 0.537, 0.578, 0.977, 0.534, + 0.563, 0.977, 0.531, 0.547, 0.977, 0.527, 0.531, 0.976, 0.524, 0.514, + 0.976, 0.520, 0.497, 0.975, 0.516, 0.480, 0.973, 0.511, 0.463, 0.972, + 0.507, 0.446, 0.970, 0.502, 0.428, 0.968, 0.497, 0.410, 0.966, 0.492, + 0.392, 0.963, 0.487, 0.373, 0.960, 0.481, 0.355, 0.957, 0.476, 0.336, + 0.954, 0.470, 0.317, 0.951, 0.465, 0.297, 0.947, 0.459, 0.278, 0.944, + 0.453, 0.258, 0.940, 0.447, 0.237, 0.936, 0.441, 0.216, 0.932, 0.435, + 0.194, 0.927, 0.429, 0.171, 0.923, 0.422, 0.147, 0.918, 0.416, 0.120, + 0.802, 0.385, 0.965, 0.808, 0.395, 0.957, 0.813, 0.405, 0.948, 0.819, + 0.414, 0.940, 0.825, 0.423, 0.932, 0.830, 0.431, 0.924, 0.836, 0.439, + 0.916, 0.842, 0.447, 0.908, 0.847, 0.454, 0.900, 0.853, 0.462, 0.892, + 0.859, 0.468, 0.885, 0.864, 0.475, 0.877, 0.870, 0.481, 0.869, 0.876, + 0.487, 0.862, 0.881, 0.492, 0.854, 0.887, 0.498, 0.846, 0.892, 0.502, + 0.838, 0.898, 0.507, 0.830, 0.903, 0.511, 0.822, 0.908, 0.515, 0.814, + 0.913, 0.519, 0.805, 0.918, 0.522, 0.797, 0.923, 0.525, 0.788, 0.928, + 0.528, 0.778, 0.932, 0.530, 0.769, 0.937, 0.532, 0.759, 0.941, 0.534, + 0.749, 0.945, 0.535, 0.739, 0.949, 0.536, 0.728, 0.952, 0.537, 0.717, + 0.956, 0.537, 0.706, 0.959, 0.537, 0.694, 0.962, 0.537, 0.682, 0.964, + 0.536, 0.669, 0.967, 0.536, 0.656, 0.969, 0.534, 0.643, 0.971, 0.533, + 0.629, 0.972, 0.531, 0.615, 0.974, 0.529, 0.601, 0.975, 0.526, 0.586, + 0.975, 0.523, 0.571, 0.976, 0.521, 0.555, 0.976, 0.517, 0.540, 0.976, + 0.514, 0.523, 0.975, 0.510, 0.507, 0.975, 0.506, 0.490, 0.974, 0.502, + 0.474, 0.972, 0.498, 0.456, 0.971, 0.494, 0.439, 0.969, 0.489, 0.421, + 0.967, 0.484, 0.404, 0.965, 0.479, 0.386, 0.963, 0.474, 0.367, 0.960, + 0.469, 0.349, 0.957, 0.464, 0.330, 0.954, 0.458, 0.311, 0.951, 0.453, + 0.292, 0.947, 0.447, 0.273, 0.944, 0.441, 0.253, 0.940, 0.435, 0.232, + 0.936, 0.429, 0.211, 0.932, 0.423, 0.190, 0.927, 0.417, 0.167, 0.923, + 0.411, 0.142, 0.919, 0.405, 0.116, 0.806, 0.370, 0.963, 0.811, 0.380, + 0.954, 0.816, 0.390, 0.945, 0.822, 0.399, 0.937, 0.827, 0.408, 0.929, + 0.833, 0.417, 0.920, 0.838, 0.425, 0.912, 0.844, 0.433, 0.904, 0.850, + 0.440, 0.896, 0.855, 0.447, 0.888, 0.861, 0.454, 0.880, 0.866, 0.461, + 0.872, 0.872, 0.467, 0.865, 0.877, 0.473, 0.857, 0.883, 0.478, 0.849, + 0.888, 0.483, 0.841, 0.893, 0.488, 0.833, 0.899, 0.493, 0.824, 0.904, + 0.497, 0.816, 0.909, 0.501, 0.807, 0.914, 0.505, 0.799, 0.919, 0.508, + 0.790, 0.923, 0.511, 0.781, 0.928, 0.514, 0.771, 0.932, 0.516, 0.762, + 0.937, 0.518, 0.752, 0.941, 0.520, 0.742, 0.945, 0.521, 0.731, 0.948, + 0.522, 0.720, 0.952, 0.523, 0.709, 0.955, 0.523, 0.698, 0.958, 0.523, + 0.686, 0.961, 0.523, 0.674, 0.964, 0.522, 0.661, 0.966, 0.521, 0.648, + 0.968, 0.520, 0.635, 0.970, 0.518, 0.621, 0.971, 0.517, 0.607, 0.972, + 0.514, 0.593, 0.973, 0.512, 0.578, 0.974, 0.509, 0.563, 0.975, 0.507, + 0.548, 0.975, 0.503, 0.532, 0.975, 0.500, 0.516, 0.974, 0.497, 0.500, + 0.974, 0.493, 0.483, 0.973, 0.489, 0.467, 0.971, 0.485, 0.450, 0.970, + 0.480, 0.433, 0.968, 0.476, 0.415, 0.966, 0.471, 0.397, 0.964, 0.466, + 0.380, 0.962, 0.461, 0.362, 0.959, 0.456, 0.343, 0.956, 0.451, 0.325, + 0.953, 0.446, 0.306, 0.950, 0.440, 0.287, 0.947, 0.435, 0.268, 0.943, + 0.429, 0.248, 0.939, 0.423, 0.228, 0.936, 0.417, 0.207, 0.931, 0.411, + 0.185, 0.927, 0.405, 0.162, 0.923, 0.399, 0.138, 0.918, 0.393, 0.111, + 0.809, 0.354, 0.960, 0.814, 0.364, 0.951, 0.819, 0.375, 0.942, 0.825, + 0.384, 0.934, 0.830, 0.393, 0.925, 0.835, 0.402, 0.917, 0.841, 0.410, + 0.908, 0.846, 0.418, 0.900, 0.852, 0.426, 0.892, 0.857, 0.433, 0.884, + 0.863, 0.440, 0.876, 0.868, 0.446, 0.868, 0.873, 0.452, 0.860, 0.879, + 0.458, 0.852, 0.884, 0.464, 0.843, 0.889, 0.469, 0.835, 0.894, 0.474, + 0.827, 0.899, 0.478, 0.818, 0.904, 0.483, 0.810, 0.909, 0.486, 0.801, + 0.914, 0.490, 0.792, 0.919, 0.493, 0.783, 0.923, 0.496, 0.774, 0.928, + 0.499, 0.764, 0.932, 0.501, 0.755, 0.936, 0.503, 0.745, 0.940, 0.505, + 0.734, 0.944, 0.506, 0.724, 0.948, 0.507, 0.713, 0.951, 0.508, 0.701, + 0.954, 0.508, 0.690, 0.957, 0.508, 0.678, 0.960, 0.508, 0.666, 0.962, + 0.507, 0.653, 0.965, 0.507, 0.640, 0.967, 0.505, 0.627, 0.968, 0.504, + 0.613, 0.970, 0.502, 0.599, 0.971, 0.500, 0.585, 0.972, 0.498, 0.570, + 0.973, 0.495, 0.555, 0.973, 0.493, 0.540, 0.973, 0.490, 0.525, 0.973, + 0.486, 0.509, 0.973, 0.483, 0.493, 0.972, 0.479, 0.476, 0.971, 0.475, + 0.460, 0.970, 0.471, 0.443, 0.969, 0.467, 0.426, 0.967, 0.463, 0.409, + 0.965, 0.458, 0.391, 0.963, 0.453, 0.374, 0.961, 0.449, 0.356, 0.958, + 0.444, 0.338, 0.956, 0.438, 0.319, 0.953, 0.433, 0.301, 0.949, 0.428, + 0.282, 0.946, 0.422, 0.263, 0.943, 0.417, 0.243, 0.939, 0.411, 0.223, + 0.935, 0.405, 0.202, 0.931, 0.399, 0.181, 0.927, 0.393, 0.158, 0.923, + 0.387, 0.134, 0.918, 0.381, 0.107, + ]).reshape((65, 65, 3)) + +BiOrangeBlue = np.array( + [0.000, 0.000, 0.000, 0.000, 0.062, 0.125, 0.000, 0.125, 0.250, 0.000, + 0.188, 0.375, 0.000, 0.250, 0.500, 0.000, 0.312, 0.625, 0.000, 0.375, + 0.750, 0.000, 0.438, 0.875, 0.000, 0.500, 1.000, 0.125, 0.062, 0.000, + 0.125, 0.125, 0.125, 0.125, 0.188, 0.250, 0.125, 0.250, 0.375, 0.125, + 0.312, 0.500, 0.125, 0.375, 0.625, 0.125, 0.438, 0.750, 0.125, 0.500, + 0.875, 0.125, 0.562, 1.000, 0.250, 0.125, 0.000, 0.250, 0.188, 0.125, + 0.250, 0.250, 0.250, 0.250, 0.312, 0.375, 0.250, 0.375, 0.500, 0.250, + 0.438, 0.625, 0.250, 0.500, 0.750, 0.250, 0.562, 0.875, 0.250, 0.625, + 1.000, 0.375, 0.188, 0.000, 0.375, 0.250, 0.125, 0.375, 0.312, 0.250, + 0.375, 0.375, 0.375, 0.375, 0.438, 0.500, 0.375, 0.500, 0.625, 0.375, + 0.562, 0.750, 0.375, 0.625, 0.875, 0.375, 0.688, 1.000, 0.500, 0.250, + 0.000, 0.500, 0.312, 0.125, 0.500, 0.375, 0.250, 0.500, 0.438, 0.375, + 0.500, 0.500, 0.500, 0.500, 0.562, 0.625, 0.500, 0.625, 0.750, 0.500, + 0.688, 0.875, 0.500, 0.750, 1.000, 0.625, 0.312, 0.000, 0.625, 0.375, + 0.125, 0.625, 0.438, 0.250, 0.625, 0.500, 0.375, 0.625, 0.562, 0.500, + 0.625, 0.625, 0.625, 0.625, 0.688, 0.750, 0.625, 0.750, 0.875, 0.625, + 0.812, 1.000, 0.750, 0.375, 0.000, 0.750, 0.438, 0.125, 0.750, 0.500, + 0.250, 0.750, 0.562, 0.375, 0.750, 0.625, 0.500, 0.750, 0.688, 0.625, + 0.750, 0.750, 0.750, 0.750, 0.812, 0.875, 0.750, 0.875, 1.000, 0.875, + 0.438, 0.000, 0.875, 0.500, 0.125, 0.875, 0.562, 0.250, 0.875, 0.625, + 0.375, 0.875, 0.688, 0.500, 0.875, 0.750, 0.625, 0.875, 0.812, 0.750, + 0.875, 0.875, 0.875, 0.875, 0.938, 1.000, 1.000, 0.500, 0.000, 1.000, + 0.562, 0.125, 1.000, 0.625, 0.250, 1.000, 0.688, 0.375, 1.000, 0.750, + 0.500, 1.000, 0.812, 0.625, 1.000, 0.875, 0.750, 1.000, 0.938, 0.875, + 1.000, 1.000, 1.000, + ]).reshape((9, 9, 3)) + +cmaps = { + "BiPeak": SegmentedBivarColormap( + BiPeak, "BiPeak", 256, "square"), + "BiOrangeBlue": SegmentedBivarColormap( + BiOrangeBlue, "BiOrangeBlue", 256, "square"), + "BiCone": SegmentedBivarColormap(BiPeak, "BiCone", 256, "circle"), +} diff --git a/lib/matplotlib/_cm_multivar.py b/lib/matplotlib/_cm_multivar.py new file mode 100644 index 000000000000..7d46eb0ef72d --- /dev/null +++ b/lib/matplotlib/_cm_multivar.py @@ -0,0 +1,166 @@ +# auto-genreated by https://github.com/trygvrad/multivariate_colormaps +# date: 2024-05-28 + +from .colors import LinearSegmentedColormap, MultivarColormap +import matplotlib as mpl +_LUTSIZE = mpl.rcParams['image.lut'] + +_2VarAddA0_data = [[0.000, 0.000, 0.000], + [0.020, 0.026, 0.031], + [0.049, 0.068, 0.085], + [0.075, 0.107, 0.135], + [0.097, 0.144, 0.183], + [0.116, 0.178, 0.231], + [0.133, 0.212, 0.279], + [0.148, 0.244, 0.326], + [0.161, 0.276, 0.374], + [0.173, 0.308, 0.422], + [0.182, 0.339, 0.471], + [0.190, 0.370, 0.521], + [0.197, 0.400, 0.572], + [0.201, 0.431, 0.623], + [0.204, 0.461, 0.675], + [0.204, 0.491, 0.728], + [0.202, 0.520, 0.783], + [0.197, 0.549, 0.838], + [0.187, 0.577, 0.895]] + +_2VarAddA1_data = [[0.000, 0.000, 0.000], + [0.030, 0.023, 0.018], + [0.079, 0.060, 0.043], + [0.125, 0.093, 0.065], + [0.170, 0.123, 0.083], + [0.213, 0.151, 0.098], + [0.255, 0.177, 0.110], + [0.298, 0.202, 0.120], + [0.341, 0.226, 0.128], + [0.384, 0.249, 0.134], + [0.427, 0.271, 0.138], + [0.472, 0.292, 0.141], + [0.517, 0.313, 0.142], + [0.563, 0.333, 0.141], + [0.610, 0.353, 0.139], + [0.658, 0.372, 0.134], + [0.708, 0.390, 0.127], + [0.759, 0.407, 0.118], + [0.813, 0.423, 0.105]] + +_2VarSubA0_data = [[1.000, 1.000, 1.000], + [0.959, 0.973, 0.986], + [0.916, 0.948, 0.974], + [0.874, 0.923, 0.965], + [0.832, 0.899, 0.956], + [0.790, 0.875, 0.948], + [0.748, 0.852, 0.940], + [0.707, 0.829, 0.934], + [0.665, 0.806, 0.927], + [0.624, 0.784, 0.921], + [0.583, 0.762, 0.916], + [0.541, 0.740, 0.910], + [0.500, 0.718, 0.905], + [0.457, 0.697, 0.901], + [0.414, 0.675, 0.896], + [0.369, 0.652, 0.892], + [0.320, 0.629, 0.888], + [0.266, 0.604, 0.884], + [0.199, 0.574, 0.881]] + +_2VarSubA1_data = [[1.000, 1.000, 1.000], + [0.982, 0.967, 0.955], + [0.966, 0.935, 0.908], + [0.951, 0.902, 0.860], + [0.937, 0.870, 0.813], + [0.923, 0.838, 0.765], + [0.910, 0.807, 0.718], + [0.898, 0.776, 0.671], + [0.886, 0.745, 0.624], + [0.874, 0.714, 0.577], + [0.862, 0.683, 0.530], + [0.851, 0.653, 0.483], + [0.841, 0.622, 0.435], + [0.831, 0.592, 0.388], + [0.822, 0.561, 0.340], + [0.813, 0.530, 0.290], + [0.806, 0.498, 0.239], + [0.802, 0.464, 0.184], + [0.801, 0.426, 0.119]] + +_3VarAddA0_data = [[0.000, 0.000, 0.000], + [0.018, 0.023, 0.028], + [0.040, 0.056, 0.071], + [0.059, 0.087, 0.110], + [0.074, 0.114, 0.147], + [0.086, 0.139, 0.183], + [0.095, 0.163, 0.219], + [0.101, 0.187, 0.255], + [0.105, 0.209, 0.290], + [0.107, 0.230, 0.326], + [0.105, 0.251, 0.362], + [0.101, 0.271, 0.398], + [0.091, 0.291, 0.434], + [0.075, 0.309, 0.471], + [0.046, 0.325, 0.507], + [0.021, 0.341, 0.546], + [0.021, 0.363, 0.584], + [0.022, 0.385, 0.622], + [0.023, 0.408, 0.661]] + +_3VarAddA1_data = [[0.000, 0.000, 0.000], + [0.020, 0.024, 0.016], + [0.047, 0.058, 0.034], + [0.072, 0.088, 0.048], + [0.093, 0.116, 0.059], + [0.113, 0.142, 0.067], + [0.131, 0.167, 0.071], + [0.149, 0.190, 0.074], + [0.166, 0.213, 0.074], + [0.182, 0.235, 0.072], + [0.198, 0.256, 0.068], + [0.215, 0.276, 0.061], + [0.232, 0.296, 0.051], + [0.249, 0.314, 0.037], + [0.270, 0.330, 0.018], + [0.288, 0.347, 0.000], + [0.302, 0.369, 0.000], + [0.315, 0.391, 0.000], + [0.328, 0.414, 0.000]] + +_3VarAddA2_data = [[0.000, 0.000, 0.000], + [0.029, 0.020, 0.023], + [0.072, 0.045, 0.055], + [0.111, 0.067, 0.084], + [0.148, 0.085, 0.109], + [0.184, 0.101, 0.133], + [0.219, 0.115, 0.155], + [0.254, 0.127, 0.176], + [0.289, 0.138, 0.195], + [0.323, 0.147, 0.214], + [0.358, 0.155, 0.232], + [0.393, 0.161, 0.250], + [0.429, 0.166, 0.267], + [0.467, 0.169, 0.283], + [0.507, 0.168, 0.298], + [0.546, 0.168, 0.313], + [0.580, 0.172, 0.328], + [0.615, 0.175, 0.341], + [0.649, 0.178, 0.355]] + +cmaps = { + name: LinearSegmentedColormap.from_list(name, data, _LUTSIZE) for name, data in [ + ('2VarAddA0', _2VarAddA0_data), + ('2VarAddA1', _2VarAddA1_data), + ('2VarSubA0', _2VarSubA0_data), + ('2VarSubA1', _2VarSubA1_data), + ('3VarAddA0', _3VarAddA0_data), + ('3VarAddA1', _3VarAddA1_data), + ('3VarAddA2', _3VarAddA2_data), + ]} + +cmap_families = { + '2VarAddA': MultivarColormap('2VarAddA', [cmaps['2VarAddA' + str(i)] for + i in range(2)], 'Add'), + '2VarSubA': MultivarColormap('2VarSubA', [cmaps['2VarSubA' + str(i)] for + i in range(2)], 'Sub'), + '3VarAddA': MultivarColormap('3VarAddA', [cmaps['3VarAddA' + str(i)] for + i in range(3)], 'Add'), +} diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py index f5bc455df1f7..d85554f76f26 100644 --- a/lib/matplotlib/cm.py +++ b/lib/matplotlib/cm.py @@ -24,6 +24,8 @@ from matplotlib import _api, colors, cbook, scale from matplotlib._cm import datad from matplotlib._cm_listed import cmaps as cmaps_listed +from matplotlib._cm_multivar import cmap_families as multivar_cmaps +from matplotlib._cm_bivar import cmaps as bivar_cmaps _LUTSIZE = mpl.rcParams['image.lut'] @@ -238,6 +240,12 @@ def get_cmap(self, cmap): _colormaps = ColormapRegistry(_gen_cmap_registry()) globals().update(_colormaps) +_multivar_colormaps = ColormapRegistry(multivar_cmaps) +globals().update(_multivar_colormaps) + +_bivar_colormaps = ColormapRegistry(bivar_cmaps) +globals().update(_bivar_colormaps) + # This is an exact copy of pyplot.get_cmap(). It was removed in 3.9, but apparently # caused more user trouble than expected. Re-added for 3.9.1 and extended the diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 5f40e7b0fb9a..0cd4846e06b4 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -54,7 +54,7 @@ import matplotlib as mpl import numpy as np -from matplotlib import _api, _cm, cbook, scale +from matplotlib import _api, _cm, cbook, scale, _image from ._color_data import BASE_COLORS, TABLEAU_COLORS, CSS4_COLORS, XKCD_COLORS @@ -87,6 +87,7 @@ def __delitem__(self, key): _colors_full_map = _ColorMapping(_colors_full_map) _REPR_PNG_SIZE = (512, 64) +_BIVAR_REPR_PNG_SIZE = 256 def get_named_colors_mapping(): @@ -674,14 +675,23 @@ def _create_lookup_table(N, data, gamma=1.0): return np.clip(lut, 0.0, 1.0) -class Colormap: +class ColormapBase: + """ + Base class for all colormaps, both scalar, bivariate and multivariate. + + This class is used for type checking, and cannot be initialized. + """ + ... + + +class Colormap(ColormapBase): """ Baseclass for all scalar to RGBA mappings. Typically, Colormap instances are used to convert data values (floats) from the interval ``[0, 1]`` to the RGBA color that the respective Colormap represents. For scaling of data into the ``[0, 1]`` interval see - `matplotlib.colors.Normalize`. Subclasses of `matplotlib.cm.ScalarMappable` + `matplotlib.colors.Normalize`. Subclasses of `matplotlib.cm.VectorMappable` make heavy use of this ``data -> normalize -> map-to-color`` processing chain. """ @@ -704,13 +714,14 @@ def __init__(self, name, N=256): self._i_over = self.N + 1 self._i_bad = self.N + 2 self._isinit = False + self.n_variates = 1 #: When this colormap exists on a scalar mappable and colorbar_extend #: is not False, colorbar creation will pick up ``colorbar_extend`` as #: the default value for the ``extend`` keyword in the #: `matplotlib.colorbar.Colorbar` constructor. self.colorbar_extend = False - def __call__(self, X, alpha=None, bytes=False): + def __call__(self, X, alpha=None, bytes=False, return_mask_bad=False): r""" Parameters ---------- @@ -727,6 +738,8 @@ def __call__(self, X, alpha=None, bytes=False): If False (default), the returned RGBA values will be floats in the interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the interval ``[0, 255]``. + return_mask_bad : bool + If true, also return a mask of bad values. Returns ------- @@ -778,6 +791,8 @@ def __call__(self, X, alpha=None, bytes=False): if not np.iterable(X): rgba = tuple(rgba) + if return_mask_bad: + return rgba, mask_bad return rgba def __copy__(self): @@ -1226,6 +1241,594 @@ def reversed(self, name=None): return new_cmap +class MultivarColormap(ColormapBase): + """ + Class for holding multiple `~matplotlib.colors.Colormap` for use in a + `~matplotlib.cm.VectorMappable` object + + MultivarColormap does not support alpha in the constituent + look up tables (ignored). + """ + def __init__(self, name, colormaps, combination_mode): + """ + Parameters + ---------- + name : str + The name of the colormap family. + colormaps: list or tuple of `~matplotlib.colors.Colormap` objects + The individual colormaps that are combined + combination_mode: str, 'Add' or 'Sub' + Describe how colormaps are combined in sRGB space + 'Add' -> additive + 'Sub' -> subtractive + """ + self.name = name + + if not np.iterable(colormaps) or len(colormaps) == 1: + raise ValueError("A MultivarColormap must have more than one colormap.") + for cmap in colormaps: + if not issubclass(type(cmap), Colormap): + raise ValueError("colormaps must be a list of objects that subclass" + " Colormap, not strings or list of strings") + + self.colormaps = colormaps + self.combination_mode = combination_mode + self.n_variates = len(colormaps) + self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything. + + def __call__(self, X, alpha=None, bytes=False): + r""" + Parameters + ---------- + X : tuple (X0, X1, ...) of length equal to the number of colormaps + X0, X1 ...: + float or int, `~numpy.ndarray` or scalar + The data value(s) to convert to RGBA. + For floats, *Xi...* should be in the interval ``[0.0, 1.0]`` to + return the RGBA values ``X*100`` percent along the Colormap line. + For integers, *Xi...* should be in the interval ``[0, self[i].N)`` to + return RGBA values *indexed* from colormap [i] with index ``Xi``, where + self[i] is colormap i. + alpha : float or array-like or None + Alpha must be a scalar between 0 and 1, a sequence of such + floats with shape matching Xi, or None. + bytes : bool + If False (default), the returned RGBA values will be floats in the + interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the + interval ``[0, 255]``. + + Returns + ------- + Tuple of RGBA values if X[0] is scalar, otherwise an array of + RGBA values with a shape of ``X.shape + (4, )``. + """ + + if len(X) != len(self): + raise ValueError( + f'For the selected colormap the data must have a first dimension ' + f'{len(self)}, not {len(X)}') + rgba, mask_bad = self[0](X[0], bytes=False, return_mask_bad=True) + rgba = np.asarray(rgba) + for c, xx in zip(self[1:], X[1:]): + sub_rgba, sub_mask_bad = c(xx, bytes=False, return_mask_bad=True) + sub_rgba = np.asarray(sub_rgba) + rgba[..., :3] += sub_rgba[..., :3] # add colors + rgba[..., 3] *= sub_rgba[..., 3] # multiply alpha + mask_bad |= sub_mask_bad + + if self.combination_mode == 'Sub': + rgba[..., :3] -= len(self) - 1 + + rgba[mask_bad] = self.get_bad() + + rgba = np.clip(rgba, 0, 1) + + if alpha is not None: + alpha = np.clip(alpha, 0, 1) + if alpha.shape not in [(), np.array(X[0]).shape]: + raise ValueError( + f"alpha is array-like but its shape {alpha.shape} does " + f"not match that of X[0] {np.array(X[0]).shape}") + rgba[..., -1] *= alpha + + if bytes: + rgba = (rgba * 255).astype('uint8') + + if not np.iterable(X[0]): + rgba = tuple(rgba) + + return rgba + + def copy(self): + """Return a copy of the multivarcolormap.""" + return self.__copy__() + + def __copy__(self): + cls = self.__class__ + cmapobject = cls.__new__(cls) + cmapobject.__dict__.update(self.__dict__) + cmapobject.colormaps = [cm.copy() for cm in self.colormaps] + cmapobject._rgba_bad = np.copy(self._rgba_bad) + return cmapobject + + def __eq__(self, other): + if not isinstance(other, MultivarColormap): + return False + if not len(self) == len(other): + return False + for c0, c1 in zip(self, other): + if not c0 == c1: + return False + if not all(self._rgba_bad == other._rgba_bad): + return False + if not self.combination_mode == other.combination_mode: + return False + return True + + def __getitem__(self, item): + return self.colormaps[item] + + def __iter__(self): + for c in self.colormaps: + yield c + + def __len__(self): + return len(self.colormaps) + + def __str__(self): + return self.name + + def get_bad(self): + """Get the color for masked values.""" + return np.array(self._rgba_bad) + + def set_bad(self, color='k', alpha=None): + """Set the color for masked values.""" + self._rgba_bad = to_rgba(color, alpha) + + @property + def combination_mode(self): + return self._combination_mode + + @combination_mode.setter + def combination_mode(self, mode): + if mode not in ['Add', 'Sub']: + raise ValueError("Combination_mode must be 'Add' or 'Sub'," + f" {mode!r} is not allowed.") + self._combination_mode = mode + + def _repr_png_(self): + raise NotImplementedError("no png representation of MultivarColormap" + " but you may access png repreesntations of the" + " individual colorbars.") + + def _repr_html_(self): + """Generate an HTML representation of the MultivarColormap.""" + return ''.join([c._repr_html_() for c in self.colormaps]) + + +class BivarColormap(ColormapBase): + """ + Baseclass for all bivarate to RGBA mappings. + + Designed as a drop-in replcement for Colormap when using a 2D + lookup table. To be used with `~matplotlib.cm.VectorMappable`. + """ + + def __init__(self, name, N=256, M=256, shape='square'): + """ + Parameters + ---------- + name : str + The name of the colormap. + N : int + The number of RGB quantization levels along the first axis. + M : int + The number of RGB quantization levels along the second axis. + If None, M = N + shape: str 'square' or 'circle' or 'ignore' or 'circleignore' + + - If 'square' each variate is clipped to [0,1] independently + - If 'circle' the variates are clipped radially to the center + of the colormap, and a circular mask is applied when the colormap + is displayed + - If 'ignore' the variates are not clipped, but instead assigned the + 'outside' color + - If 'circleignore' a circular mask is applied, but the data is not + clipped and instead assigned the 'outside' color + + """ + + self.name = name + self.N = int(N) # ensure that N is always int + self.M = int(M) + self.shape = shape + self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything. + self._rgba_outside = (1.0, 0.0, 1.0, 1.0) + self._isinit = False + self.n_variates = 2 + '''#: When this colormap exists on a scalar mappable and colorbar_extend + #: is not False, colorbar creation will pick up ``colorbar_extend`` as + #: the default value for the ``extend`` keyword in the + #: `matplotlib.colorbar.Colorbar` constructor. + self.colorbar_extend = False''' + + def __call__(self, X, alpha=None, bytes=False): + r""" + Parameters + ---------- + X : tuple (X0, X1), X0 and X1: float or int `~numpy.ndarray` or scalar + The data value(s) to convert to RGBA. + + - For floats, *X* should be in the interval ``[0.0, 1.0]`` to + return the RGBA values ``X*100`` percent along the Colormap. + - For integers, *X* should be in the interval ``[0, Colormap.N)`` to + return RGBA values *indexed* from the Colormap with index ``X``. + + alpha : float or array-like or None + Alpha must be a scalar between 0 and 1, a sequence of such + floats with shape matching X0, or None. + bytes : bool + If False (default), the returned RGBA values will be floats in the + interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the + interval ``[0, 255]``. + + Returns + ------- + Tuple of RGBA values if X is scalar, otherwise an array of + RGBA values with a shape of ``X.shape + (4, )``. + """ + + if len(X) != 2: + raise ValueError( + f'For a `BivarColormap` the data must have a first dimension ' + f'{2}, not {len(X)}') + + if not self._isinit: + self._init() + + X0 = np.ma.array(X[0], copy=True) + X1 = np.ma.array(X[1], copy=True) + # clip to shape of colormap, circle square, etc. + self._clip((X0, X1)) + + # Native byteorder is faster. + if not X0.dtype.isnative: + X0 = X0.byteswap().view(X0.dtype.newbyteorder()) + if not X1.dtype.isnative: + X1 = X1.byteswap().view(X1.dtype.newbyteorder()) + + if X0.dtype.kind == "f": + X0 *= self.N + # xa == 1 (== N after multiplication) is not out of range. + X0[X0 == self.N] = self.N - 1 + + if X1.dtype.kind == "f": + X1 *= self.M + # xa == 1 (== N after multiplication) is not out of range. + X1[X1 == self.M] = self.M - 1 + + # Pre-compute the masks before casting to int (which can truncate) + mask_outside = (X0 < 0) | (X1 < 0) \ + | (X0 >= self.N) | (X1 >= self.M) + # If input was masked, get the bad mask from it; else mask out nans. + mask_bad_0 = X0.mask if np.ma.is_masked(X0) else np.isnan(X0) + mask_bad_1 = X1.mask if np.ma.is_masked(X1) else np.isnan(X1) + mask_bad = mask_bad_0 | mask_bad_1 + + with np.errstate(invalid="ignore"): + # We need this cast for unsigned ints as well as floats + X0 = X0.astype(int) + X1 = X1.astype(int) + + # Set masked values to zero + # The corresponding rgb values will be replaced later + for X_part in [X0, X1]: + X_part[mask_outside] = 0 + X_part[mask_bad] = 0 + + rgba = self._lut[X0, X1] + if np.isscalar(X[0]): + rgba = np.copy(rgba) + rgba[mask_outside] = self._rgba_outside + rgba[mask_bad] = self._rgba_bad + if bytes: + rgba = (rgba * 255).astype(np.uint8) + if alpha is not None: + alpha = np.clip(alpha, 0, 1) + if bytes: + alpha *= 255 # Will be cast to uint8 upon assignment. + if alpha.shape not in [(), np.array(X0).shape]: + raise ValueError( + f"alpha is array-like but its shape {alpha.shape} does " + f"not match that of X[0] {np.array(X0).shape}") + rgba[..., -1] = alpha + # If the "bad" color is all zeros, then ignore alpha input. + if (np.array(self._rgba_bad) == 0).all(): + rgba[mask_bad] = (0, 0, 0, 0) + + if not np.iterable(X[0]): + rgba = tuple(rgba) + return rgba + + @property + def lut(self): + """ + For external access to the lut, i.e. for displaying the cmap. + For circular colormaps this returns a lut with a circular mask. + + Internal functions (such as to_rgb()) should use _lut + which stores the lut without a circular mask + A lut without the circular mask is needed in to_rgb() because the + conversion from floats to ints results in some some pixel-requests + just outside of the circular mask + + """ + if not self._isinit: + self._init() + lut = np.copy(self._lut) + if self.shape == 'circle' or self.shape == 'circleignore': + n = np.linspace(-1, 1, self.N) + m = np.linspace(-1, 1, self.M) + radii_sqr = (n**2)[:, np.newaxis] + (m**2)[np.newaxis, :] + mask_outside = radii_sqr > 1 + lut[mask_outside, 3] = 0 + return lut + + def __copy__(self): + cls = self.__class__ + cmapobject = cls.__new__(cls) + cmapobject.__dict__.update(self.__dict__) + + cmapobject._rgba_outside = np.copy(self._rgba_outside) + cmapobject._rgba_bad = np.copy(self._rgba_bad) + cmapobject.shape = self.shape + if self._isinit: + cmapobject._lut = np.copy(self._lut) + return cmapobject + + def __eq__(self, other): + if not isinstance(other, BivarColormap): + return False + # To compare lookup tables the Colormaps have to be initialized + if not self._isinit: + self._init() + if not other._isinit: + other._init() + if not np.array_equal(self._lut, other._lut): + return False + if not np.array_equal(self._rgba_bad, other._rgba_bad): + return False + if not np.array_equal(self._rgba_outside, other._rgba_outside): + return False + if not self.shape == other.shape: + return False + return True + + def get_bad(self): + """Get the color for masked values.""" + return self._rgba_bad + + def set_bad(self, color='k', alpha=None): + """Set the color for masked values.""" + self._rgba_bad = to_rgba(color, alpha) + + def get_outside(self): + """Get the color for out-of-range values.""" + return self._rgba_outside + + def set_outside(self, color='k', alpha=None): + """Set the color for out-of-range values.""" + self._rgba_outside = to_rgba(color, alpha) + + def _init(self): + """Generate the lookup table, ``self._lut``.""" + raise NotImplementedError("Abstract class only") + + @property + def shape(self): + return self._shape + + @shape.setter + def shape(self, shape): + if shape in ['square', 'circle', 'ignore', 'circleignore']: + self._shape = shape + else: + raise ValueError("The shape must be a valid string, " + "'square', 'circle', 'ignore', or 'circleignore'") + + def _clip(self, X): + """ + For internal use when applying a BivarColormap to data. + i.e. cm.VectorMappable().to_rgba() + Clips X[0] and X[1] according to 'self.shape'. + X is modified in-place. + + Parameters + ---------- + X: np.array + array of floats or ints to be clipped + shape: str 'square' or 'circle' or 'ignore' or 'circleignore' + + - If 'square' each variate is clipped to [0,1] independently + - If 'circle' the variates are clipped radially to the center + of the colormap. + It is assumed that a circular mask is applied when the colormap + is displayed + - If 'ignore' the variates are not clipped, but instead assigned the + 'outside' color + - If 'circleignore' a circular mask is applied, but the data is not clipped + and instead assigned the 'outside' color + + """ + if self.shape == 'square': + for X_part, mx in zip(X, (self.N, self.M)): + X_part[X_part < 0] = 0 + if X_part.dtype.kind == "f": + X_part[X_part > 1] = 1 + else: + X_part[X_part >= mx] = mx - 1 + + elif self.shape == 'ignore': + for X_part, mx in zip(X, (self.N, self.M)): + X_part[X_part < 0] = -1 + if X_part.dtype.kind == "f": + X_part[X_part > 1] = -1 + else: + X_part[X_part >= mx] = -1 + + elif self.shape == 'circle' or self.shape == 'circleignore': + for X_part in X: + if not X_part.dtype.kind == "f": + raise NotImplementedError( + "Circular bivariate colormaps are only" + " implemented for use with with floats, not integers") + radii_sqr = (X[0] - 0.5)**2 + (X[1] - 0.5)**2 + mask_outside = radii_sqr > 0.25 + if self.shape == 'circle': + overextend = 2 * np.sqrt(radii_sqr[mask_outside]) + X[0][mask_outside] = (X[0][mask_outside] - 0.5) / overextend + 0.5 + X[1][mask_outside] = (X[1][mask_outside] - 0.5) / overextend + 0.5 + else: + X[0][mask_outside] = -1 + X[1][mask_outside] = -1 + + def _repr_png_(self): + """Generate a PNG representation of the BivarColormap.""" + if not self._isinit: + self._init() + + pixels = (self.lut[::-1, :, :] * 255).astype(np.uint8) + + png_bytes = io.BytesIO() + title = self.name + ' BivarColormap' + author = f'Matplotlib v{mpl.__version__}, https://matplotlib.org' + pnginfo = PngInfo() + pnginfo.add_text('Title', title) + pnginfo.add_text('Description', title) + pnginfo.add_text('Author', author) + pnginfo.add_text('Software', author) + Image.fromarray(pixels).save(png_bytes, format='png', pnginfo=pnginfo) + return png_bytes.getvalue() + + def _repr_html_(self): + """Generate an HTML representation of the Colormap.""" + png_bytes = self._repr_png_() + png_base64 = base64.b64encode(png_bytes).decode('ascii') + def color_block(color): + hex_color = to_hex(color, keep_alpha=True) + return (f'
') + + return ('
' + f'{self.name} ' + '
' + '
' + '
' + '
' + f'{color_block(self.get_outside())} outside' + '
' + '
' + f'bad {color_block(self.get_bad())}' + '
') + + def copy(self): + """Return a copy of the colormap.""" + return self.__copy__() + + +class SegmentedBivarColormap(BivarColormap): + """ + BivarColormap object generated by supersampling a regular grid + + Parameters + ---------- + patch : nparray of shape (k, k, 3) + This patch gets supersamples to a lut of shape (N, M, 4) + name : str + The name of the colormap. + N : int + The number of RGB quantization levels along each axis. + shape: str 'square' or 'circle' or 'ignore' or 'circleignore' + + - If 'square' each variate is clipped to [0,1] independently + - If 'circle' the variates are clipped radially to the center + of the colormap, and a circular mask is applied when the colormap + is displayed + - If 'ignore' the variates are not clipped, but instead assigned the + 'outside' color + - If 'circleignore' a circular mask is applied, but the data is not clipped + + """ + + def __init__(self, patch, name, N=256, shape='square'): + self.patch = patch + super().__init__(name, N, N, shape) + + def _init(self): + s = self.patch.shape + _patch = np.empty((s[0], s[1], 4)) + _patch[:, :, :3] = self.patch + _patch[:, :, 3] = 1 + transform = mpl.transforms.Affine2D().translate(-0.5, -0.5)\ + .scale(self.N / (s[0] - 1), self.N / (s[0] - 1)) + self._lut = np.empty((self.N, self.N, 4)) + + _image.resample(_patch, self._lut, transform, _image.BILINEAR, + resample=False, alpha=1) + self._isinit = True + + +class BivarColormapFromImage(BivarColormap): + """ + BivarColormap object generated by supersampling a regular grid + + Parameters + ---------- + lut : nparray of shape (N, M, 3) or (N, M, 4) + The look-up-table + name : str + The name of the colormap. + shape: str 'square' or 'circle' or 'ignore' or 'circleignore' + + - If 'square' each variate is clipped to [0,1] independently + - If 'circle' the variates are clipped radially to the center + of the colormap, and a circular mask is applied when the colormap + is displayed + - If 'ignore' the variates are not clipped, but instead assigned the + 'outside' color + - If 'circleignore' a circular mask is applied, but the data is not clipped + + """ + + def __init__(self, lut, name='', shape='square'): + # We can allow for a PIL.Image as unput in the following way, but importing + # matplotlib.image.pil_to_array() results in a circular import + # For now, this function only accepts numpy arrays. + # if isinstance(Image, lut): + # lut = image.pil_to_array(lut) + lut = np.array(lut, copy=True) + if lut.ndim != 3 or lut.shape[2] not in (3, 4): + raise ValueError("The lut must be an array of shape (n, m, 3) or (n, m, 4)", + " or a PIL.image encoded as RGB or RGBA") + self._lut = lut + super().__init__(name, lut.shape[0], lut.shape[1], shape) + + def _init(self): + self._isinit = True + + class Normalize: """ A class which, when called, maps values within the interval @@ -2168,7 +2771,7 @@ def inverse(self, value): class NoNorm(Normalize): """ Dummy replacement for `Normalize`, for the case where we want to use - indices directly in a `~matplotlib.cm.ScalarMappable`. + indices directly in a `~matplotlib.cm.VectorMappable`. """ def __call__(self, value, clip=None): if np.iterable(value): diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index 514801b714b8..dc317101be8b 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -64,7 +64,10 @@ class ColorConverter: colorConverter: ColorConverter -class Colormap: +class ColormapBase: + ... + +class Colormap(ColormapBase): name: str N: int colorbar_extend: bool @@ -138,6 +141,69 @@ class ListedColormap(Colormap): def resampled(self, lutsize: int) -> ListedColormap: ... def reversed(self, name: str | None = ...) -> ListedColormap: ... +class MultivarColormap(ColormapBase): + name: str + colormaps: list[Colormap] + combination_mode: str + n_variates: int + def __init__(self, name: str, colormaps: list[Colormap], combination_mode: str) -> None: ... + @overload + def __call__( + self, X: Sequence[Sequence[float]] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ... + ) -> np.ndarray: ... + @overload + def __call__( + self, X: Sequence[float], alpha: float | None = ..., bytes: bool = ... + ) -> tuple[float, float, float, float]: ... + @overload + def __call__( + self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ... + ) -> tuple[float, float, float, float] | np.ndarray: ... + def copy(self) -> MultivarColormap: ... + def __copy__(self) -> MultivarColormap: ... + def __getitem__(self, item: int) -> Colormap: ... + def __iter__(self) -> Colormap: ... + def __len__(self) -> int: ... + def get_bad(self) -> np.ndarray: ... + def set_bad(self, color: ColorType = ..., alpha: float | None = ...) -> None: ... + +class BivarColormap(ColormapBase): + name: str + N: int + M: int + shape: str + n_variates: int + def __init__(self, name: str, N: int = ..., M: int | None = ..., shape: str = ...) -> None: ... + @overload + def __call__( + self, X: Sequence[Sequence[float]] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ... + ) -> np.ndarray: ... + @overload + def __call__( + self, X: Sequence[float], alpha: float | None = ..., bytes: bool = ... + ) -> tuple[float, float, float, float]: ... + @overload + def __call__( + self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ... + ) -> tuple[float, float, float, float] | np.ndarray: ... + @property + def lut(self) -> np.ndarray: ... + def __copy__(self) -> BivarColormap: ... + def __eq__(self, other) -> bool: ... + def get_bad(self) -> np.ndarray: ... + def set_bad(self, color: ColorType = ..., alpha: float | None = ...) -> None: ... + def get_outside(self) -> np.ndarray: ... + def set_outside(self, color: ColorType = ..., alpha: float | None = ...) -> None: ... + def copy(self) -> BivarColormap: ... + +class SegmentedBivarColormap(BivarColormap): + def __init__( + self, patch: np.ndarray, name: str, N: int = ..., shape: str = ..., + ) -> None: ... + +class BivarColormapFromImage(BivarColormap): + def __init__(self, lut: np.ndarray, name: str = ..., shape: str = ...) -> None: ... + class Normalize: callbacks: cbook.CallbackRegistry def __init__( diff --git a/lib/matplotlib/meson.build b/lib/matplotlib/meson.build index c4b66fc1b336..657adfd4e835 100644 --- a/lib/matplotlib/meson.build +++ b/lib/matplotlib/meson.build @@ -4,7 +4,9 @@ python_sources = [ '_animation_data.py', '_blocking_input.py', '_cm.py', + '_cm_bivar.py', '_cm_listed.py', + '_cm_multivar.py', '_color_data.py', '_constrained_layout.py', '_docstring.py', diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/bivariate_cmap_shapes.png b/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/bivariate_cmap_shapes.png new file mode 100644 index 0000000000000000000000000000000000000000..f22b446fc84b5c0675d96f50faece58312ecc7d1 GIT binary patch literal 5157 zcmcgwX;@R&x{gPWw3V8RR7F9sfY6p%W+4O>#VCRT$|N8lLzw3XAw;FEP(}qAB~h6S zqhT~Mq$m&>!x-j7pco*87(xqDSY0sDL0z-TrddVpQ1Oj{PUVC0b%iti8bR^8;to_}m%vn^%OcYAu z6Pe0c3@4m~f3@f8sr73eKNeRNDHa*`Tv=mOy}=aDS@aGa*f5eG_9|@( zm!=EHCEl}j@gk8<7dn&f9lG$>K=o&ed&0XH(H$1TxX zCi&s4XoiYp%{c@Z6!Zg=1JxdX5816xKHCcdnM>`91n$4Nckduj{q?^F_kcjpKz}&{ zG`=!7mjHq8{pGXWExZQWa{{Qo_|0g~zh(4eIN00`$y-f2VG`Z(CW_%17iY>h+Q=gi z2otSH#H~1XC*G$&e`cZH+vYvag{SVu9^(V;JWxwgk8&qakm1bq+o`} zoLDZi2ZL_MOEkj7~&M(vRhigRBjdU9~EAiDw-FZIK z5elL{mj!Umra4K#w#>;Cq(feh@@8o93ruz+y`CsiQuJr`nh3DV3LVX24wo1wcVy*L z9G%E{lN&cBP>S!X1`|~=+%A5-$WSHQ1UiuJQ>!z~-VRbgTHgBfBfkMce1oB&{qY>k z>_&G}*J4eO*r?F?vd~mY3h$2};?0nDw#f5e*6b9daiyxt&{u*Kh1>ls0;@BN`T0zgDYG4M?UPM!rA~ z?9A4x{<>Af&7tyqq>=mpm^Y)-T@=sIHEqr05h9=QZa=|PRtEVQRxr0ZH*AU@360QB|c|vW#*h=O%6S6 z8H^#PHL9v=6-#Vn9+`p;sNS^d6qF&TnKzOTyTi`hiG$;rgl_hYuz_XSGn91wsc{86 z@ms#~e2y+gn%>c6v@X&)Y!^f>{7k@)7Y`#kF-z{}#KZhz?3Pm4?;$&F?Llu{BQ~yG zt&T_A+;;F~5@YPBJ4c}lH=KlsWIOlG^-2-B* z*rN9+lVuuB)P2RAzNGnDF+kXT^j2Pjxb(nji4EF&Ax!XCcb&R3J70g)ExSRSSHNk@ zs9k>0l0MQ;?rtv22|P5Ieq&9`l*?l6%qsGk#b`8NRSVr&m|(`PPC!?Q@S3kpkw#^R zI1m1p>>p+g8|4a|@(1*;>NlHhV`SV2<%<(It+*Ios5NVId!S(6J%mVDoY*0t@=9^R z0fDJFKa@2=Feqy(RSn(XIgSjulE3ABzfg6QSFG^)lHA4{zWk!Cf5ykhQ4U?F7|ujl z^tckDLth+ywlqne=-bMMoX{%Sk8`VKuix$NqPVLGs3KaD1M2pb(xmdqBh$5RFA}MU z;*-s~(OsnzS@0JmGA6Wz`xdjI1fBn5JoDjnfSi4WrKI|e%B#piU+#z7wj#fIhSj?U zXJ=Gvp1p#Uj_Ffgh~$@sk5?otS76+RMSl)hvV;p6;(^sKM%@1T3Tw`1Y9a2$qekC^ zcW{v<1CBf*r8Dek{q$=l+koO|#h~TZ*c;Bg=j8U5M03u=xI|;4e0%hDN!2m2zY_CG zg(|s(*|UOzlU1d`B@k>1B%@JP)~xnB9A@p)T!C7Af6S#7%q{kDsl@QDgZ~l6{0Ewq z>4AWLkfAB);G3L-G?-DCx`z(RW7so#+4ktm5z4su{&N~QGeytEP$gV^Wm|?lH14)0 zuC4LaEnIM1n7b`aI!OF;+^B@F+88AO&S%@t_u_1vhOEk=C3yyDDdhU?DNG8bVwV3f zRo#IBtu@7tHZkl`nrDh>Si3i(EoILeo)FSJpSW&rE9&Zi#KeAt5JV#}Set>Ek*VU? z*aFtC5#y?+*C17!ZS1%Co&j!!P$I2- z^GlB--?AwOki+TwA`c3R5Eo53Gc$IB@7$t!CG6Tdtsvt@M!Rd+B-C}P^o6MnY6MH# zbRuRWC1WSNW!;YF1o@#nHU=cxWVPDXezKCjQK!+^*)@^7`~J~`)q#m)1)g?*TR{Id z%66j39}Awnm0^QRJBj$#vC9(sGo7JPm3j8!tRZJx(YNQ}HFgmA*Hr+SI;ty=f%^x5 z!b;iAH|FJaTwq+ByE9EXko@7k%<)PC&cx4twyXn|Oc)Q6XFZ+y+^1p7^7t&`#Ku=e z3-IwJCZC?QihL}HJ7i;1Nq01jTD)Hzi+tRuDit8jSoUs9_+t+FX9NGS{SSE;f;Jyr zZlHsUAt|L7GkX?_liG^~b~n??xbBy}gZc#<;93mc7~nU;*p3kyB7P5jHMr+&sar^ByH zw)jP0?k^b|Qn$ulvj=Jb7REp`ys~wuz8q6F{f7r59GxLjh(e`uKd836TrR&SWxKdIL ziw`qb3X-_J{g#hcikz5cGQnoK6#hPw5`z<74gO8sI~q*xHRP}eIh^9(G?>QFBG5|TgKG%YzM2GC4HoDyP`&9I??ZJSR6GFNE)D&m#$CRKPk2ajmcz+tE# zP5vc`{d3e{QevV+weXx|wOoL4^l&z3zF33qouBB&!Li~E%|xxN1^|rMUGhYrKV{1} zPsVC68e9g_%Ub-rN|ToKv4VjUVeblQ!N!yz9V4 zt2YV3&xqAdCfW#uEs6*&`-Pf0O3xgsUy~~m+-Io*UF#t*q`~v?|F78ZErV$`ya?W| zw`E=n(#9dY;xE{W&IJKzjmBl$Lv>wj%F`2NCYrPC-LV)O1`(}^Gdq%__C%vkg$HR@ z$pLr3dWGMORlAvGj3QsSRk1vh4~)RLbyvpL7T#epAd@);=j)8~?4hUYzr&fWq+a&! z-Kz^nHTMnPcZG39J5Pt1fD(d@$yFfS@48ZU+*=nfz_>W%di{8uF-|^B_1|IC86lWY z8nj?=`^MWo6gwj4GEW1+eXFl^By6O$YJ8pA@IzJSEF3K0<1did+`Z{>-9Ktp>G`kF-lrRF2N@Cc)-Hn%Gk?w5(#h$2jM;liTe+&m}(pw=fn`KfDf z+BkP2G;eAtytHpFzh1TW>PhNWu>il=F&zsB!l)ZtXLvnCvXmK%`X(^b?^g;*@s%wUlI8q5!06E(a0xRnK$)AKn49b5j|`^WQi`4(-{Svl$!n~E9@z_S zB8vGM>Li4`KiW)DF*{q0+bSHKd8qKPKxEbW{)T|iXls=)Os;#xoDK! zuilx?qE7R+sU`KqQ=dM~$B{U?b%p#p{`e`cU+^kNrdWcV$=Fpv7@e4-)0ov*@$SkA z-ksoY+fX!W8aE-;8f!T%Y$ul~z1W5%Z7!xDI$eXHK4(h3d{<4cD=pJ3MPTUK<-e!< zs4AHIbQ=n(tJs1h3))aZ-b}^dBM95=Epiz-95{8D<=v749HVp~KWCM&V9*Nvf_|UD zCf@3#v`O@8VibeYxw(0FEo@0QA|e8hp1Wz7D vp0ob#c;Q!X*!=x;`=5W(|JCW!J@H=m_ojV*YCSK2K|rtz))v+0Za?^Mik4j< literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/multivar_alpha_mixing.png b/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/multivar_alpha_mixing.png new file mode 100644 index 0000000000000000000000000000000000000000..415dfda291dcd476b5e81e152092ad59bdd49844 GIT binary patch literal 4917 zcmeHLdr(tX9zHx2s2Z@gSVV!5nX->3Z9i5N| z9Ub@C{hmsVrMMsv(LZ;GlgQzSyQNd{&=6*^-&~{sV2)os4a(6EBLMg`^~CXGXXC$@ z4;)J`yJpZfuRo-a&34E->s&9-PCUA?u}mK3lIrDUlkxen+n>ttCN8w;Itx=;I?T*t zq}0DHbiA}ky3@rgaq8Vht16o(nXs~qsUlwXY#-J1#)&Sx4SSf$jyuX8qr^*XnI&;y zO(;c&3_3mofW@+oG>7H|gBchC@Y>V@0K2bQP~m`~F#zdX?ErAk00sbDGByRk!rmR~ z^2KkDt|5>SY0k>3=W@>-baoa>B!LBNcCDKUfThaia^o^-iKL|(|dUA5|e9@+H4Njq0$tsb_`?fL!O!o5l&38+x z#3Ws@9+Nogu)RGSJqz96vNR*&c;~gZwzle<%wIWpK{L{fTn?xluOUAg6bp$v_Xznz z(0KP??`bpXoR(yT02v5>wqF^qYoj+_d+jGeH}A6vfwOrPf(6{sA!t(f5~a*2Ja3Re z&l2hP^M_piq*%bYGz2%r_AThoB&`&jE@XO`{5y9zIVetr+go?d=f%{6Hbaf2|oCnSiu zD9)a6Z~us`;8a>ewD&+mV~x+3*(ZtlG{e-t#Frnz`sQjPMSxY&e9hFvbGa!g(S$Tqc}8Dc>+uRZ`BsUt}E+7I}K=%9Kdd zCDKnfv-Q@#n>8z09paF#-a3)(@(AK%miV6~MxnJK%Mw}vR)djSX~+vNPsz=SlM?a$ zg39L!SNk=?yOz{A>!k(et7bM^oq|StV{Y*UIe4_bE3H&eX#iLT>tm8(XX!xJt3;j7 zDY(hOiYJ;Di^)^Mtn-1!prw8`R4wi8%LWgqLO3iZehD5qr=)lU*<*0erPE?1`d-WBbvaVyQ< zM(EdMi8gE0Rywsg9m0;OJ?w*nO^C2P*k_M_)E1AjJ>kqBaTxabpAK7 zE^s!4cb?wblo6JB?k39r3)SJ!b~M z7oV+9=KPQh-uFxjA794Mu=J%rqQ>81I{%Bw+UVbSj?bbV{xcqgiMjLf!F^)!-u;&f z)uxu~5uvSmZ2#pU*Y71YgydJaiV1w=GdYZ<%Z$egGOO&j6=%+h`qqpd)2G2sEIry$3)=pauS;5s% z$jX%%xwssdBjxVh#NAG%Kv6;8A~70S9$QcB)(jWEq9YO1U9uYDkJUmB?;7tv+wJ@! z-$BS|n2am861d}F}y%%5gx`tG@ZD|TP?fcpaMz=VP79x ztu!3jHnE<-|MH|w@jDqrZ#EFeME91&-sd6&!St6YeNA&7ax;@ajjW#Q(41oLe&6&v ziNtxh3fgNCaS0q{s!=s#zD2SP_EgE6j6BUU!mz-PI<1hJQ7C+A@;fP=u(CX(?7gkJv#a}G$y literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/meson.build b/lib/matplotlib/tests/meson.build index 107066636f31..1dcf3791638a 100644 --- a/lib/matplotlib/tests/meson.build +++ b/lib/matplotlib/tests/meson.build @@ -57,6 +57,7 @@ python_sources = [ 'test_marker.py', 'test_mathtext.py', 'test_matplotlib.py', + 'test_multivariate_colormaps.py', 'test_mlab.py', 'test_offsetbox.py', 'test_patches.py', diff --git a/lib/matplotlib/tests/test_multivariate_colormaps.py b/lib/matplotlib/tests/test_multivariate_colormaps.py new file mode 100644 index 000000000000..3033faad2df0 --- /dev/null +++ b/lib/matplotlib/tests/test_multivariate_colormaps.py @@ -0,0 +1,440 @@ +import numpy as np +from numpy.testing import assert_array_equal, assert_allclose +import matplotlib.pyplot as plt +from matplotlib.testing.decorators import (image_comparison, + remove_ticks_and_titles) +import matplotlib as mpl +import pytest +from pathlib import Path +from io import BytesIO +from PIL import Image +import base64 + + +@image_comparison(["bivariate_cmap_shapes.png"]) +def test_bivariate_cmap_shapes(): + x_0 = (np.arange(100, dtype='float32').reshape(10, 10) % 10)/9 * 1.2 - 0.1 + x_1 = x_0.T + + fig, axes = plt.subplots(1, 4, figsize=(10, 2)) + + # shape = 'square' + cmap = mpl.bivar_colormaps['BiPeak'] + axes[0].imshow(cmap((x_0, x_1)), interpolation='nearest') + + # shape = 'circle' + cmap = mpl.bivar_colormaps['BiCone'] + axes[1].imshow(cmap((x_0, x_1)), interpolation='nearest') + + # shape = 'ignore' + cmap = mpl.bivar_colormaps['BiPeak'] + cmap.shape = 'ignore' + axes[2].imshow(cmap((x_0, x_1)), interpolation='nearest') + + # shape = circleignore + cmap = mpl.bivar_colormaps['BiCone'] + cmap.shape = 'circleignore' + axes[3].imshow(cmap((x_0, x_1)), interpolation='nearest') + remove_ticks_and_titles(fig) + + +def test_multivar_creation(): + # test creation of a custom multivariate colorbar + blues = mpl.colormaps['Blues'] + oranges = mpl.colormaps['Oranges'] + cmap = mpl.colors.MultivarColormap('custom', (blues, oranges), 'Sub') + y, x = np.mgrid[0:3, 0:3]/2 + im = cmap((y, x)) + res = np.array([[[0.96862745, 0.94509804, 0.92156863, 1], + [0.96004614, 0.53504037, 0.23277201, 1], + [0.46666667, 0.1372549, 0.01568627, 1]], + [[0.41708574, 0.64141484, 0.75980008, 1], + [0.40850442, 0.23135717, 0.07100346, 1], + [0, 0, 0, 1]], + [[0.03137255, 0.14901961, 0.34117647, 1], + [0.02279123, 0, 0, 1], + [0, 0, 0, 1]]]) + assert_allclose(im, res, atol=0.01) + + with pytest.raises(ValueError, match="colormaps must be a list of"): + cmap = mpl.colors.MultivarColormap('custom', (blues, 'Oranges'), 'Sub') + with pytest.raises(ValueError, match="colormaps must be a list of"): + cmap = mpl.colors.MultivarColormap('custom', 'blues', 'Sub') + with pytest.raises(ValueError, match="A MultivarColormap must"): + cmap = mpl.colors.MultivarColormap('custom', (blues), 'Sub') + + +@image_comparison(["multivar_alpha_mixing.png"]) +def test_multivar_alpha_mixing(): + # test creation of a custom colormap using 'rainbow' + # and a colormap that goes from alpha = 1 to alpha = 0 + rainbow = mpl.colormaps['rainbow'] + alpha = np.zeros((256, 4)) + alpha[:, 3] = np.linspace(1, 0, 256) + alpha_cmap = mpl.colors.LinearSegmentedColormap.from_list('from_list', alpha) + + cmap = mpl.colors.MultivarColormap('custom', (rainbow, alpha_cmap), 'Add') + y, x = np.mgrid[0:10, 0:10]/9 + im = cmap((y, x)) + + fig, ax = plt.subplots() + ax.imshow(im, interpolation='nearest') + remove_ticks_and_titles(fig) + + +def test_multivar_cmap_call(): + cmap = mpl.multivar_colormaps['2VarAddA'] + assert_array_equal(cmap((0.0, 0.0)), (0, 0, 0, 1)) + assert_array_equal(cmap((1.0, 1.0)), (1, 1, 1, 1)) + assert_allclose(cmap((0.0, 0.0), alpha=0.1), (0, 0, 0, 0.1), atol=0.1) + + cmap = mpl.multivar_colormaps['2VarSubA'] + assert_array_equal(cmap((0.0, 0.0)), (1, 1, 1, 1)) + assert_allclose(cmap((1.0, 1.0)), (0, 0, 0, 1), atol=0.1) + + # check outside and bad + cs = cmap([(0., 0., 0., 1.2, np.nan), (0., 1.2, np.nan, 0., 0., )]) + assert_allclose(cs, [[1., 1., 1., 1.], + [0.801, 0.426, 0.119, 1.], + [0., 0., 0., 0.], + [0.199, 0.574, 0.881, 1.], + [0., 0., 0., 0.]]) + + assert_array_equal(cmap((0.0, 0.0), bytes=True), (255, 255, 255, 255)) + + with pytest.raises(ValueError, match="alpha is array-like but its shape"): + cs = cmap([(0, 5, 9), (0, 0, 0)], alpha=(0.5, 0.3)) + + with pytest.raises(ValueError, match="For the selected colormap the data"): + cs = cmap([(0, 5, 9), (0, 0, 0), (0, 0, 0)]) + + # Tests calling a multivariate colormap with integer values + cmap = mpl.multivar_colormaps['2VarSubA'] + + # call only integers + cs = cmap([(0, 50, 100, 0, 0, 300), (0, 0, 0, 50, 100, 300)]) + res = np.array([[1, 1, 1, 1], + [0.85176471, 0.91029412, 0.96023529, 1], + [0.70452941, 0.82764706, 0.93358824, 1], + [0.94358824, 0.88505882, 0.83511765, 1], + [0.89729412, 0.77417647, 0.66823529, 1], + [0, 0, 0, 1]]) + assert_allclose(cs, res, atol=0.01) + + # call mix floats integers + # check calling with bytes = True + cs = cmap([(0, 50, 100, 0, 0, 300), (0, 0, 0, 50, 100, 300)], bytes=True) + res = np.array([[255, 255, 255, 255], + [217, 232, 244, 255], + [179, 211, 238, 255], + [240, 225, 212, 255], + [228, 197, 170, 255], + [0, 0, 0, 255]]) + assert_allclose(cs, res, atol=0.01) + + cs = cmap([(0, 50, 100, 0, 0, 300), (0, 0, 0, 50, 100, 300)], alpha=0.5) + res = np.array([[1, 1, 1, 0.5], + [0.85176471, 0.91029412, 0.96023529, 0.5], + [0.70452941, 0.82764706, 0.93358824, 0.5], + [0.94358824, 0.88505882, 0.83511765, 0.5], + [0.89729412, 0.77417647, 0.66823529, 0.5], + [0, 0, 0, 0.5]]) + assert_allclose(cs, res, atol=0.01) + # call with tuple + assert_allclose(cmap((100, 120), bytes=True, alpha=0.5), + [149, 142, 136, 127], atol=0.01) + + # alpha and bytes + cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)], bytes=True, alpha=0.5) + res = np.array([[0, 0, 255, 127], + [141, 0, 255, 127], + [255, 0, 255, 127], + [0, 115, 255, 127], + [0, 255, 255, 127], + [255, 255, 255, 127]]) + + # bad alpha shape + with pytest.raises(ValueError, match="alpha is array-like but its shape"): + cs = cmap([(0, 5, 9), (0, 0, 0)], bytes=True, alpha=(0.5, 0.3)) + + cmap.set_bad((1, 1, 1, 1)) + cs = cmap([(0., 1.1, np.nan), (0., 1.2, 1.)]) + res = np.array([[1., 1., 1., 1.], + [0., 0., 0., 1.], + [1., 1., 1., 1.]]) + assert_allclose(cs, res, atol=0.01) + + # call outside with tuple + assert_allclose(cmap((300, 300), bytes=True, alpha=0.5), + [0, 0, 0, 127], atol=0.01) + + with pytest.raises(ValueError, + match="For the selected colormap the data must have"): + cs = cmap((0, 5, 9)) + + +def test_multivar_bad_mode(): + cmap = mpl.multivar_colormaps['2VarSubA'] + with pytest.raises(ValueError, match="Combination_mode must be 'Add' or 'Sub'"): + cmap.combination_mode = 'bad' + + +def test_bivar_cmap_call_tuple(): + cmap = mpl.bivar_colormaps['BiOrangeBlue'] + assert_allclose(cmap((1.0, 1.0)), (1, 1, 1, 1), atol=0.01) + assert_allclose(cmap((0.0, 0.0)), (0, 0, 0, 1), atol=0.1) + assert_allclose(cmap((0.0, 0.0), alpha=0.1), (0, 0, 0, 0.1), atol=0.1) + + +def test_bivar_cmap_call(): + """ + Tests calling a bivariate colormap with integer values + """ + im = np.ones((10, 12, 4)) + im[:, :, 0] = np.linspace(0, 1, 10)[:, np.newaxis] + im[:, :, 1] = np.linspace(0, 1, 12)[np.newaxis, :] + cmap = mpl.colors.BivarColormapFromImage(im) + + # call only integers + cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)]) + res = np.array([[0, 0, 1, 1], + [0.556, 0, 1, 1], + [1, 0, 1, 1], + [0, 0.454, 1, 1], + [0, 1, 1, 1], + [1, 1, 1, 1]]) + assert_allclose(cs, res, atol=0.01) + + # call mix floats integers + cmap.set_outside((1, 0, 0, 0)) + cs = cmap([(0.5, 0), (0, 3)]) + res = np.array([[0.555, 0, 1, 1], + [0, 0.2727, 1, 1]]) + assert_allclose(cs, res, atol=0.01) + + # check calling with bytes = True + cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)], bytes=True) + res = np.array([[0, 0, 255, 255], + [141, 0, 255, 255], + [255, 0, 255, 255], + [0, 115, 255, 255], + [0, 255, 255, 255], + [255, 255, 255, 255]]) + assert_allclose(cs, res, atol=0.01) + + # test alpha + cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)], alpha=0.5) + res = np.array([[0, 0, 1, 0.5], + [0.556, 0, 1, 0.5], + [1, 0, 1, 0.5], + [0, 0.454, 1, 0.5], + [0, 1, 1, 0.5], + [1, 1, 1, 0.5]]) + assert_allclose(cs, res, atol=0.01) + # call with tuple + assert_allclose(cmap((10, 12), bytes=True, alpha=0.5), + [255, 255, 255, 127], atol=0.01) + + # alpha and bytes + cs = cmap([(0, 5, 9, 0, 0, 10), (0, 0, 0, 5, 11, 12)], bytes=True, alpha=0.5) + res = np.array([[0, 0, 255, 127], + [141, 0, 255, 127], + [255, 0, 255, 127], + [0, 115, 255, 127], + [0, 255, 255, 127], + [255, 255, 255, 127]]) + + # bad alpha shape + with pytest.raises(ValueError, match="alpha is array-like but its shape"): + cs = cmap([(0, 5, 9), (0, 0, 0)], bytes=True, alpha=(0.5, 0.3)) + + # set shape to 'ignore'. + # final point is outside colormap and should then receive + # the 'outside' (in this case [1,0,0,0]) + # also test 'bad' (in this case [1,1,1,0]) + cmap.shape = 'ignore' + cmap.set_outside((1, 0, 0, 0)) + cmap.set_bad((1, 1, 1, 0)) + cs = cmap([(0., 1.1, np.nan), (0., 1.2, 1.)]) + res = np.array([[0, 0, 1, 1], + [1, 0, 0, 0], + [1, 1, 1, 0]]) + assert_allclose(cs, res, atol=0.01) + # call outside with tuple + assert_allclose(cmap((10, 12), bytes=True, alpha=0.5), + [255, 0, 0, 127], atol=0.01) + # with integers + # cmap = mpl.colors.BivarColormapFromImage(im) + # cmap.shape = 'ignore' + # cmap.set_outside((1, 0, 0, 0)) + cs = cmap([(0, 10), (0, 12)]) + res = np.array([[0, 0, 1, 1], + [1, 0, 0, 0]]) + assert_allclose(cs, res, atol=0.01) + + with pytest.raises(ValueError, + match="For a `BivarColormap` the data must have"): + cs = cmap((0, 5, 9)) + + cmap.shape = 'circle' + with pytest.raises(NotImplementedError, + match="only implemented for use with with floats"): + cs = cmap([(0, 5, 9, 0, 0, 9), (0, 0, 0, 5, 11, 11)]) + + +def test_bivar_cmap_bad_shape(): + """ + Tests calling a bivariate colormap with integer values + """ + cmap = mpl.bivar_colormaps['BiCone'] + with pytest.raises(ValueError, + match="shape must be a valid string"): + cmap.shape = 'bad shape' + + +def test_bivar_cmap_bad_lut(): + """ + Tests calling a bivariate colormap with integer values + """ + with pytest.raises(ValueError, + match="The lut must be an array of shape"): + cmap = mpl.colors.BivarColormapFromImage(np.ones((3, 3, 5))) + + +def test_bivar_cmap_from_image(): + """ + This tests the creation and use of a bivariate colormap + generated from an image + """ + + data_0 = np.arange(6).reshape((2, 3))/5 + data_1 = np.arange(6).reshape((3, 2)).T/5 + + # bivariate colormap from array + im = np.ones((10, 12, 4)) + im[:, :, 0] = np.arange(10)[:, np.newaxis]/10 + im[:, :, 1] = np.arange(12)[np.newaxis, :]/12 + + cmap = mpl.colors.BivarColormapFromImage(im, 'custom') + im = cmap((data_0, data_1)) + res = np.array([[[0, 0, 1, 1], + [0.2, 0.33333333, 1, 1], + [0.4, 0.75, 1, 1]], + [[0.6, 0.16666667, 1, 1], + [0.8, 0.58333333, 1, 1], + [0.9, 0.91666667, 1, 1]]]) + assert_allclose(im, res, atol=0.01) + + # bivariate colormap from array + png_path = Path(__file__).parent / "baseline_images/pngsuite/basn2c16.png" + im = Image.open(png_path) + im = np.asarray(im.convert('RGBA')) + + cmap = mpl.colors.BivarColormapFromImage(im, 'custom') + im = cmap((data_0, data_1)) + + res = np.array([[[255, 255, 0, 255], + [156, 206, 0, 255], + [49, 156, 49, 255]], + [[206, 99, 0, 255], + [99, 49, 107, 255], + [0, 0, 255, 255]]]) + assert_allclose(im, res, atol=0.01) + + +def test_bivariate_repr_png(): + cmap = mpl.bivar_colormaps['BiCone'] + png = cmap._repr_png_() + assert len(png) > 0 + img = Image.open(BytesIO(png)) + assert img.width > 0 + assert img.height > 0 + assert 'Title' in img.text + assert 'Description' in img.text + assert 'Author' in img.text + assert 'Software' in img.text + + +def test_bivariate_repr_html(): + cmap = mpl.bivar_colormaps['BiCone'] + html = cmap._repr_html_() + assert len(html) > 0 + png = cmap._repr_png_() + assert base64.b64encode(png).decode('ascii') in html + assert cmap.name in html + assert html.startswith('') + + +def test_multivariate_repr_html(): + cmap = mpl.multivar_colormaps['3VarAddA'] + html = cmap._repr_html_() + assert len(html) > 0 + for c in cmap.colormaps: + png = c._repr_png_() + assert base64.b64encode(png).decode('ascii') in html + assert cmap.name in html + assert html.startswith('') + + +def test_bivar_eq(): + """ + Tests equality between multivariate colormaps + """ + cmap_0 = mpl.bivar_colormaps['BiPeak'] + + cmap_1 = mpl.bivar_colormaps['BiPeak'] + assert (cmap_0 == cmap_1) is True + + cmap_1 = mpl.multivar_colormaps['2VarAddA'] + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiCone'] + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiPeak'] + cmap_1.set_bad('k') + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiPeak'] + cmap_1.set_outside('k') + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiPeak'] + cmap_1._init() + cmap_1._lut *= 0.5 + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiPeak'] + cmap_1.shape = 'ignore' + assert (cmap_0 == cmap_1) is False + + +def test_multivar_eq(): + """ + Tests equality between multivariate colormaps + """ + cmap_0 = mpl.multivar_colormaps['2VarAddA'] + + cmap_1 = mpl.multivar_colormaps['2VarAddA'] + assert (cmap_0 == cmap_1) is True + + cmap_1 = mpl.bivar_colormaps['BiPeak'] + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.colors.MultivarColormap('2VarAddA', + [cmap_0[0]]*2, + 'Add') + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.multivar_colormaps['3VarAddA'] + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.multivar_colormaps['2VarAddA'] + cmap_1.set_bad('k') + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.multivar_colormaps['2VarAddA'] + cmap_1.combination_mode = 'Sub' + assert (cmap_0 == cmap_1) is False From f09791c8b82b9db49b7d69a0752f53f4d8e9e548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Tue, 2 Jul 2024 15:51:17 +0200 Subject: [PATCH 02/18] __getitem__ for colors.BivarColormap Adds support for __getitem__ on colors.BivarColormap, i.e.: BivarColormap[0] and BivarColormap[1], which returns (1D) Colormap objects along the selected axes --- lib/matplotlib/_cm_bivar.py | 6 +-- lib/matplotlib/colors.py | 47 +++++++++++++++++-- lib/matplotlib/colors.pyi | 8 ++-- .../tests/test_multivariate_colormaps.py | 25 +++++++++- 4 files changed, 74 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/_cm_bivar.py b/lib/matplotlib/_cm_bivar.py index c9ff59930bf3..d64dcaf87de5 100644 --- a/lib/matplotlib/_cm_bivar.py +++ b/lib/matplotlib/_cm_bivar.py @@ -1305,8 +1305,8 @@ cmaps = { "BiPeak": SegmentedBivarColormap( - BiPeak, "BiPeak", 256, "square"), + BiPeak, "BiPeak", 256, "square", (128, 128)), "BiOrangeBlue": SegmentedBivarColormap( - BiOrangeBlue, "BiOrangeBlue", 256, "square"), - "BiCone": SegmentedBivarColormap(BiPeak, "BiCone", 256, "circle"), + BiOrangeBlue, "BiOrangeBlue", 256, "square", (0, 0)), + "BiCone": SegmentedBivarColormap(BiPeak, "BiCone", 256, "circle", (128, 128)), } diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 0cd4846e06b4..f114915048a4 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1415,7 +1415,7 @@ class BivarColormap(ColormapBase): lookup table. To be used with `~matplotlib.cm.VectorMappable`. """ - def __init__(self, name, N=256, M=256, shape='square'): + def __init__(self, name, N=256, M=256, shape='square', origin=(0, 0)): """ Parameters ---------- @@ -1437,6 +1437,10 @@ def __init__(self, name, N=256, M=256, shape='square'): - If 'circleignore' a circular mask is applied, but the data is not clipped and instead assigned the 'outside' color + origin: (int, int) + The relative origin of the colormap. Typically (0, 0), for colormaps + that are linear on both axis, and (int(N*0.5), int(.5*M) for + circular colormaps. """ self.name = name @@ -1447,6 +1451,7 @@ def __init__(self, name, N=256, M=256, shape='square'): self._rgba_outside = (1.0, 0.0, 1.0, 1.0) self._isinit = False self.n_variates = 2 + self._origin = origin '''#: When this colormap exists on a scalar mappable and colorbar_extend #: is not False, colorbar creation will pick up ``colorbar_extend`` as #: the default value for the ``extend`` keyword in the @@ -1693,6 +1698,30 @@ def _clip(self, X): X[0][mask_outside] = -1 X[1][mask_outside] = -1 + def __getitem__(self, item): + """Creates and returns a colorbar along the selected axis""" + if not self._isinit: + self._init() + if item == 0: + cmap = Colormap(self.name+'0', self.N) + one_d_lut = self._lut[:, self._origin[1]] + elif item == 1: + cmap = Colormap(self.name+'1', self.M) + one_d_lut = self._lut[self._origin[0], :] + else: + raise KeyError(f"only 0 or 1 are" + f" valid keys for BivarColormap, not {item!r}") + cmap._lut = np.zeros((self.N + 3, 4), float) + cmap._lut[:-3] = one_d_lut + cmap.set_bad(self._rgba_bad) + self._rgba_outside + if self.shape in ['ignore', 'circleignore']: + cmap.set_under(self._rgba_outside) + cmap.set_over(self._rgba_outside) + cmap._set_extremes() + cmap._isinit = True + return cmap + def _repr_png_(self): """Generate a PNG representation of the BivarColormap.""" if not self._isinit: @@ -1770,11 +1799,15 @@ class SegmentedBivarColormap(BivarColormap): 'outside' color - If 'circleignore' a circular mask is applied, but the data is not clipped + origin: (int, int) + The relative origin of the colormap. Typically (0, 0), for colormaps + that are linear on both axis, and (int(N*0.5), int(.5*M) for + circular colormaps. """ - def __init__(self, patch, name, N=256, shape='square'): + def __init__(self, patch, name, N=256, shape='square', origin=(0, 0)): self.patch = patch - super().__init__(name, N, N, shape) + super().__init__(name, N, N, shape, origin) def _init(self): s = self.patch.shape @@ -1810,9 +1843,13 @@ class BivarColormapFromImage(BivarColormap): 'outside' color - If 'circleignore' a circular mask is applied, but the data is not clipped + origin: (int, int) + The relative origin of the colormap. Typically (0, 0), for colormaps + that are linear on both axis, and (int(N*0.5), int(.5*M) for + circular colormaps. """ - def __init__(self, lut, name='', shape='square'): + def __init__(self, lut, name='', shape='square', origin=(0, 0)): # We can allow for a PIL.Image as unput in the following way, but importing # matplotlib.image.pil_to_array() results in a circular import # For now, this function only accepts numpy arrays. @@ -1823,7 +1860,7 @@ def __init__(self, lut, name='', shape='square'): raise ValueError("The lut must be an array of shape (n, m, 3) or (n, m, 4)", " or a PIL.image encoded as RGB or RGBA") self._lut = lut - super().__init__(name, lut.shape[0], lut.shape[1], shape) + super().__init__(name, lut.shape[0], lut.shape[1], shape, origin) def _init(self): self._isinit = True diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index dc317101be8b..ff7750cdd31b 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -173,7 +173,8 @@ class BivarColormap(ColormapBase): M: int shape: str n_variates: int - def __init__(self, name: str, N: int = ..., M: int | None = ..., shape: str = ...) -> None: ... + def __init__(self, name: str, N: int = ..., M: int | None = ..., shape: str = ..., origin: tuple[int, int] = ... + ) -> None: ... @overload def __call__( self, X: Sequence[Sequence[float]] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ... @@ -198,11 +199,12 @@ class BivarColormap(ColormapBase): class SegmentedBivarColormap(BivarColormap): def __init__( - self, patch: np.ndarray, name: str, N: int = ..., shape: str = ..., + self, patch: np.ndarray, name: str, N: int = ..., shape: str = ..., origin: tuple[int, int] = ... ) -> None: ... class BivarColormapFromImage(BivarColormap): - def __init__(self, lut: np.ndarray, name: str = ..., shape: str = ...) -> None: ... + def __init__(self, lut: np.ndarray, name: str = ..., shape: str = ..., origin: tuple[int, int] = ... + ) -> None: ... class Normalize: callbacks: cbook.CallbackRegistry diff --git a/lib/matplotlib/tests/test_multivariate_colormaps.py b/lib/matplotlib/tests/test_multivariate_colormaps.py index 3033faad2df0..267c9e6999c6 100644 --- a/lib/matplotlib/tests/test_multivariate_colormaps.py +++ b/lib/matplotlib/tests/test_multivariate_colormaps.py @@ -281,7 +281,30 @@ def test_bivar_cmap_call(): match="only implemented for use with with floats"): cs = cmap([(0, 5, 9, 0, 0, 9), (0, 0, 0, 5, 11, 11)]) - +def test_bivar_getitem(): + '''Test __getitem__ on BivarColormap''' + xA = ([.0, .25, .5, .75, 1., -1, 2], [.5]*7) + xB = ([.5]*7, [.0, .25, .5, .75, 1., -1, 2]) + + cmaps = mpl.bivar_colormaps['BiPeak'] + assert_array_equal(cmaps(xA), cmaps[0](xA[0])) + assert_array_equal(cmaps(xB), cmaps[1](xB[1])) + + cmaps.shape = 'ignore' + assert_array_equal(cmaps(xA), cmaps[0](xA[0])) + assert_array_equal(cmaps(xB), cmaps[1](xB[1])) + + xA = ([.0, .25, .5, .75, 1., -1, 2], [.0]*7) + xB = ([.0]*7, [.0, .25, .5, .75, 1., -1, 2]) + cmaps = mpl.bivar_colormaps['BiOrangeBlue'] + assert_array_equal(cmaps(xA), cmaps[0](xA[0])) + assert_array_equal(cmaps(xB), cmaps[1](xB[1])) + + cmaps.shape = 'ignore' + assert_array_equal(cmaps(xA), cmaps[0](xA[0])) + assert_array_equal(cmaps(xB), cmaps[1](xB[1])) + + def test_bivar_cmap_bad_shape(): """ Tests calling a bivariate colormap with integer values From 8c848aadef264a1424271acd782bb1aa393de942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Tue, 2 Jul 2024 15:51:52 +0200 Subject: [PATCH 03/18] Better descriptors for 'Add' and 'Sub' in colors.MultivarColormap --- lib/matplotlib/colors.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index f114915048a4..50367fbc114b 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1259,8 +1259,11 @@ def __init__(self, name, colormaps, combination_mode): The individual colormaps that are combined combination_mode: str, 'Add' or 'Sub' Describe how colormaps are combined in sRGB space - 'Add' -> additive - 'Sub' -> subtractive + + - If 'Add' -> Mixing produces brighter colors + `sRGB = cmap[0][X[0]] + cmap[1][x[1]] + ... + cmap[n-1][x[n-1]]` + - If 'Sub' -> Mixing produces darker colors + `sRGB = cmap[0][X[0]] + cmap[1][x[1]] + ... + cmap[n-1][x[n-1]] - n + 1` """ self.name = name From 2213a88cc67d43ed328b446d6cafbbc4b7d44028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Wed, 3 Jul 2024 11:35:20 +0200 Subject: [PATCH 04/18] minor fixes for MultivarColormap and BivarColormap removal of ColormapBase Addition of _repr_png_() for MultivarColormap addition of _get_rgba_and_mask() for Colormap to clean up __call__() --- lib/matplotlib/colors.py | 95 ++++++++++++------- lib/matplotlib/colors.pyi | 14 +-- .../tests/test_multivariate_colormaps.py | 12 +-- 3 files changed, 77 insertions(+), 44 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 50367fbc114b..366bc6903740 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -675,16 +675,7 @@ def _create_lookup_table(N, data, gamma=1.0): return np.clip(lut, 0.0, 1.0) -class ColormapBase: - """ - Base class for all colormaps, both scalar, bivariate and multivariate. - - This class is used for type checking, and cannot be initialized. - """ - ... - - -class Colormap(ColormapBase): +class Colormap: """ Baseclass for all scalar to RGBA mappings. @@ -721,7 +712,7 @@ def __init__(self, name, N=256): #: `matplotlib.colorbar.Colorbar` constructor. self.colorbar_extend = False - def __call__(self, X, alpha=None, bytes=False, return_mask_bad=False): + def __call__(self, X, alpha=None, bytes=False): r""" Parameters ---------- @@ -737,15 +728,40 @@ def __call__(self, X, alpha=None, bytes=False, return_mask_bad=False): bytes : bool If False (default), the returned RGBA values will be floats in the interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the - interval ``[0, 255]``. - return_mask_bad : bool - If true, also return a mask of bad values. + interval ``[0, 255]`` Returns ------- Tuple of RGBA values if X is scalar, otherwise an array of RGBA values with a shape of ``X.shape + (4, )``. """ + rgba, mask = self._get_rgba_and_mask(X, alpha=alpha, bytes=bytes) + return rgba + + def _get_rgba_and_mask(self, X, alpha=None, bytes=False): + r""" + Parameters + ---------- + X : float or int, `~numpy.ndarray` or scalar + The data value(s) to convert to RGBA. + For floats, *X* should be in the interval ``[0.0, 1.0]`` to + return the RGBA values ``X*100`` percent along the Colormap line. + For integers, *X* should be in the interval ``[0, Colormap.N)`` to + return RGBA values *indexed* from the Colormap with index ``X``. + alpha : float or array-like or None + Alpha must be a scalar between 0 and 1, a sequence of such + floats with shape matching X, or None. + bytes : bool + If False (default), the returned RGBA values will be floats in the + interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the + interval ``[0, 255]``. + + Returns + ------- + (colors, mask), where color is a tuple of RGBA values if X is scalar, + otherwise an array of RGBA values with a shape of ``X.shape + (4, )``, + and mask is a boolean array. + """ if not self._isinit: self._init() @@ -791,9 +807,7 @@ def __call__(self, X, alpha=None, bytes=False, return_mask_bad=False): if not np.iterable(X): rgba = tuple(rgba) - if return_mask_bad: - return rgba, mask_bad - return rgba + return rgba, mask_bad def __copy__(self): cls = self.__class__ @@ -1241,13 +1255,10 @@ def reversed(self, name=None): return new_cmap -class MultivarColormap(ColormapBase): +class MultivarColormap: """ Class for holding multiple `~matplotlib.colors.Colormap` for use in a `~matplotlib.cm.VectorMappable` object - - MultivarColormap does not support alpha in the constituent - look up tables (ignored). """ def __init__(self, name, colormaps, combination_mode): """ @@ -1259,7 +1270,7 @@ def __init__(self, name, colormaps, combination_mode): The individual colormaps that are combined combination_mode: str, 'Add' or 'Sub' Describe how colormaps are combined in sRGB space - + - If 'Add' -> Mixing produces brighter colors `sRGB = cmap[0][X[0]] + cmap[1][x[1]] + ... + cmap[n-1][x[n-1]]` - If 'Sub' -> Mixing produces darker colors @@ -1267,12 +1278,18 @@ def __init__(self, name, colormaps, combination_mode): """ self.name = name - if not np.iterable(colormaps) or len(colormaps) == 1: + if not np.iterable(colormaps) \ + or len(colormaps) == 1 \ + or isinstance(colormaps, str): raise ValueError("A MultivarColormap must have more than one colormap.") - for cmap in colormaps: + colormaps = list(colormaps) # ensure cmaps is a list, i.e. not a tuple + for i, cmap in enumerate(colormaps): if not issubclass(type(cmap), Colormap): - raise ValueError("colormaps must be a list of objects that subclass" - " Colormap, not strings or list of strings") + if isinstance(cmap, str): + colormaps[i] = mpl.colormaps[cmap] + else: + raise ValueError("colormaps must be a list of objects that subclass" + " Colormap or valid strings.") self.colormaps = colormaps self.combination_mode = combination_mode @@ -1310,10 +1327,10 @@ def __call__(self, X, alpha=None, bytes=False): raise ValueError( f'For the selected colormap the data must have a first dimension ' f'{len(self)}, not {len(X)}') - rgba, mask_bad = self[0](X[0], bytes=False, return_mask_bad=True) + rgba, mask_bad = self[0]._get_rgba_and_mask(X[0], bytes=False) rgba = np.asarray(rgba) for c, xx in zip(self[1:], X[1:]): - sub_rgba, sub_mask_bad = c(xx, bytes=False, return_mask_bad=True) + sub_rgba, sub_mask_bad = c._get_rgba_and_mask(xx, bytes=False) sub_rgba = np.asarray(sub_rgba) rgba[..., :3] += sub_rgba[..., :3] # add colors rgba[..., 3] *= sub_rgba[..., 3] # multiply alpha @@ -1401,16 +1418,30 @@ def combination_mode(self, mode): self._combination_mode = mode def _repr_png_(self): - raise NotImplementedError("no png representation of MultivarColormap" - " but you may access png repreesntations of the" - " individual colorbars.") + """Generate a PNG representation of the Colormap.""" + X = np.tile(np.linspace(0, 1, _REPR_PNG_SIZE[0]), + (_REPR_PNG_SIZE[1], 1)) + pixels = np.zeros((_REPR_PNG_SIZE[1]*len(self), _REPR_PNG_SIZE[0], 4), + dtype=np.uint8) + for i, c in enumerate(self): + pixels[i*_REPR_PNG_SIZE[1]:(i+1)*_REPR_PNG_SIZE[1], :] = c(X, bytes=True) + png_bytes = io.BytesIO() + title = self.name + ' multivariate colormap' + author = f'Matplotlib v{mpl.__version__}, https://matplotlib.org' + pnginfo = PngInfo() + pnginfo.add_text('Title', title) + pnginfo.add_text('Description', title) + pnginfo.add_text('Author', author) + pnginfo.add_text('Software', author) + Image.fromarray(pixels).save(png_bytes, format='png', pnginfo=pnginfo) + return png_bytes.getvalue() def _repr_html_(self): """Generate an HTML representation of the MultivarColormap.""" return ''.join([c._repr_html_() for c in self.colormaps]) -class BivarColormap(ColormapBase): +class BivarColormap: """ Baseclass for all bivarate to RGBA mappings. diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index ff7750cdd31b..0a24082389be 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -64,10 +64,7 @@ class ColorConverter: colorConverter: ColorConverter -class ColormapBase: - ... - -class Colormap(ColormapBase): +class Colormap: name: str N: int colorbar_extend: bool @@ -141,7 +138,7 @@ class ListedColormap(Colormap): def resampled(self, lutsize: int) -> ListedColormap: ... def reversed(self, name: str | None = ...) -> ListedColormap: ... -class MultivarColormap(ColormapBase): +class MultivarColormap: name: str colormaps: list[Colormap] combination_mode: str @@ -166,8 +163,10 @@ class MultivarColormap(ColormapBase): def __len__(self) -> int: ... def get_bad(self) -> np.ndarray: ... def set_bad(self, color: ColorType = ..., alpha: float | None = ...) -> None: ... + def _repr_html_(self) -> str: ... + def _repr_png_(self) -> bytes: ... -class BivarColormap(ColormapBase): +class BivarColormap: name: str N: int M: int @@ -190,12 +189,15 @@ class BivarColormap(ColormapBase): @property def lut(self) -> np.ndarray: ... def __copy__(self) -> BivarColormap: ... + def __getitem__(self, item: int) -> Colormap: ... def __eq__(self, other) -> bool: ... def get_bad(self) -> np.ndarray: ... def set_bad(self, color: ColorType = ..., alpha: float | None = ...) -> None: ... def get_outside(self) -> np.ndarray: ... def set_outside(self, color: ColorType = ..., alpha: float | None = ...) -> None: ... def copy(self) -> BivarColormap: ... + def _repr_html_(self) -> str: ... + def _repr_png_(self) -> bytes: ... class SegmentedBivarColormap(BivarColormap): def __init__( diff --git a/lib/matplotlib/tests/test_multivariate_colormaps.py b/lib/matplotlib/tests/test_multivariate_colormaps.py index 267c9e6999c6..089c1aa1de30 100644 --- a/lib/matplotlib/tests/test_multivariate_colormaps.py +++ b/lib/matplotlib/tests/test_multivariate_colormaps.py @@ -41,8 +41,7 @@ def test_bivariate_cmap_shapes(): def test_multivar_creation(): # test creation of a custom multivariate colorbar blues = mpl.colormaps['Blues'] - oranges = mpl.colormaps['Oranges'] - cmap = mpl.colors.MultivarColormap('custom', (blues, oranges), 'Sub') + cmap = mpl.colors.MultivarColormap('custom', (blues, 'Oranges'), 'Sub') y, x = np.mgrid[0:3, 0:3]/2 im = cmap((y, x)) res = np.array([[[0.96862745, 0.94509804, 0.92156863, 1], @@ -57,8 +56,8 @@ def test_multivar_creation(): assert_allclose(im, res, atol=0.01) with pytest.raises(ValueError, match="colormaps must be a list of"): - cmap = mpl.colors.MultivarColormap('custom', (blues, 'Oranges'), 'Sub') - with pytest.raises(ValueError, match="colormaps must be a list of"): + cmap = mpl.colors.MultivarColormap('custom', (blues, [blues]), 'Sub') + with pytest.raises(ValueError, match="A MultivarColormap must"): cmap = mpl.colors.MultivarColormap('custom', 'blues', 'Sub') with pytest.raises(ValueError, match="A MultivarColormap must"): cmap = mpl.colors.MultivarColormap('custom', (blues), 'Sub') @@ -281,8 +280,9 @@ def test_bivar_cmap_call(): match="only implemented for use with with floats"): cs = cmap([(0, 5, 9, 0, 0, 9), (0, 0, 0, 5, 11, 11)]) + def test_bivar_getitem(): - '''Test __getitem__ on BivarColormap''' + """Test __getitem__ on BivarColormap""" xA = ([.0, .25, .5, .75, 1., -1, 2], [.5]*7) xB = ([.5]*7, [.0, .25, .5, .75, 1., -1, 2]) @@ -304,7 +304,7 @@ def test_bivar_getitem(): assert_array_equal(cmaps(xA), cmaps[0](xA[0])) assert_array_equal(cmaps(xB), cmaps[1](xB[1])) - + def test_bivar_cmap_bad_shape(): """ Tests calling a bivariate colormap with integer values From 446e1bdaa58cccc8d7c4fee2d458df6eac4da97c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Wed, 3 Jul 2024 13:20:28 +0200 Subject: [PATCH 05/18] Allows one to not clip to 0...1 in colors.MultivarColormap.__call__() Also adds a an improvement to colors.BIvarColormap.__getitem__() so that this returns a ListedColormap object instead of a Colormap object --- lib/matplotlib/colors.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 366bc6903740..70fe22178f68 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1296,7 +1296,7 @@ def __init__(self, name, colormaps, combination_mode): self.n_variates = len(colormaps) self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything. - def __call__(self, X, alpha=None, bytes=False): + def __call__(self, X, alpha=None, bytes=False, clip=True): r""" Parameters ---------- @@ -1316,6 +1316,8 @@ def __call__(self, X, alpha=None, bytes=False): If False (default), the returned RGBA values will be floats in the interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the interval ``[0, 255]``. + clip : bool + If True, clip output to 0 to 1 Returns ------- @@ -1341,10 +1343,12 @@ def __call__(self, X, alpha=None, bytes=False): rgba[mask_bad] = self.get_bad() - rgba = np.clip(rgba, 0, 1) + if clip: + rgba = np.clip(rgba, 0, 1) if alpha is not None: - alpha = np.clip(alpha, 0, 1) + if clip: + alpha = np.clip(alpha, 0, 1) if alpha.shape not in [(), np.array(X[0]).shape]: raise ValueError( f"alpha is array-like but its shape {alpha.shape} does " @@ -1352,6 +1356,11 @@ def __call__(self, X, alpha=None, bytes=False): rgba[..., -1] *= alpha if bytes: + if not clip: + raise ValueError( + "clip cannot be false while bytes is true" + " as uint8 does not support values below 0" + " or above 255.") rgba = (rgba * 255).astype('uint8') if not np.iterable(X[0]): @@ -1737,24 +1746,20 @@ def __getitem__(self, item): if not self._isinit: self._init() if item == 0: - cmap = Colormap(self.name+'0', self.N) one_d_lut = self._lut[:, self._origin[1]] + new_cmap = ListedColormap(one_d_lut, name=self.name+'_0', N=self.N) + elif item == 1: - cmap = Colormap(self.name+'1', self.M) one_d_lut = self._lut[self._origin[0], :] + new_cmap = ListedColormap(one_d_lut, name=self.name+'_1', N=self.M) else: raise KeyError(f"only 0 or 1 are" f" valid keys for BivarColormap, not {item!r}") - cmap._lut = np.zeros((self.N + 3, 4), float) - cmap._lut[:-3] = one_d_lut - cmap.set_bad(self._rgba_bad) - self._rgba_outside + new_cmap._rgba_bad = self._rgba_bad if self.shape in ['ignore', 'circleignore']: - cmap.set_under(self._rgba_outside) - cmap.set_over(self._rgba_outside) - cmap._set_extremes() - cmap._isinit = True - return cmap + new_cmap.set_over(self._rgba_outside) + new_cmap.set_under(self._rgba_outside) + return new_cmap def _repr_png_(self): """Generate a PNG representation of the BivarColormap.""" From 04228c791edc3a4802a4435d622c4d1bfb3a15e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Fri, 5 Jul 2024 14:03:52 +0200 Subject: [PATCH 06/18] Multivariate and bivariate resampling of colormaps Also removed all set_ functions, and replaced them with with_extremes() --- lib/matplotlib/_cm_bivar.py | 4 +- lib/matplotlib/colors.py | 316 +++++++++++++++--- lib/matplotlib/colors.pyi | 28 +- .../tests/test_multivariate_colormaps.py | 122 +++++-- 4 files changed, 383 insertions(+), 87 deletions(-) diff --git a/lib/matplotlib/_cm_bivar.py b/lib/matplotlib/_cm_bivar.py index d64dcaf87de5..cf3c91dc6632 100644 --- a/lib/matplotlib/_cm_bivar.py +++ b/lib/matplotlib/_cm_bivar.py @@ -1305,8 +1305,8 @@ cmaps = { "BiPeak": SegmentedBivarColormap( - BiPeak, "BiPeak", 256, "square", (128, 128)), + BiPeak, "BiPeak", 256, "square", (.5, .5)), "BiOrangeBlue": SegmentedBivarColormap( BiOrangeBlue, "BiOrangeBlue", 256, "square", (0, 0)), - "BiCone": SegmentedBivarColormap(BiPeak, "BiCone", 256, "circle", (128, 128)), + "BiCone": SegmentedBivarColormap(BiPeak, "BiCone", 256, "circle", (.5, .5)), } diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 70fe22178f68..2f3cb04084b5 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1291,8 +1291,11 @@ def __init__(self, name, colormaps, combination_mode): raise ValueError("colormaps must be a list of objects that subclass" " Colormap or valid strings.") - self.colormaps = colormaps - self.combination_mode = combination_mode + self._colormaps = colormaps + if combination_mode not in ['Add', 'Sub']: + raise ValueError("Combination_mode must be 'Add' or 'Sub'," + f" {combination_mode!r} is not allowed.") + self._combination_mode = combination_mode self.n_variates = len(colormaps) self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything. @@ -1376,7 +1379,7 @@ def __copy__(self): cls = self.__class__ cmapobject = cls.__new__(cls) cmapobject.__dict__.update(self.__dict__) - cmapobject.colormaps = [cm.copy() for cm in self.colormaps] + cmapobject._colormaps = [cm.copy() for cm in self._colormaps] cmapobject._rgba_bad = np.copy(self._rgba_bad) return cmapobject @@ -1395,14 +1398,14 @@ def __eq__(self, other): return True def __getitem__(self, item): - return self.colormaps[item] + return self._colormaps[item] def __iter__(self): - for c in self.colormaps: + for c in self._colormaps: yield c def __len__(self): - return len(self.colormaps) + return len(self._colormaps) def __str__(self): return self.name @@ -1411,21 +1414,80 @@ def get_bad(self): """Get the color for masked values.""" return np.array(self._rgba_bad) - def set_bad(self, color='k', alpha=None): - """Set the color for masked values.""" - self._rgba_bad = to_rgba(color, alpha) + def resampled(self, lutshape): + """ + Return a new colormap with *lutshape* entries. + + Parameters + ---------- + lutshape : tuple of ints or None + The tuple must be of length matching the number of variates, + and each entry is either an int or None. + If an int, the corresponding colorbar is resampled. + If None, the corresponding colorbar is not resampled. + + Returns + ------- + MultivarColormap + """ + + if not np.iterable(lutshape) or not len(lutshape) == len(self): + raise ValueError(f"lutshape must be of length {len(self)}") + new_cmap = self.copy() + for i, s in enumerate(lutshape): + if s is not None: + new_cmap._colormaps[i] = self[i].resampled(s) + return new_cmap + + def with_extremes(self, *, bad=None, under=None, over=None): + """ + Return a copy of the MultivarColormap, for which the colors for masked (*bad*) + values has been set and, low (*under*) and high (*over*) out-of-range values, + been set in the component colormaps. Note that *under* and *over* colors + are subject to the mixing rules determined by the *combination_mode*. + + Parameters + ---------- + bad : None or Matplotlib color + If Matplotlib color, the bad value is set accordingly in the copy + + under : None or tuple of length matching the length of the MultivarColormap + If tuple, the `under` value of each component is set with the values + from the tuple. + + over : None or tuple of length matching the length of the MultivarColormap + If tuple, the `under` value of each component is set with the values + from the tuple. + + Returns + ------- + MultivarColormap + copy of self with attributes set + + """ + new_cm = self.copy() + if bad is not None: + new_cm._rgba_bad = to_rgba(bad) + if under is not None: + if not np.iterable(under) or not len(under) == len(new_cm): + raise ValueError("*under* must contain a color for each scalar colormap" + f" i.e. be of length {len(new_cm)}.") + else: + for c, b in zip(new_cm, under): + c.set_under(b) + if over is not None: + if not np.iterable(over) or not len(over) == len(new_cm): + raise ValueError("*over* must contain a color for each scalar colormap" + f" i.e. be of length {len(new_cm)}.") + else: + for c, b in zip(new_cm, over): + c.set_over(b) + return new_cm @property def combination_mode(self): return self._combination_mode - @combination_mode.setter - def combination_mode(self, mode): - if mode not in ['Add', 'Sub']: - raise ValueError("Combination_mode must be 'Add' or 'Sub'," - f" {mode!r} is not allowed.") - self._combination_mode = mode - def _repr_png_(self): """Generate a PNG representation of the Colormap.""" X = np.tile(np.linspace(0, 1, _REPR_PNG_SIZE[0]), @@ -1447,7 +1509,7 @@ def _repr_png_(self): def _repr_html_(self): """Generate an HTML representation of the MultivarColormap.""" - return ''.join([c._repr_html_() for c in self.colormaps]) + return ''.join([c._repr_html_() for c in self._colormaps]) class BivarColormap: @@ -1480,21 +1542,25 @@ def __init__(self, name, N=256, M=256, shape='square', origin=(0, 0)): - If 'circleignore' a circular mask is applied, but the data is not clipped and instead assigned the 'outside' color - origin: (int, int) + origin: (float, float) The relative origin of the colormap. Typically (0, 0), for colormaps - that are linear on both axis, and (int(N*0.5), int(.5*M) for - circular colormaps. + that are linear on both axis, and (.5, .5) for circular colormaps. + Used when getting 1D colormaps from 2D colormaps. """ self.name = name self.N = int(N) # ensure that N is always int self.M = int(M) - self.shape = shape + if shape in ['square', 'circle', 'ignore', 'circleignore']: + self._shape = shape + else: + raise ValueError("The shape must be a valid string, " + "'square', 'circle', 'ignore', or 'circleignore'") self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything. self._rgba_outside = (1.0, 0.0, 1.0, 1.0) self._isinit = False self.n_variates = 2 - self._origin = origin + self._origin = (float(origin[0]), float(origin[1])) '''#: When this colormap exists on a scalar mappable and colorbar_extend #: is not False, colorbar creation will pick up ``colorbar_extend`` as #: the default value for the ``extend`` keyword in the @@ -1630,7 +1696,7 @@ def __copy__(self): cmapobject._rgba_outside = np.copy(self._rgba_outside) cmapobject._rgba_bad = np.copy(self._rgba_bad) - cmapobject.shape = self.shape + cmapobject._shape = self.shape if self._isinit: cmapobject._lut = np.copy(self._lut) return cmapobject @@ -1657,17 +1723,151 @@ def get_bad(self): """Get the color for masked values.""" return self._rgba_bad - def set_bad(self, color='k', alpha=None): - """Set the color for masked values.""" - self._rgba_bad = to_rgba(color, alpha) - def get_outside(self): """Get the color for out-of-range values.""" return self._rgba_outside - def set_outside(self, color='k', alpha=None): - """Set the color for out-of-range values.""" - self._rgba_outside = to_rgba(color, alpha) + def resampled(self, lutshape, transposed=False): + """ + Return a new colormap with *lutshape* entries. + Note that this function does not move the origin. + + Parameters + ---------- + lutshape : tuple of ints or None + The tuple must be of length 2, and each entry is either an int or None. + + - If an int, the corresponding axis is resampled. + - If -1, the axis is inverted + - If negative the corresponding axis is resampled in reverse + - If 1 or None, the corresponding axis is not resampled. + + transposed : bool + if True, the axes are swapped after resampling + + Returns + ------- + BivarColormap + """ + + if not np.iterable(lutshape) or not len(lutshape) == 2: + raise ValueError("lutshape must be of length 2") + lutshape = [lutshape[0], lutshape[1]] + if lutshape[0] is None or lutshape[0] == 1: + lutshape[0] = self.N + if lutshape[1] is None or lutshape[1] == 1: + lutshape[1] = self.M + + inverted = [False, False] + if lutshape[0] < 0: + inverted[0] = True + lutshape[0] = -lutshape[0] + if lutshape[0] == 1: + lutshape[0] = self.N + if lutshape[1] < 0: + inverted[1] = True + lutshape[1] = -lutshape[1] + if lutshape[1] == 1: + lutshape[1] = self.M + if not inverted[0]: + x_0 = np.linspace(0, 1, lutshape[0])[:, np.newaxis] * np.ones(lutshape) + else: + x_0 = np.linspace(1, 0, lutshape[0])[:, np.newaxis] * np.ones(lutshape) + if not inverted[1]: + x_1 = np.linspace(0, 1, lutshape[1])[np.newaxis, :] * np.ones(lutshape) + else: + x_1 = np.linspace(1, 0, lutshape[1])[np.newaxis, :] * np.ones(lutshape) + + # we need to use shape = 'sqare' while resampling the colormap. + # if the colormap has shape = 'circle' we would otherwise get *outside* in the + # resampled colormap + shape_memory = self._shape + self._shape = 'square' + if transposed: + new_lut = self((x_1, x_0)) + new_cmap = BivarColormapFromImage(new_lut, name=self.name, + shape=shape_memory, + origin=(self.origin[1], self.origin[0])) + else: + new_lut = self((x_0, x_1)) + new_cmap = BivarColormapFromImage(new_lut, name=self.name, + shape=shape_memory, + origin=self.origin) + self._shape = shape_memory + + new_cmap._rgba_bad = self._rgba_bad + new_cmap._rgba_outside = self._rgba_outside + return new_cmap + + def reversed(self, axis_0=True, axis_1=True): + """ + Reverses both or one of the axis. + """ + r_0 = 1 + if axis_0: + r_0 = -1 + r_1 = 1 + if axis_1: + r_1 = -1 + return self.resampled((r_0, r_1)) + + def transposed(self): + """ + Transposes the colormap by swapping the order of the axis + """ + return self.resampled((None, None), transposed=True) + + def with_extremes(self, *, bad=None, outside=None, shape=None, origin=None): + """ + Return a copy of the BivarColormap, for which the colors for masked (*bad*) + valuesand if shape = 'ignore' or 'circleignore', out-of-range *outside* values, + have been set accordingly. + + Parameters + ---------- + bad : None or Matplotlib color + If Matplotlib color, the *bad* value is set accordingly in the copy + + outside : None or Matplotlib color + If Matplotlib color and shape is 'ignore' or 'circleignore', values + *outside* the colormap are colored accordingly in the copy + + shape: str 'square' or 'circle' or 'ignore' or 'circleignore' + + - If 'square' each variate is clipped to [0,1] independently + - If 'circle' the variates are clipped radially to the center + of the colormap, and a circular mask is applied when the colormap + is displayed + - If 'ignore' the variates are not clipped, but instead assigned the + *outside* color + - If 'circleignore' a circular mask is applied, but the data is not + clipped and instead assigned the *outside* color + + origin: (float, float) + The relative origin of the colormap. Typically (0, 0), for colormaps + that are linear on both axis, and (.5, .5) for circular colormaps. + Used when getting 1D colormaps from 2D colormaps. + + Returns + ------- + BivarColormap + copy of self with attributes set + """ + new_cm = self.copy() + if bad is not None: + new_cm._rgba_bad = to_rgba(bad) + if outside is not None: + new_cm._rgba_outside = to_rgba(outside) + if shape is not None: + if shape in ['square', 'circle', 'ignore', 'circleignore']: + new_cm._shape = shape + else: + raise ValueError("The shape must be a valid string, " + "'square', 'circle', 'ignore', or 'circleignore'") + if origin is not None: + self._origin = (float(origin[0]), float(origin[1])) + + return new_cm def _init(self): """Generate the lookup table, ``self._lut``.""" @@ -1677,13 +1877,9 @@ def _init(self): def shape(self): return self._shape - @shape.setter - def shape(self, shape): - if shape in ['square', 'circle', 'ignore', 'circleignore']: - self._shape = shape - else: - raise ValueError("The shape must be a valid string, " - "'square', 'circle', 'ignore', or 'circleignore'") + @property + def origin(self): + return self._origin def _clip(self, X): """ @@ -1746,11 +1942,17 @@ def __getitem__(self, item): if not self._isinit: self._init() if item == 0: - one_d_lut = self._lut[:, self._origin[1]] + o = int(self._origin[1]*self.M) + if o > self.M-1: + o = self.M-1 + one_d_lut = self._lut[:, o] new_cmap = ListedColormap(one_d_lut, name=self.name+'_0', N=self.N) elif item == 1: - one_d_lut = self._lut[self._origin[0], :] + o = int(self._origin[0]*self.N) + if o > self.N-1: + o = self.N-1 + one_d_lut = self._lut[o, :] new_cmap = ListedColormap(one_d_lut, name=self.name+'_1', N=self.M) else: raise KeyError(f"only 0 or 1 are" @@ -1765,9 +1967,16 @@ def _repr_png_(self): """Generate a PNG representation of the BivarColormap.""" if not self._isinit: self._init() - - pixels = (self.lut[::-1, :, :] * 255).astype(np.uint8) - + pixels = self.lut + if pixels.shape[0] < _BIVAR_REPR_PNG_SIZE: + pixels = np.repeat(pixels, + repeats=_BIVAR_REPR_PNG_SIZE//pixels.shape[0], + axis=0)[:256, :] + if pixels.shape[1] < _BIVAR_REPR_PNG_SIZE: + pixels = np.repeat(pixels, + repeats=_BIVAR_REPR_PNG_SIZE//pixels.shape[1], + axis=1)[:, :256] + pixels = (pixels[::-1, :, :] * 255).astype(np.uint8) png_bytes = io.BytesIO() title = self.name + ' BivarColormap' author = f'Matplotlib v{mpl.__version__}, https://matplotlib.org' @@ -1838,10 +2047,11 @@ class SegmentedBivarColormap(BivarColormap): 'outside' color - If 'circleignore' a circular mask is applied, but the data is not clipped - origin: (int, int) + origin: (float, float) The relative origin of the colormap. Typically (0, 0), for colormaps - that are linear on both axis, and (int(N*0.5), int(.5*M) for - circular colormaps. + that are linear on both axis, and (.5, .5) for circular colormaps. + Used when getting 1D colormaps from 2D colormaps. + """ def __init__(self, patch, name, N=256, shape='square', origin=(0, 0)): @@ -1882,22 +2092,32 @@ class BivarColormapFromImage(BivarColormap): 'outside' color - If 'circleignore' a circular mask is applied, but the data is not clipped - origin: (int, int) + origin: (float, float) The relative origin of the colormap. Typically (0, 0), for colormaps - that are linear on both axis, and (int(N*0.5), int(.5*M) for - circular colormaps. + that are linear on both axis, and (.5, .5) for circular colormaps. + Used when getting 1D colormaps from 2D colormaps. + """ def __init__(self, lut, name='', shape='square', origin=(0, 0)): # We can allow for a PIL.Image as unput in the following way, but importing # matplotlib.image.pil_to_array() results in a circular import # For now, this function only accepts numpy arrays. + # i.e.: # if isinstance(Image, lut): # lut = image.pil_to_array(lut) lut = np.array(lut, copy=True) if lut.ndim != 3 or lut.shape[2] not in (3, 4): raise ValueError("The lut must be an array of shape (n, m, 3) or (n, m, 4)", " or a PIL.image encoded as RGB or RGBA") + + if lut.dtype == np.uint8: + lut = lut.astype(np.float32)/255 + if lut.shape[2] == 3: + new_lut = np.empty((lut.shape[0], lut.shape[1], 4), dtype=lut.dtype) + new_lut[:, :, :3] = lut + new_lut[:, :, 3] = 1. + lut = new_lut self._lut = lut super().__init__(name, lut.shape[0], lut.shape[1], shape, origin) diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index 0a24082389be..fb04815bd860 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -162,7 +162,14 @@ class MultivarColormap: def __iter__(self) -> Colormap: ... def __len__(self) -> int: ... def get_bad(self) -> np.ndarray: ... - def set_bad(self, color: ColorType = ..., alpha: float | None = ...) -> None: ... + def resampled(self, lutsize: Sequence[int | None]) -> MultivarColormap: ... + def with_extremes( + self, + *, + bad: ColorType | None = ..., + under: Sequence[ColorType] | None = ..., + over: Sequence[ColorType] | None = ... + ) -> MultivarColormap: ... def _repr_html_(self) -> str: ... def _repr_png_(self) -> bytes: ... @@ -172,7 +179,8 @@ class BivarColormap: M: int shape: str n_variates: int - def __init__(self, name: str, N: int = ..., M: int | None = ..., shape: str = ..., origin: tuple[int, int] = ... + origin: tuple[float, float] + def __init__(self, name: str, N: int = ..., M: int | None = ..., shape: str = ..., origin: tuple[float, float] = ... ) -> None: ... @overload def __call__( @@ -192,20 +200,28 @@ class BivarColormap: def __getitem__(self, item: int) -> Colormap: ... def __eq__(self, other) -> bool: ... def get_bad(self) -> np.ndarray: ... - def set_bad(self, color: ColorType = ..., alpha: float | None = ...) -> None: ... def get_outside(self) -> np.ndarray: ... - def set_outside(self, color: ColorType = ..., alpha: float | None = ...) -> None: ... def copy(self) -> BivarColormap: ... + def resampled(self, lutsize: Sequence[int | None]) -> BivarColormap: ... + def transposed(self) -> BivarColormap: ... + def reversed(self, axis_0: bool = True, axis_1: bool = True) -> BivarColormap: ... + def with_extremes( + self, + *, + bad: ColorType | None = ..., + outside: ColorType | None = ..., + shape: str | None = ..., + ) -> MultivarColormap: ... def _repr_html_(self) -> str: ... def _repr_png_(self) -> bytes: ... class SegmentedBivarColormap(BivarColormap): def __init__( - self, patch: np.ndarray, name: str, N: int = ..., shape: str = ..., origin: tuple[int, int] = ... + self, patch: np.ndarray, name: str, N: int = ..., shape: str = ..., origin: tuple[float, float] = ... ) -> None: ... class BivarColormapFromImage(BivarColormap): - def __init__(self, lut: np.ndarray, name: str = ..., shape: str = ..., origin: tuple[int, int] = ... + def __init__(self, lut: np.ndarray, name: str = ..., shape: str = ..., origin: tuple[float, float] = ... ) -> None: ... class Normalize: diff --git a/lib/matplotlib/tests/test_multivariate_colormaps.py b/lib/matplotlib/tests/test_multivariate_colormaps.py index 089c1aa1de30..986f6a8f5f05 100644 --- a/lib/matplotlib/tests/test_multivariate_colormaps.py +++ b/lib/matplotlib/tests/test_multivariate_colormaps.py @@ -28,12 +28,12 @@ def test_bivariate_cmap_shapes(): # shape = 'ignore' cmap = mpl.bivar_colormaps['BiPeak'] - cmap.shape = 'ignore' + cmap = cmap.with_extremes(shape='ignore') axes[2].imshow(cmap((x_0, x_1)), interpolation='nearest') # shape = circleignore cmap = mpl.bivar_colormaps['BiCone'] - cmap.shape = 'circleignore' + cmap = cmap.with_extremes(shape='circleignore') axes[3].imshow(cmap((x_0, x_1)), interpolation='nearest') remove_ticks_and_titles(fig) @@ -156,7 +156,7 @@ def test_multivar_cmap_call(): with pytest.raises(ValueError, match="alpha is array-like but its shape"): cs = cmap([(0, 5, 9), (0, 0, 0)], bytes=True, alpha=(0.5, 0.3)) - cmap.set_bad((1, 1, 1, 1)) + cmap = cmap.with_extremes(bad=(1, 1, 1, 1)) cs = cmap([(0., 1.1, np.nan), (0., 1.2, 1.)]) res = np.array([[1., 1., 1., 1.], [0., 0., 0., 1.], @@ -166,16 +166,40 @@ def test_multivar_cmap_call(): # call outside with tuple assert_allclose(cmap((300, 300), bytes=True, alpha=0.5), [0, 0, 0, 127], atol=0.01) - with pytest.raises(ValueError, match="For the selected colormap the data must have"): cs = cmap((0, 5, 9)) + # test over/under + cmap = mpl.multivar_colormaps['2VarAddA'] + with pytest.raises(ValueError, match='i.e. be of length 2'): + cmap.with_extremes(over=0) + with pytest.raises(ValueError, match='i.e. be of length 2'): + cmap.with_extremes(under=0) + + cmap = cmap.with_extremes(under=[(0, 0, 0, 0)]*2) + assert_allclose((0, 0, 0, 0), cmap((-1., 0)), atol=1e-2) + cmap = cmap.with_extremes(over=[(0, 0, 0, 0)]*2) + assert_allclose((0, 0, 0, 0), cmap((2., 0)), atol=1e-2) + def test_multivar_bad_mode(): cmap = mpl.multivar_colormaps['2VarSubA'] with pytest.raises(ValueError, match="Combination_mode must be 'Add' or 'Sub'"): - cmap.combination_mode = 'bad' + cmap = mpl.colors.MultivarColormap('', cmap[:], 'bad') + + +def test_multivar_resample(): + cmap = mpl.multivar_colormaps['3VarAddA'] + cmap_resampled = cmap.resampled((None, 10, 3)) + + assert_allclose(cmap_resampled[1](0.25), (0.093, 0.116, 0.059, 1.0)) + assert_allclose(cmap_resampled((0, 0.25, 0)), (0.093, 0.116, 0.059, 1.0)) + assert_allclose(cmap_resampled((1, 0.25, 1)), (0.417271, 0.264624, 0.274976, 1.), + atol=0.01) + + with pytest.raises(ValueError, match="lutshape must be of length"): + cmap = cmap.resampled(4) def test_bivar_cmap_call_tuple(): @@ -205,7 +229,7 @@ def test_bivar_cmap_call(): assert_allclose(cs, res, atol=0.01) # call mix floats integers - cmap.set_outside((1, 0, 0, 0)) + cmap = cmap.with_extremes(outside=(1, 0, 0, 0)) cs = cmap([(0.5, 0), (0, 3)]) res = np.array([[0.555, 0, 1, 1], [0, 0.2727, 1, 1]]) @@ -251,9 +275,7 @@ def test_bivar_cmap_call(): # final point is outside colormap and should then receive # the 'outside' (in this case [1,0,0,0]) # also test 'bad' (in this case [1,1,1,0]) - cmap.shape = 'ignore' - cmap.set_outside((1, 0, 0, 0)) - cmap.set_bad((1, 1, 1, 0)) + cmap = cmap.with_extremes(outside=(1, 0, 0, 0), bad=(1, 1, 1, 0), shape='ignore') cs = cmap([(0., 1.1, np.nan), (0., 1.2, 1.)]) res = np.array([[0, 0, 1, 1], [1, 0, 0, 0], @@ -263,9 +285,6 @@ def test_bivar_cmap_call(): assert_allclose(cmap((10, 12), bytes=True, alpha=0.5), [255, 0, 0, 127], atol=0.01) # with integers - # cmap = mpl.colors.BivarColormapFromImage(im) - # cmap.shape = 'ignore' - # cmap.set_outside((1, 0, 0, 0)) cs = cmap([(0, 10), (0, 12)]) res = np.array([[0, 0, 1, 1], [1, 0, 0, 0]]) @@ -275,7 +294,7 @@ def test_bivar_cmap_call(): match="For a `BivarColormap` the data must have"): cs = cmap((0, 5, 9)) - cmap.shape = 'circle' + cmap = cmap.with_extremes(shape='circle') with pytest.raises(NotImplementedError, match="only implemented for use with with floats"): cs = cmap([(0, 5, 9, 0, 0, 9), (0, 0, 0, 5, 11, 11)]) @@ -290,7 +309,7 @@ def test_bivar_getitem(): assert_array_equal(cmaps(xA), cmaps[0](xA[0])) assert_array_equal(cmaps(xB), cmaps[1](xB[1])) - cmaps.shape = 'ignore' + cmaps = cmaps.with_extremes(shape='ignore') assert_array_equal(cmaps(xA), cmaps[0](xA[0])) assert_array_equal(cmaps(xB), cmaps[1](xB[1])) @@ -300,7 +319,7 @@ def test_bivar_getitem(): assert_array_equal(cmaps(xA), cmaps[0](xA[0])) assert_array_equal(cmaps(xB), cmaps[1](xB[1])) - cmaps.shape = 'ignore' + cmaps = cmaps.with_extremes(shape='ignore') assert_array_equal(cmaps(xA), cmaps[0](xA[0])) assert_array_equal(cmaps(xB), cmaps[1](xB[1])) @@ -312,7 +331,12 @@ def test_bivar_cmap_bad_shape(): cmap = mpl.bivar_colormaps['BiCone'] with pytest.raises(ValueError, match="shape must be a valid string"): - cmap.shape = 'bad shape' + cmap.with_extremes(shape='bad_shape') + + with pytest.raises(ValueError, + match="shape must be a valid string"): + mpl.colors.BivarColormapFromImage(np.ones((3, 3, 4)), + shape='bad_shape') def test_bivar_cmap_bad_lut(): @@ -334,11 +358,11 @@ def test_bivar_cmap_from_image(): data_1 = np.arange(6).reshape((3, 2)).T/5 # bivariate colormap from array - im = np.ones((10, 12, 4)) - im[:, :, 0] = np.arange(10)[:, np.newaxis]/10 - im[:, :, 1] = np.arange(12)[np.newaxis, :]/12 + cim = np.ones((10, 12, 3)) + cim[:, :, 0] = np.arange(10)[:, np.newaxis]/10 + cim[:, :, 1] = np.arange(12)[np.newaxis, :]/12 - cmap = mpl.colors.BivarColormapFromImage(im, 'custom') + cmap = mpl.colors.BivarColormapFromImage(cim, 'custom') im = cmap((data_0, data_1)) res = np.array([[[0, 0, 1, 1], [0.2, 0.33333333, 1, 1], @@ -348,14 +372,28 @@ def test_bivar_cmap_from_image(): [0.9, 0.91666667, 1, 1]]]) assert_allclose(im, res, atol=0.01) - # bivariate colormap from array - png_path = Path(__file__).parent / "baseline_images/pngsuite/basn2c16.png" - im = Image.open(png_path) - im = np.asarray(im.convert('RGBA')) + # input as unit8 + cim = np.ones((10, 12, 3))*255 + cim[:, :, 0] = np.arange(10)[:, np.newaxis]/10*255 + cim[:, :, 1] = np.arange(12)[np.newaxis, :]/12*255 - cmap = mpl.colors.BivarColormapFromImage(im, 'custom') + cmap = mpl.colors.BivarColormapFromImage(cim.astype(np.uint8), 'custom') im = cmap((data_0, data_1)) + res = np.array([[[0, 0, 1, 1], + [0.2, 0.33333333, 1, 1], + [0.4, 0.75, 1, 1]], + [[0.6, 0.16666667, 1, 1], + [0.8, 0.58333333, 1, 1], + [0.9, 0.91666667, 1, 1]]]) + assert_allclose(im, res, atol=0.01) + + # bivariate colormap from array + png_path = Path(__file__).parent / "baseline_images/pngsuite/basn2c16.png" + cim = Image.open(png_path) + cim = np.asarray(cim.convert('RGBA')) + cmap = mpl.colors.BivarColormapFromImage(cim, 'custom') + im = cmap((data_0, data_1), bytes=True) res = np.array([[[255, 255, 0, 255], [156, 206, 0, 255], [49, 156, 49, 255]], @@ -365,6 +403,28 @@ def test_bivar_cmap_from_image(): assert_allclose(im, res, atol=0.01) +def test_bivar_resample(): + cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((2, 2)) + assert_allclose(cmap((0.25, 0.25)), (0, 0, 0, 1), atol=1e-2) + + cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((-2, 2)) + assert_allclose(cmap((0.25, 0.25)), (1., 0.5, 0., 1.), atol=1e-2) + + cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((2, -2)) + assert_allclose(cmap((0.25, 0.25)), (0., 0.5, 1., 1.), atol=1e-2) + + cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((-2, -2)) + assert_allclose(cmap((0.25, 0.25)), (1, 1, 1, 1), atol=1e-2) + + cmap = mpl.bivar_colormaps['BiOrangeBlue'].reversed() + assert_allclose(cmap((0.25, 0.25)), (0.748535, 0.748547, 0.748535, 1.), atol=1e-2) + cmap = mpl.bivar_colormaps['BiOrangeBlue'].transposed() + assert_allclose(cmap((0.25, 0.25)), (0.252441, 0.252422, 0.252441, 1.), atol=1e-2) + + with pytest.raises(ValueError, match="lutshape must be of length"): + cmap = cmap.resampled(4) + + def test_bivariate_repr_png(): cmap = mpl.bivar_colormaps['BiCone'] png = cmap._repr_png_() @@ -393,7 +453,7 @@ def test_multivariate_repr_html(): cmap = mpl.multivar_colormaps['3VarAddA'] html = cmap._repr_html_() assert len(html) > 0 - for c in cmap.colormaps: + for c in cmap: png = c._repr_png_() assert base64.b64encode(png).decode('ascii') in html assert cmap.name in html @@ -417,11 +477,11 @@ def test_bivar_eq(): assert (cmap_0 == cmap_1) is False cmap_1 = mpl.bivar_colormaps['BiPeak'] - cmap_1.set_bad('k') + cmap_1 = cmap_1.with_extremes(bad='k') assert (cmap_0 == cmap_1) is False cmap_1 = mpl.bivar_colormaps['BiPeak'] - cmap_1.set_outside('k') + cmap_1 = cmap_1.with_extremes(outside='k') assert (cmap_0 == cmap_1) is False cmap_1 = mpl.bivar_colormaps['BiPeak'] @@ -430,7 +490,7 @@ def test_bivar_eq(): assert (cmap_0 == cmap_1) is False cmap_1 = mpl.bivar_colormaps['BiPeak'] - cmap_1.shape = 'ignore' + cmap_1 = cmap_1.with_extremes(shape='ignore') assert (cmap_0 == cmap_1) is False @@ -455,9 +515,9 @@ def test_multivar_eq(): assert (cmap_0 == cmap_1) is False cmap_1 = mpl.multivar_colormaps['2VarAddA'] - cmap_1.set_bad('k') + cmap_1 = cmap_1.with_extremes(bad='k') assert (cmap_0 == cmap_1) is False cmap_1 = mpl.multivar_colormaps['2VarAddA'] - cmap_1.combination_mode = 'Sub' + cmap_1 = mpl.colors.MultivarColormap('', cmap_1[:], 'Sub') assert (cmap_0 == cmap_1) is False From 2911933ff07b942251f27fdb0faed984bb7747ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Sat, 6 Jul 2024 12:38:17 +0200 Subject: [PATCH 07/18] Corrected stubs for mutlivariate and bivariate colormaps --- lib/matplotlib/__init__.pyi | 2 ++ lib/matplotlib/cm.pyi | 2 ++ lib/matplotlib/colors.pyi | 26 +++++++++++++++----------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/__init__.pyi b/lib/matplotlib/__init__.pyi index 05dc927dc6c9..5b6797d3a7da 100644 --- a/lib/matplotlib/__init__.pyi +++ b/lib/matplotlib/__init__.pyi @@ -116,4 +116,6 @@ def _preprocess_data( ) -> Callable: ... from matplotlib.cm import _colormaps as colormaps # noqa: E402 +from matplotlib.cm import _multivar_colormaps as multivar_colormaps # noqa: E402 +from matplotlib.cm import _bivar_colormaps as bivar_colormaps # noqa: E402 from matplotlib.colors import _color_sequences as color_sequences # noqa: E402 diff --git a/lib/matplotlib/cm.pyi b/lib/matplotlib/cm.pyi index be8f10b39cb6..40e841d829ab 100644 --- a/lib/matplotlib/cm.pyi +++ b/lib/matplotlib/cm.pyi @@ -18,6 +18,8 @@ class ColormapRegistry(Mapping[str, colors.Colormap]): def get_cmap(self, cmap: str | colors.Colormap) -> colors.Colormap: ... _colormaps: ColormapRegistry = ... +_multivar_colormaps: ColormapRegistry = ... +_bivar_colormaps: ColormapRegistry = ... def get_cmap(name: str | colors.Colormap | None = ..., lut: int | None = ...) -> colors.Colormap: ... diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index fb04815bd860..4f821fe12f2f 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -141,20 +141,19 @@ class ListedColormap(Colormap): class MultivarColormap: name: str colormaps: list[Colormap] - combination_mode: str n_variates: int def __init__(self, name: str, colormaps: list[Colormap], combination_mode: str) -> None: ... @overload def __call__( - self, X: Sequence[Sequence[float]] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ... + self, X: Sequence[Sequence[float]] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ..., clip: bool = ... ) -> np.ndarray: ... @overload def __call__( - self, X: Sequence[float], alpha: float | None = ..., bytes: bool = ... + self, X: Sequence[float], alpha: float | None = ..., bytes: bool = ..., clip: bool = ... ) -> tuple[float, float, float, float]: ... @overload def __call__( - self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ... + self, X: ArrayLike, alpha: ArrayLike | None = ..., bytes: bool = ..., clip: bool = ... ) -> tuple[float, float, float, float] | np.ndarray: ... def copy(self) -> MultivarColormap: ... def __copy__(self) -> MultivarColormap: ... @@ -162,7 +161,7 @@ class MultivarColormap: def __iter__(self) -> Colormap: ... def __len__(self) -> int: ... def get_bad(self) -> np.ndarray: ... - def resampled(self, lutsize: Sequence[int | None]) -> MultivarColormap: ... + def resampled(self, lutshape: Sequence[int | None]) -> MultivarColormap: ... def with_extremes( self, *, @@ -170,6 +169,8 @@ class MultivarColormap: under: Sequence[ColorType] | None = ..., over: Sequence[ColorType] | None = ... ) -> MultivarColormap: ... + @property + def combination_mode(self) -> str: ... def _repr_html_(self) -> str: ... def _repr_png_(self) -> bytes: ... @@ -177,10 +178,8 @@ class BivarColormap: name: str N: int M: int - shape: str n_variates: int - origin: tuple[float, float] - def __init__(self, name: str, N: int = ..., M: int | None = ..., shape: str = ..., origin: tuple[float, float] = ... + def __init__(self, name: str, N: int = ..., M: int | None = ..., shape: str = ..., origin: Sequence[float] = ... ) -> None: ... @overload def __call__( @@ -196,13 +195,17 @@ class BivarColormap: ) -> tuple[float, float, float, float] | np.ndarray: ... @property def lut(self) -> np.ndarray: ... + @property + def shape(self) -> str: ... + @property + def origin(self) -> tuple[float, float]: ... def __copy__(self) -> BivarColormap: ... def __getitem__(self, item: int) -> Colormap: ... def __eq__(self, other) -> bool: ... def get_bad(self) -> np.ndarray: ... def get_outside(self) -> np.ndarray: ... def copy(self) -> BivarColormap: ... - def resampled(self, lutsize: Sequence[int | None]) -> BivarColormap: ... + def resampled(self, lutshape: Sequence[int | None], transposed: bool = ...) -> BivarColormap: ... def transposed(self) -> BivarColormap: ... def reversed(self, axis_0: bool = True, axis_1: bool = True) -> BivarColormap: ... def with_extremes( @@ -211,17 +214,18 @@ class BivarColormap: bad: ColorType | None = ..., outside: ColorType | None = ..., shape: str | None = ..., + origin: None | Sequence[float] = ..., ) -> MultivarColormap: ... def _repr_html_(self) -> str: ... def _repr_png_(self) -> bytes: ... class SegmentedBivarColormap(BivarColormap): def __init__( - self, patch: np.ndarray, name: str, N: int = ..., shape: str = ..., origin: tuple[float, float] = ... + self, patch: np.ndarray, name: str, N: int = ..., shape: str = ..., origin: Sequence[float] = ... ) -> None: ... class BivarColormapFromImage(BivarColormap): - def __init__(self, lut: np.ndarray, name: str = ..., shape: str = ..., origin: tuple[float, float] = ... + def __init__(self, lut: np.ndarray, name: str = ..., shape: str = ..., origin: Sequence[float] = ... ) -> None: ... class Normalize: From e7d884c77055965d496263cb142f0be890024a77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Tue, 9 Jul 2024 15:26:49 +0200 Subject: [PATCH 08/18] minor fixes to colors.py --- lib/matplotlib/colors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 2f3cb04084b5..d13c7b51a077 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1284,7 +1284,7 @@ def __init__(self, name, colormaps, combination_mode): raise ValueError("A MultivarColormap must have more than one colormap.") colormaps = list(colormaps) # ensure cmaps is a list, i.e. not a tuple for i, cmap in enumerate(colormaps): - if not issubclass(type(cmap), Colormap): + if not isinstance(cmap, Colormap): if isinstance(cmap, str): colormaps[i] = mpl.colormaps[cmap] else: @@ -1926,7 +1926,7 @@ def _clip(self, X): if not X_part.dtype.kind == "f": raise NotImplementedError( "Circular bivariate colormaps are only" - " implemented for use with with floats, not integers") + " implemented for use with with floats") radii_sqr = (X[0] - 0.5)**2 + (X[1] - 0.5)**2 mask_outside = radii_sqr > 0.25 if self.shape == 'circle': From d365483e1be849ada7c34cbbc0465d3a4e0be44f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Thu, 18 Jul 2024 14:21:39 +0200 Subject: [PATCH 09/18] Rename 'Add' to 'sRGB_add' This renames the 'combination_mode' keywords from 'Add' and 'Sub' to 'sRGB_add' and 'sRGB_sub'. This change is intended to provide increased clarity if/when additional keywords are added. --- lib/matplotlib/_cm_multivar.py | 6 +++--- lib/matplotlib/colors.py | 12 ++++++------ .../tests/test_multivariate_colormaps.py | 16 ++++++++-------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/_cm_multivar.py b/lib/matplotlib/_cm_multivar.py index 7d46eb0ef72d..7ae6c9a9edf3 100644 --- a/lib/matplotlib/_cm_multivar.py +++ b/lib/matplotlib/_cm_multivar.py @@ -158,9 +158,9 @@ cmap_families = { '2VarAddA': MultivarColormap('2VarAddA', [cmaps['2VarAddA' + str(i)] for - i in range(2)], 'Add'), + i in range(2)], 'sRGB_add'), '2VarSubA': MultivarColormap('2VarSubA', [cmaps['2VarSubA' + str(i)] for - i in range(2)], 'Sub'), + i in range(2)], 'sRGB_sub'), '3VarAddA': MultivarColormap('3VarAddA', [cmaps['3VarAddA' + str(i)] for - i in range(3)], 'Add'), + i in range(3)], 'sRGB_add'), } diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index d13c7b51a077..9128e23906f2 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1268,12 +1268,12 @@ def __init__(self, name, colormaps, combination_mode): The name of the colormap family. colormaps: list or tuple of `~matplotlib.colors.Colormap` objects The individual colormaps that are combined - combination_mode: str, 'Add' or 'Sub' + combination_mode: str, 'sRGB_add' or 'sRGB_sub' Describe how colormaps are combined in sRGB space - - If 'Add' -> Mixing produces brighter colors + - If 'sRGB_add' -> Mixing produces brighter colors `sRGB = cmap[0][X[0]] + cmap[1][x[1]] + ... + cmap[n-1][x[n-1]]` - - If 'Sub' -> Mixing produces darker colors + - If 'sRGB_sub' -> Mixing produces darker colors `sRGB = cmap[0][X[0]] + cmap[1][x[1]] + ... + cmap[n-1][x[n-1]] - n + 1` """ self.name = name @@ -1292,8 +1292,8 @@ def __init__(self, name, colormaps, combination_mode): " Colormap or valid strings.") self._colormaps = colormaps - if combination_mode not in ['Add', 'Sub']: - raise ValueError("Combination_mode must be 'Add' or 'Sub'," + if combination_mode not in ['sRGB_add', 'sRGB_sub']: + raise ValueError("Combination_mode must be 'sRGB_add' or 'sRGB_sub'," f" {combination_mode!r} is not allowed.") self._combination_mode = combination_mode self.n_variates = len(colormaps) @@ -1341,7 +1341,7 @@ def __call__(self, X, alpha=None, bytes=False, clip=True): rgba[..., 3] *= sub_rgba[..., 3] # multiply alpha mask_bad |= sub_mask_bad - if self.combination_mode == 'Sub': + if self.combination_mode == 'sRGB_sub': rgba[..., :3] -= len(self) - 1 rgba[mask_bad] = self.get_bad() diff --git a/lib/matplotlib/tests/test_multivariate_colormaps.py b/lib/matplotlib/tests/test_multivariate_colormaps.py index 986f6a8f5f05..1cdae9d8e812 100644 --- a/lib/matplotlib/tests/test_multivariate_colormaps.py +++ b/lib/matplotlib/tests/test_multivariate_colormaps.py @@ -41,7 +41,7 @@ def test_bivariate_cmap_shapes(): def test_multivar_creation(): # test creation of a custom multivariate colorbar blues = mpl.colormaps['Blues'] - cmap = mpl.colors.MultivarColormap('custom', (blues, 'Oranges'), 'Sub') + cmap = mpl.colors.MultivarColormap('custom', (blues, 'Oranges'), 'sRGB_sub') y, x = np.mgrid[0:3, 0:3]/2 im = cmap((y, x)) res = np.array([[[0.96862745, 0.94509804, 0.92156863, 1], @@ -56,11 +56,11 @@ def test_multivar_creation(): assert_allclose(im, res, atol=0.01) with pytest.raises(ValueError, match="colormaps must be a list of"): - cmap = mpl.colors.MultivarColormap('custom', (blues, [blues]), 'Sub') + cmap = mpl.colors.MultivarColormap('custom', (blues, [blues]), 'sRGB_sub') with pytest.raises(ValueError, match="A MultivarColormap must"): - cmap = mpl.colors.MultivarColormap('custom', 'blues', 'Sub') + cmap = mpl.colors.MultivarColormap('custom', 'blues', 'sRGB_sub') with pytest.raises(ValueError, match="A MultivarColormap must"): - cmap = mpl.colors.MultivarColormap('custom', (blues), 'Sub') + cmap = mpl.colors.MultivarColormap('custom', (blues), 'sRGB_sub') @image_comparison(["multivar_alpha_mixing.png"]) @@ -72,7 +72,7 @@ def test_multivar_alpha_mixing(): alpha[:, 3] = np.linspace(1, 0, 256) alpha_cmap = mpl.colors.LinearSegmentedColormap.from_list('from_list', alpha) - cmap = mpl.colors.MultivarColormap('custom', (rainbow, alpha_cmap), 'Add') + cmap = mpl.colors.MultivarColormap('custom', (rainbow, alpha_cmap), 'sRGB_add') y, x = np.mgrid[0:10, 0:10]/9 im = cmap((y, x)) @@ -185,7 +185,7 @@ def test_multivar_cmap_call(): def test_multivar_bad_mode(): cmap = mpl.multivar_colormaps['2VarSubA'] - with pytest.raises(ValueError, match="Combination_mode must be 'Add' or 'Sub'"): + with pytest.raises(ValueError, match="Combination_mode must be 'sRGB_add' or"): cmap = mpl.colors.MultivarColormap('', cmap[:], 'bad') @@ -508,7 +508,7 @@ def test_multivar_eq(): cmap_1 = mpl.colors.MultivarColormap('2VarAddA', [cmap_0[0]]*2, - 'Add') + 'sRGB_add') assert (cmap_0 == cmap_1) is False cmap_1 = mpl.multivar_colormaps['3VarAddA'] @@ -519,5 +519,5 @@ def test_multivar_eq(): assert (cmap_0 == cmap_1) is False cmap_1 = mpl.multivar_colormaps['2VarAddA'] - cmap_1 = mpl.colors.MultivarColormap('', cmap_1[:], 'Sub') + cmap_1 = mpl.colors.MultivarColormap('', cmap_1[:], 'sRGB_sub') assert (cmap_0 == cmap_1) is False From 77da937020ddebe708cef3d5ad6edceda564659a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Thu, 25 Jul 2024 14:59:08 +0200 Subject: [PATCH 10/18] name as keyword variable and additional tests to multivariate colormaps --- lib/matplotlib/_cm_bivar.py | 6 +- lib/matplotlib/_cm_multivar.py | 12 +-- lib/matplotlib/colors.py | 75 +++++++++---------- lib/matplotlib/colors.pyi | 8 +- .../tests/test_multivariate_colormaps.py | 52 ++++++++++--- 5 files changed, 90 insertions(+), 63 deletions(-) diff --git a/lib/matplotlib/_cm_bivar.py b/lib/matplotlib/_cm_bivar.py index cf3c91dc6632..bbe450a24775 100644 --- a/lib/matplotlib/_cm_bivar.py +++ b/lib/matplotlib/_cm_bivar.py @@ -1305,8 +1305,8 @@ cmaps = { "BiPeak": SegmentedBivarColormap( - BiPeak, "BiPeak", 256, "square", (.5, .5)), + BiPeak, 256, "square", (.5, .5), name="BiPeak"), "BiOrangeBlue": SegmentedBivarColormap( - BiOrangeBlue, "BiOrangeBlue", 256, "square", (0, 0)), - "BiCone": SegmentedBivarColormap(BiPeak, "BiCone", 256, "circle", (.5, .5)), + BiOrangeBlue, 256, "square", (0, 0), name="BiOrangeBlue"), + "BiCone": SegmentedBivarColormap(BiPeak, 256, "circle", (.5, .5), name="BiCone"), } diff --git a/lib/matplotlib/_cm_multivar.py b/lib/matplotlib/_cm_multivar.py index 7ae6c9a9edf3..e010c9caf877 100644 --- a/lib/matplotlib/_cm_multivar.py +++ b/lib/matplotlib/_cm_multivar.py @@ -157,10 +157,10 @@ ]} cmap_families = { - '2VarAddA': MultivarColormap('2VarAddA', [cmaps['2VarAddA' + str(i)] for - i in range(2)], 'sRGB_add'), - '2VarSubA': MultivarColormap('2VarSubA', [cmaps['2VarSubA' + str(i)] for - i in range(2)], 'sRGB_sub'), - '3VarAddA': MultivarColormap('3VarAddA', [cmaps['3VarAddA' + str(i)] for - i in range(3)], 'sRGB_add'), + '2VarAddA': MultivarColormap([cmaps['2VarAddA' + str(i)] for i in range(2)], + 'sRGB_add', name='2VarAddA'), + '2VarSubA': MultivarColormap([cmaps['2VarSubA' + str(i)] for i in range(2)], + 'sRGB_sub', name='2VarSubA'), + '3VarAddA': MultivarColormap([cmaps['3VarAddA' + str(i)] for i in range(3)], + 'sRGB_add', name='3VarAddA'), } diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 9128e23906f2..f2ee8ff836a0 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1260,12 +1260,10 @@ class MultivarColormap: Class for holding multiple `~matplotlib.colors.Colormap` for use in a `~matplotlib.cm.VectorMappable` object """ - def __init__(self, name, colormaps, combination_mode): + def __init__(self, colormaps, combination_mode, name='multivariate colormap'): """ Parameters ---------- - name : str - The name of the colormap family. colormaps: list or tuple of `~matplotlib.colors.Colormap` objects The individual colormaps that are combined combination_mode: str, 'sRGB_add' or 'sRGB_sub' @@ -1275,6 +1273,8 @@ def __init__(self, name, colormaps, combination_mode): `sRGB = cmap[0][X[0]] + cmap[1][x[1]] + ... + cmap[n-1][x[n-1]]` - If 'sRGB_sub' -> Mixing produces darker colors `sRGB = cmap[0][X[0]] + cmap[1][x[1]] + ... + cmap[n-1][x[n-1]] - n + 1` + name : str, optional + The name of the colormap family. """ self.name = name @@ -1284,12 +1284,11 @@ def __init__(self, name, colormaps, combination_mode): raise ValueError("A MultivarColormap must have more than one colormap.") colormaps = list(colormaps) # ensure cmaps is a list, i.e. not a tuple for i, cmap in enumerate(colormaps): - if not isinstance(cmap, Colormap): - if isinstance(cmap, str): - colormaps[i] = mpl.colormaps[cmap] - else: - raise ValueError("colormaps must be a list of objects that subclass" - " Colormap or valid strings.") + if isinstance(cmap, str): + colormaps[i] = mpl.colormaps[cmap] + elif not isinstance(cmap, Colormap): + raise ValueError("colormaps must be a list of objects that subclass" + " Colormap or valid strings.") self._colormaps = colormaps if combination_mode not in ['sRGB_add', 'sRGB_sub']: @@ -1314,7 +1313,7 @@ def __call__(self, X, alpha=None, bytes=False, clip=True): self[i] is colormap i. alpha : float or array-like or None Alpha must be a scalar between 0 and 1, a sequence of such - floats with shape matching Xi, or None. + floats with shape matching *Xi*, or None. bytes : bool If False (default), the returned RGBA values will be floats in the interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the @@ -1473,15 +1472,13 @@ def with_extremes(self, *, bad=None, under=None, over=None): raise ValueError("*under* must contain a color for each scalar colormap" f" i.e. be of length {len(new_cm)}.") else: - for c, b in zip(new_cm, under): - c.set_under(b) + [c.set_under(b) for c, b in zip(new_cm, under)] if over is not None: if not np.iterable(over) or not len(over) == len(new_cm): raise ValueError("*over* must contain a color for each scalar colormap" f" i.e. be of length {len(new_cm)}.") else: - for c, b in zip(new_cm, over): - c.set_over(b) + [c.set_over(b) for c, b in zip(new_cm, over)] return new_cm @property @@ -1520,12 +1517,11 @@ class BivarColormap: lookup table. To be used with `~matplotlib.cm.VectorMappable`. """ - def __init__(self, name, N=256, M=256, shape='square', origin=(0, 0)): + def __init__(self, N=256, M=256, shape='square', origin=(0, 0), + name='bivariate colormap'): """ Parameters ---------- - name : str - The name of the colormap. N : int The number of RGB quantization levels along the first axis. M : int @@ -1533,19 +1529,21 @@ def __init__(self, name, N=256, M=256, shape='square', origin=(0, 0)): If None, M = N shape: str 'square' or 'circle' or 'ignore' or 'circleignore' - - If 'square' each variate is clipped to [0,1] independently - - If 'circle' the variates are clipped radially to the center + - 'square' each variate is clipped to [0,1] independently + - 'circle' the variates are clipped radially to the center of the colormap, and a circular mask is applied when the colormap is displayed - - If 'ignore' the variates are not clipped, but instead assigned the + - 'ignore' the variates are not clipped, but instead assigned the 'outside' color - - If 'circleignore' a circular mask is applied, but the data is not + - 'circleignore' a circular mask is applied, but the data is not clipped and instead assigned the 'outside' color origin: (float, float) The relative origin of the colormap. Typically (0, 0), for colormaps that are linear on both axis, and (.5, .5) for circular colormaps. Used when getting 1D colormaps from 2D colormaps. + name : str, optional + The name of the colormap. """ self.name = name @@ -1865,7 +1863,7 @@ def with_extremes(self, *, bad=None, outside=None, shape=None, origin=None): raise ValueError("The shape must be a valid string, " "'square', 'circle', 'ignore', or 'circleignore'") if origin is not None: - self._origin = (float(origin[0]), float(origin[1])) + new_cm._origin = (float(origin[0]), float(origin[1])) return new_cm @@ -1942,17 +1940,17 @@ def __getitem__(self, item): if not self._isinit: self._init() if item == 0: - o = int(self._origin[1]*self.M) - if o > self.M-1: - o = self.M-1 - one_d_lut = self._lut[:, o] + origin_1_as_int = int(self._origin[1]*self.M) + if origin_1_as_int > self.M-1: + origin_1_as_int = self.M-1 + one_d_lut = self._lut[:, origin_1_as_int] new_cmap = ListedColormap(one_d_lut, name=self.name+'_0', N=self.N) elif item == 1: - o = int(self._origin[0]*self.N) - if o > self.N-1: - o = self.N-1 - one_d_lut = self._lut[o, :] + origin_0_as_int = int(self._origin[0]*self.N) + if origin_0_as_int > self.N-1: + origin_0_as_int = self.N-1 + one_d_lut = self._lut[origin_0_as_int, :] new_cmap = ListedColormap(one_d_lut, name=self.name+'_1', N=self.M) else: raise KeyError(f"only 0 or 1 are" @@ -2033,8 +2031,6 @@ class SegmentedBivarColormap(BivarColormap): ---------- patch : nparray of shape (k, k, 3) This patch gets supersamples to a lut of shape (N, M, 4) - name : str - The name of the colormap. N : int The number of RGB quantization levels along each axis. shape: str 'square' or 'circle' or 'ignore' or 'circleignore' @@ -2052,11 +2048,14 @@ class SegmentedBivarColormap(BivarColormap): that are linear on both axis, and (.5, .5) for circular colormaps. Used when getting 1D colormaps from 2D colormaps. + name : str, optional + The name of the colormap. """ - def __init__(self, patch, name, N=256, shape='square', origin=(0, 0)): + def __init__(self, patch, N=256, shape='square', origin=(0, 0), + name='segmented bivariate colormap'): self.patch = patch - super().__init__(name, N, N, shape, origin) + super().__init__(N, N, shape, origin, name=name) def _init(self): s = self.patch.shape @@ -2080,8 +2079,6 @@ class BivarColormapFromImage(BivarColormap): ---------- lut : nparray of shape (N, M, 3) or (N, M, 4) The look-up-table - name : str - The name of the colormap. shape: str 'square' or 'circle' or 'ignore' or 'circleignore' - If 'square' each variate is clipped to [0,1] independently @@ -2096,10 +2093,12 @@ class BivarColormapFromImage(BivarColormap): The relative origin of the colormap. Typically (0, 0), for colormaps that are linear on both axis, and (.5, .5) for circular colormaps. Used when getting 1D colormaps from 2D colormaps. + name : str, optional + The name of the colormap. """ - def __init__(self, lut, name='', shape='square', origin=(0, 0)): + def __init__(self, lut, shape='square', origin=(0, 0), name='from image'): # We can allow for a PIL.Image as unput in the following way, but importing # matplotlib.image.pil_to_array() results in a circular import # For now, this function only accepts numpy arrays. @@ -2119,7 +2118,7 @@ def __init__(self, lut, name='', shape='square', origin=(0, 0)): new_lut[:, :, 3] = 1. lut = new_lut self._lut = lut - super().__init__(name, lut.shape[0], lut.shape[1], shape, origin) + super().__init__(lut.shape[0], lut.shape[1], shape, origin, name=name) def _init(self): self._isinit = True diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index 4f821fe12f2f..824081d20a8d 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -142,7 +142,7 @@ class MultivarColormap: name: str colormaps: list[Colormap] n_variates: int - def __init__(self, name: str, colormaps: list[Colormap], combination_mode: str) -> None: ... + def __init__(self, colormaps: list[Colormap], combination_mode: str, name: str = ...) -> None: ... @overload def __call__( self, X: Sequence[Sequence[float]] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ..., clip: bool = ... @@ -179,7 +179,7 @@ class BivarColormap: N: int M: int n_variates: int - def __init__(self, name: str, N: int = ..., M: int | None = ..., shape: str = ..., origin: Sequence[float] = ... + def __init__(self, N: int = ..., M: int | None = ..., shape: str = ..., origin: Sequence[float] = ..., name: str = ... ) -> None: ... @overload def __call__( @@ -221,11 +221,11 @@ class BivarColormap: class SegmentedBivarColormap(BivarColormap): def __init__( - self, patch: np.ndarray, name: str, N: int = ..., shape: str = ..., origin: Sequence[float] = ... + self, patch: np.ndarray, N: int = ..., shape: str = ..., origin: Sequence[float] = ..., name: str = ... ) -> None: ... class BivarColormapFromImage(BivarColormap): - def __init__(self, lut: np.ndarray, name: str = ..., shape: str = ..., origin: Sequence[float] = ... + def __init__(self, lut: np.ndarray, shape: str = ..., origin: Sequence[float] = ..., name: str = ... ) -> None: ... class Normalize: diff --git a/lib/matplotlib/tests/test_multivariate_colormaps.py b/lib/matplotlib/tests/test_multivariate_colormaps.py index 1cdae9d8e812..fdfcf0c1d537 100644 --- a/lib/matplotlib/tests/test_multivariate_colormaps.py +++ b/lib/matplotlib/tests/test_multivariate_colormaps.py @@ -41,7 +41,7 @@ def test_bivariate_cmap_shapes(): def test_multivar_creation(): # test creation of a custom multivariate colorbar blues = mpl.colormaps['Blues'] - cmap = mpl.colors.MultivarColormap('custom', (blues, 'Oranges'), 'sRGB_sub') + cmap = mpl.colors.MultivarColormap((blues, 'Oranges'), 'sRGB_sub') y, x = np.mgrid[0:3, 0:3]/2 im = cmap((y, x)) res = np.array([[[0.96862745, 0.94509804, 0.92156863, 1], @@ -56,11 +56,11 @@ def test_multivar_creation(): assert_allclose(im, res, atol=0.01) with pytest.raises(ValueError, match="colormaps must be a list of"): - cmap = mpl.colors.MultivarColormap('custom', (blues, [blues]), 'sRGB_sub') + cmap = mpl.colors.MultivarColormap((blues, [blues]), 'sRGB_sub') with pytest.raises(ValueError, match="A MultivarColormap must"): - cmap = mpl.colors.MultivarColormap('custom', 'blues', 'sRGB_sub') + cmap = mpl.colors.MultivarColormap('blues', 'sRGB_sub') with pytest.raises(ValueError, match="A MultivarColormap must"): - cmap = mpl.colors.MultivarColormap('custom', (blues), 'sRGB_sub') + cmap = mpl.colors.MultivarColormap((blues), 'sRGB_sub') @image_comparison(["multivar_alpha_mixing.png"]) @@ -72,7 +72,7 @@ def test_multivar_alpha_mixing(): alpha[:, 3] = np.linspace(1, 0, 256) alpha_cmap = mpl.colors.LinearSegmentedColormap.from_list('from_list', alpha) - cmap = mpl.colors.MultivarColormap('custom', (rainbow, alpha_cmap), 'sRGB_add') + cmap = mpl.colors.MultivarColormap((rainbow, alpha_cmap), 'sRGB_add') y, x = np.mgrid[0:10, 0:10]/9 im = cmap((y, x)) @@ -107,6 +107,8 @@ def test_multivar_cmap_call(): with pytest.raises(ValueError, match="For the selected colormap the data"): cs = cmap([(0, 5, 9), (0, 0, 0), (0, 0, 0)]) + with pytest.raises(ValueError, match="clip cannot be false"): + cs = cmap([(0, 5, 9), (0, 0, 0)], bytes=True, clip=False) # Tests calling a multivariate colormap with integer values cmap = mpl.multivar_colormaps['2VarSubA'] @@ -120,6 +122,12 @@ def test_multivar_cmap_call(): [0, 0, 0, 1]]) assert_allclose(cs, res, atol=0.01) + # call only integers, wrong byte order + swapped_dt = np.dtype(int).newbyteorder() + cs = cmap([np.array([0, 50, 100, 0, 0, 300], dtype=swapped_dt), + np.array([0, 0, 0, 50, 100, 300], dtype=swapped_dt)]) + assert_allclose(cs, res, atol=0.01) + # call mix floats integers # check calling with bytes = True cs = cmap([(0, 50, 100, 0, 0, 300), (0, 0, 0, 50, 100, 300)], bytes=True) @@ -186,7 +194,7 @@ def test_multivar_cmap_call(): def test_multivar_bad_mode(): cmap = mpl.multivar_colormaps['2VarSubA'] with pytest.raises(ValueError, match="Combination_mode must be 'sRGB_add' or"): - cmap = mpl.colors.MultivarColormap('', cmap[:], 'bad') + cmap = mpl.colors.MultivarColormap(cmap[:], 'bad') def test_multivar_resample(): @@ -227,6 +235,11 @@ def test_bivar_cmap_call(): [0, 1, 1, 1], [1, 1, 1, 1]]) assert_allclose(cs, res, atol=0.01) + # call only integers, wrong byte order + swapped_dt = np.dtype(int).newbyteorder() + cs = cmap([np.array([0, 5, 9, 0, 0, 10], dtype=swapped_dt), + np.array([0, 0, 0, 5, 11, 12], dtype=swapped_dt)]) + assert_allclose(cs, res, atol=0.01) # call mix floats integers cmap = cmap.with_extremes(outside=(1, 0, 0, 0)) @@ -299,6 +312,21 @@ def test_bivar_cmap_call(): match="only implemented for use with with floats"): cs = cmap([(0, 5, 9, 0, 0, 9), (0, 0, 0, 5, 11, 11)]) + # test origin + cmap = mpl.bivar_colormaps['BiOrangeBlue'].with_extremes(origin=(0.5, 0.5)) + assert_allclose(cmap[0](0.5), + (0.50244140625, 0.5024222412109375, 0.50244140625, 1)) + assert_allclose(cmap[1](0.5), + (0.50244140625, 0.5024222412109375, 0.50244140625, 1)) + cmap = mpl.bivar_colormaps['BiOrangeBlue'].with_extremes(origin=(1, 1)) + assert_allclose(cmap[0](1.), + (0.99853515625, 0.9985467529296875, 0.99853515625, 1.0)) + assert_allclose(cmap[1](1.), + (0.99853515625, 0.9985467529296875, 0.99853515625, 1.0)) + with pytest.raises(KeyError, + match="only 0 or 1 are valid keys"): + cs = cmap[2] + def test_bivar_getitem(): """Test __getitem__ on BivarColormap""" @@ -329,6 +357,7 @@ def test_bivar_cmap_bad_shape(): Tests calling a bivariate colormap with integer values """ cmap = mpl.bivar_colormaps['BiCone'] + _ = cmap.lut with pytest.raises(ValueError, match="shape must be a valid string"): cmap.with_extremes(shape='bad_shape') @@ -362,7 +391,7 @@ def test_bivar_cmap_from_image(): cim[:, :, 0] = np.arange(10)[:, np.newaxis]/10 cim[:, :, 1] = np.arange(12)[np.newaxis, :]/12 - cmap = mpl.colors.BivarColormapFromImage(cim, 'custom') + cmap = mpl.colors.BivarColormapFromImage(cim) im = cmap((data_0, data_1)) res = np.array([[[0, 0, 1, 1], [0.2, 0.33333333, 1, 1], @@ -377,7 +406,7 @@ def test_bivar_cmap_from_image(): cim[:, :, 0] = np.arange(10)[:, np.newaxis]/10*255 cim[:, :, 1] = np.arange(12)[np.newaxis, :]/12*255 - cmap = mpl.colors.BivarColormapFromImage(cim.astype(np.uint8), 'custom') + cmap = mpl.colors.BivarColormapFromImage(cim.astype(np.uint8)) im = cmap((data_0, data_1)) res = np.array([[[0, 0, 1, 1], [0.2, 0.33333333, 1, 1], @@ -392,7 +421,7 @@ def test_bivar_cmap_from_image(): cim = Image.open(png_path) cim = np.asarray(cim.convert('RGBA')) - cmap = mpl.colors.BivarColormapFromImage(cim, 'custom') + cmap = mpl.colors.BivarColormapFromImage(cim) im = cmap((data_0, data_1), bytes=True) res = np.array([[[255, 255, 0, 255], [156, 206, 0, 255], @@ -506,8 +535,7 @@ def test_multivar_eq(): cmap_1 = mpl.bivar_colormaps['BiPeak'] assert (cmap_0 == cmap_1) is False - cmap_1 = mpl.colors.MultivarColormap('2VarAddA', - [cmap_0[0]]*2, + cmap_1 = mpl.colors.MultivarColormap([cmap_0[0]]*2, 'sRGB_add') assert (cmap_0 == cmap_1) is False @@ -519,5 +547,5 @@ def test_multivar_eq(): assert (cmap_0 == cmap_1) is False cmap_1 = mpl.multivar_colormaps['2VarAddA'] - cmap_1 = mpl.colors.MultivarColormap('', cmap_1[:], 'sRGB_sub') + cmap_1 = mpl.colors.MultivarColormap(cmap_1[:], 'sRGB_sub') assert (cmap_0 == cmap_1) is False From e688f03f4fd36bae43add241eed419ca9bcd945e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Mon, 5 Aug 2024 11:49:26 +0200 Subject: [PATCH 11/18] Apply suggestions from code review Co-authored-by: Elliott Sales de Andrade --- lib/matplotlib/_cm_bivar.py | 2 +- lib/matplotlib/_cm_multivar.py | 8 +- lib/matplotlib/colors.py | 90 +++++++++---------- lib/matplotlib/colors.pyi | 6 +- .../tests/test_multivariate_colormaps.py | 2 +- 5 files changed, 53 insertions(+), 55 deletions(-) diff --git a/lib/matplotlib/_cm_bivar.py b/lib/matplotlib/_cm_bivar.py index bbe450a24775..53c0d48d7d6c 100644 --- a/lib/matplotlib/_cm_bivar.py +++ b/lib/matplotlib/_cm_bivar.py @@ -1,4 +1,4 @@ -# auto-genreated by https://github.com/trygvrad/multivariate_colormaps +# auto-generated by https://github.com/trygvrad/multivariate_colormaps # date: 2024-05-24 import numpy as np diff --git a/lib/matplotlib/_cm_multivar.py b/lib/matplotlib/_cm_multivar.py index e010c9caf877..610d7c40935b 100644 --- a/lib/matplotlib/_cm_multivar.py +++ b/lib/matplotlib/_cm_multivar.py @@ -1,4 +1,4 @@ -# auto-genreated by https://github.com/trygvrad/multivariate_colormaps +# auto-generated by https://github.com/trygvrad/multivariate_colormaps # date: 2024-05-28 from .colors import LinearSegmentedColormap, MultivarColormap @@ -157,10 +157,10 @@ ]} cmap_families = { - '2VarAddA': MultivarColormap([cmaps['2VarAddA' + str(i)] for i in range(2)], + '2VarAddA': MultivarColormap([cmaps[f'2VarAddA{i}'] for i in range(2)], 'sRGB_add', name='2VarAddA'), - '2VarSubA': MultivarColormap([cmaps['2VarSubA' + str(i)] for i in range(2)], + '2VarSubA': MultivarColormap([cmaps[f'2VarSubA{i}'] for i in range(2)], 'sRGB_sub', name='2VarSubA'), - '3VarAddA': MultivarColormap([cmaps['3VarAddA' + str(i)] for i in range(3)], + '3VarAddA': MultivarColormap([cmaps[f'3VarAddA{i}'] for i in range(3)], 'sRGB_add', name='3VarAddA'), } diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index f2ee8ff836a0..9d5f5cf955ff 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1351,10 +1351,10 @@ def __call__(self, X, alpha=None, bytes=False, clip=True): if alpha is not None: if clip: alpha = np.clip(alpha, 0, 1) - if alpha.shape not in [(), np.array(X[0]).shape]: + if np.shape(alpha) not in [(), np.shape(X[0])]: raise ValueError( - f"alpha is array-like but its shape {alpha.shape} does " - f"not match that of X[0] {np.array(X[0]).shape}") + f"alpha is array-like but its shape {np.shape(alpha)} does " + f"not match that of X[0] {np.shape(X[0])}") rgba[..., -1] *= alpha if bytes: @@ -1385,14 +1385,14 @@ def __copy__(self): def __eq__(self, other): if not isinstance(other, MultivarColormap): return False - if not len(self) == len(other): + if len(self) != len(other): return False for c0, c1 in zip(self, other): - if not c0 == c1: + if c0 != c1: return False if not all(self._rgba_bad == other._rgba_bad): return False - if not self.combination_mode == other.combination_mode: + if self.combination_mode != other.combination_mode: return False return True @@ -1430,7 +1430,7 @@ def resampled(self, lutshape): MultivarColormap """ - if not np.iterable(lutshape) or not len(lutshape) == len(self): + if not np.iterable(lutshape) or len(lutshape) != len(self): raise ValueError(f"lutshape must be of length {len(self)}") new_cmap = self.copy() for i, s in enumerate(lutshape): @@ -1447,7 +1447,7 @@ def with_extremes(self, *, bad=None, under=None, over=None): Parameters ---------- - bad : None or Matplotlib color + bad : None or :mpltype:`color` If Matplotlib color, the bad value is set accordingly in the copy under : None or tuple of length matching the length of the MultivarColormap @@ -1468,17 +1468,19 @@ def with_extremes(self, *, bad=None, under=None, over=None): if bad is not None: new_cm._rgba_bad = to_rgba(bad) if under is not None: - if not np.iterable(under) or not len(under) == len(new_cm): + if not np.iterable(under) or len(under) != len(new_cm): raise ValueError("*under* must contain a color for each scalar colormap" f" i.e. be of length {len(new_cm)}.") else: - [c.set_under(b) for c, b in zip(new_cm, under)] + for c, b in zip(new_cm, under): + c.set_under(b) if over is not None: - if not np.iterable(over) or not len(over) == len(new_cm): + if not np.iterable(over) or len(over) != len(new_cm): raise ValueError("*over* must contain a color for each scalar colormap" f" i.e. be of length {len(new_cm)}.") else: - [c.set_over(b) for c, b in zip(new_cm, over)] + for c, b in zip(new_cm, over): + c.set_over(b) return new_cm @property @@ -1511,9 +1513,9 @@ def _repr_html_(self): class BivarColormap: """ - Baseclass for all bivarate to RGBA mappings. + Base class for all bivariate to RGBA mappings. - Designed as a drop-in replcement for Colormap when using a 2D + Designed as a drop-in replacement for Colormap when using a 2D lookup table. To be used with `~matplotlib.cm.VectorMappable`. """ @@ -1527,7 +1529,7 @@ def __init__(self, N=256, M=256, shape='square', origin=(0, 0), M : int The number of RGB quantization levels along the second axis. If None, M = N - shape: str 'square' or 'circle' or 'ignore' or 'circleignore' + shape: {'square', 'circle', 'ignore', 'circleignore'} - 'square' each variate is clipped to [0,1] independently - 'circle' the variates are clipped radially to the center @@ -1594,7 +1596,7 @@ def __call__(self, X, alpha=None, bytes=False): if len(X) != 2: raise ValueError( f'For a `BivarColormap` the data must have a first dimension ' - f'{2}, not {len(X)}') + f'2, not {len(X)}') if not self._isinit: self._init() @@ -1621,8 +1623,7 @@ def __call__(self, X, alpha=None, bytes=False): X1[X1 == self.M] = self.M - 1 # Pre-compute the masks before casting to int (which can truncate) - mask_outside = (X0 < 0) | (X1 < 0) \ - | (X0 >= self.N) | (X1 >= self.M) + mask_outside = (X0 < 0) | (X1 < 0) | (X0 >= self.N) | (X1 >= self.M) # If input was masked, get the bad mask from it; else mask out nans. mask_bad_0 = X0.mask if np.ma.is_masked(X0) else np.isnan(X0) mask_bad_1 = X1.mask if np.ma.is_masked(X1) else np.isnan(X1) @@ -1650,10 +1651,10 @@ def __call__(self, X, alpha=None, bytes=False): alpha = np.clip(alpha, 0, 1) if bytes: alpha *= 255 # Will be cast to uint8 upon assignment. - if alpha.shape not in [(), np.array(X0).shape]: + if np.shape(alpha) not in [(), np.shape(X0)]: raise ValueError( - f"alpha is array-like but its shape {alpha.shape} does " - f"not match that of X[0] {np.array(X0).shape}") + f"alpha is array-like but its shape {np.shape(alpha)} does " + f"not match that of X[0] {np.shape(X0)}") rgba[..., -1] = alpha # If the "bad" color is all zeros, then ignore alpha input. if (np.array(self._rgba_bad) == 0).all(): @@ -1713,7 +1714,7 @@ def __eq__(self, other): return False if not np.array_equal(self._rgba_outside, other._rgba_outside): return False - if not self.shape == other.shape: + if self.shape != other.shape: return False return True @@ -1728,6 +1729,7 @@ def get_outside(self): def resampled(self, lutshape, transposed=False): """ Return a new colormap with *lutshape* entries. + Note that this function does not move the origin. Parameters @@ -1748,7 +1750,7 @@ def resampled(self, lutshape, transposed=False): BivarColormap """ - if not np.iterable(lutshape) or not len(lutshape) == 2: + if not np.iterable(lutshape) or len(lutshape) != 2: raise ValueError("lutshape must be of length 2") lutshape = [lutshape[0], lutshape[1]] if lutshape[0] is None or lutshape[0] == 1: @@ -1776,7 +1778,7 @@ def resampled(self, lutshape, transposed=False): else: x_1 = np.linspace(1, 0, lutshape[1])[np.newaxis, :] * np.ones(lutshape) - # we need to use shape = 'sqare' while resampling the colormap. + # we need to use shape = 'square' while resampling the colormap. # if the colormap has shape = 'circle' we would otherwise get *outside* in the # resampled colormap shape_memory = self._shape @@ -1785,7 +1787,7 @@ def resampled(self, lutshape, transposed=False): new_lut = self((x_1, x_0)) new_cmap = BivarColormapFromImage(new_lut, name=self.name, shape=shape_memory, - origin=(self.origin[1], self.origin[0])) + origin=self.origin[::-1]) else: new_lut = self((x_0, x_1)) new_cmap = BivarColormapFromImage(new_lut, name=self.name, @@ -1801,12 +1803,8 @@ def reversed(self, axis_0=True, axis_1=True): """ Reverses both or one of the axis. """ - r_0 = 1 - if axis_0: - r_0 = -1 - r_1 = 1 - if axis_1: - r_1 = -1 + r_0 = -1 if axis_0 else 1 + r_1 = -1 if axis_1 else 1 return self.resampled((r_0, r_1)) def transposed(self): @@ -1823,14 +1821,14 @@ def with_extremes(self, *, bad=None, outside=None, shape=None, origin=None): Parameters ---------- - bad : None or Matplotlib color + bad : None or :mpltype:`color` If Matplotlib color, the *bad* value is set accordingly in the copy - outside : None or Matplotlib color + outside : None or :mpltype:`color` If Matplotlib color and shape is 'ignore' or 'circleignore', values *outside* the colormap are colored accordingly in the copy - shape: str 'square' or 'circle' or 'ignore' or 'circleignore' + shape : {'square', 'circle', 'ignore', 'circleignore'} - If 'square' each variate is clipped to [0,1] independently - If 'circle' the variates are clipped radially to the center @@ -1838,7 +1836,7 @@ def with_extremes(self, *, bad=None, outside=None, shape=None, origin=None): is displayed - If 'ignore' the variates are not clipped, but instead assigned the *outside* color - - If 'circleignore' a circular mask is applied, but the data is not + - If 'circleignore' a circular mask is applied, but the data is not clipped and instead assigned the *outside* color origin: (float, float) @@ -1890,7 +1888,7 @@ def _clip(self, X): ---------- X: np.array array of floats or ints to be clipped - shape: str 'square' or 'circle' or 'ignore' or 'circleignore' + shape : {'square', 'circle', 'ignore', 'circleignore'} - If 'square' each variate is clipped to [0,1] independently - If 'circle' the variates are clipped radially to the center @@ -1921,7 +1919,7 @@ def _clip(self, X): elif self.shape == 'circle' or self.shape == 'circleignore': for X_part in X: - if not X_part.dtype.kind == "f": + if X_part.dtype.kind != "f": raise NotImplementedError( "Circular bivariate colormaps are only" " implemented for use with with floats") @@ -1944,14 +1942,14 @@ def __getitem__(self, item): if origin_1_as_int > self.M-1: origin_1_as_int = self.M-1 one_d_lut = self._lut[:, origin_1_as_int] - new_cmap = ListedColormap(one_d_lut, name=self.name+'_0', N=self.N) + new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_0', N=self.N) elif item == 1: origin_0_as_int = int(self._origin[0]*self.N) if origin_0_as_int > self.N-1: origin_0_as_int = self.N-1 one_d_lut = self._lut[origin_0_as_int, :] - new_cmap = ListedColormap(one_d_lut, name=self.name+'_1', N=self.M) + new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_1', N=self.M) else: raise KeyError(f"only 0 or 1 are" f" valid keys for BivarColormap, not {item!r}") @@ -2025,15 +2023,15 @@ def copy(self): class SegmentedBivarColormap(BivarColormap): """ - BivarColormap object generated by supersampling a regular grid + BivarColormap object generated by supersampling a regular grid. Parameters ---------- - patch : nparray of shape (k, k, 3) - This patch gets supersamples to a lut of shape (N, M, 4) + patch : np.array of shape (k, k, 3) + This patch gets supersampled to a lut of shape (N, M, 4). N : int The number of RGB quantization levels along each axis. - shape: str 'square' or 'circle' or 'ignore' or 'circleignore' + shape: {'square', 'circle', 'ignore', 'circleignore'} - If 'square' each variate is clipped to [0,1] independently - If 'circle' the variates are clipped radially to the center @@ -2073,13 +2071,13 @@ def _init(self): class BivarColormapFromImage(BivarColormap): """ - BivarColormap object generated by supersampling a regular grid + BivarColormap object generated by supersampling a regular grid. Parameters ---------- lut : nparray of shape (N, M, 3) or (N, M, 4) The look-up-table - shape: str 'square' or 'circle' or 'ignore' or 'circleignore' + shape: {'square', 'circle', 'ignore', 'circleignore'} - If 'square' each variate is clipped to [0,1] independently - If 'circle' the variates are clipped radially to the center @@ -2099,7 +2097,7 @@ class BivarColormapFromImage(BivarColormap): """ def __init__(self, lut, shape='square', origin=(0, 0), name='from image'): - # We can allow for a PIL.Image as unput in the following way, but importing + # We can allow for a PIL.Image as input in the following way, but importing # matplotlib.image.pil_to_array() results in a circular import # For now, this function only accepts numpy arrays. # i.e.: diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index 824081d20a8d..0383c21155af 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -158,7 +158,7 @@ class MultivarColormap: def copy(self) -> MultivarColormap: ... def __copy__(self) -> MultivarColormap: ... def __getitem__(self, item: int) -> Colormap: ... - def __iter__(self) -> Colormap: ... + def __iter__(self) -> Iterator[Colormap]: ... def __len__(self) -> int: ... def get_bad(self) -> np.ndarray: ... def resampled(self, lutshape: Sequence[int | None]) -> MultivarColormap: ... @@ -201,13 +201,13 @@ class BivarColormap: def origin(self) -> tuple[float, float]: ... def __copy__(self) -> BivarColormap: ... def __getitem__(self, item: int) -> Colormap: ... - def __eq__(self, other) -> bool: ... + def __eq__(self, other: Any) -> bool: ... def get_bad(self) -> np.ndarray: ... def get_outside(self) -> np.ndarray: ... def copy(self) -> BivarColormap: ... def resampled(self, lutshape: Sequence[int | None], transposed: bool = ...) -> BivarColormap: ... def transposed(self) -> BivarColormap: ... - def reversed(self, axis_0: bool = True, axis_1: bool = True) -> BivarColormap: ... + def reversed(self, axis_0: bool = ..., axis_1: bool = ...) -> BivarColormap: ... def with_extremes( self, *, diff --git a/lib/matplotlib/tests/test_multivariate_colormaps.py b/lib/matplotlib/tests/test_multivariate_colormaps.py index fdfcf0c1d537..3b6e02b7dc9b 100644 --- a/lib/matplotlib/tests/test_multivariate_colormaps.py +++ b/lib/matplotlib/tests/test_multivariate_colormaps.py @@ -13,7 +13,7 @@ @image_comparison(["bivariate_cmap_shapes.png"]) def test_bivariate_cmap_shapes(): - x_0 = (np.arange(100, dtype='float32').reshape(10, 10) % 10)/9 * 1.2 - 0.1 + x_0 = np.repeat(np.linspace(-0.1, 1.1, 10, dtype='float32'), (10, 1)) x_1 = x_0.T fig, axes = plt.subplots(1, 4, figsize=(10, 2)) From 69f9977cf799667ee32aa4aae141263d6a540ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Mon, 5 Aug 2024 17:53:33 +0200 Subject: [PATCH 12/18] Fixes based on feedback on review --- lib/matplotlib/colors.py | 71 +++++++++---------- lib/matplotlib/colors.pyi | 17 +++-- .../tests/test_multivariate_colormaps.py | 8 +-- 3 files changed, 47 insertions(+), 49 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 9d5f5cf955ff..9e8e87bc6f4f 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -728,7 +728,7 @@ def __call__(self, X, alpha=None, bytes=False): bytes : bool If False (default), the returned RGBA values will be floats in the interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the - interval ``[0, 255]`` + interval ``[0, 255]``. Returns ------- @@ -736,6 +736,8 @@ def __call__(self, X, alpha=None, bytes=False): RGBA values with a shape of ``X.shape + (4, )``. """ rgba, mask = self._get_rgba_and_mask(X, alpha=alpha, bytes=bytes) + if not np.iterable(X): + rgba = tuple(rgba) return rgba def _get_rgba_and_mask(self, X, alpha=None, bytes=False): @@ -758,9 +760,9 @@ def _get_rgba_and_mask(self, X, alpha=None, bytes=False): Returns ------- - (colors, mask), where color is a tuple of RGBA values if X is scalar, - otherwise an array of RGBA values with a shape of ``X.shape + (4, )``, - and mask is a boolean array. + colors : array of RGBA values with a shape of ``X.shape + (4, )``. + mask : boolean array with True where the input is ``np.nan`` or + masked. """ if not self._isinit: self._init() @@ -805,8 +807,6 @@ def _get_rgba_and_mask(self, X, alpha=None, bytes=False): if (lut[-1] == 0).all(): rgba[mask_bad] = (0, 0, 0, 0) - if not np.iterable(X): - rgba = tuple(rgba) return rgba, mask_bad def __copy__(self): @@ -1291,9 +1291,7 @@ def __init__(self, colormaps, combination_mode, name='multivariate colormap'): " Colormap or valid strings.") self._colormaps = colormaps - if combination_mode not in ['sRGB_add', 'sRGB_sub']: - raise ValueError("Combination_mode must be 'sRGB_add' or 'sRGB_sub'," - f" {combination_mode!r} is not allowed.") + _api.check_in_list(['sRGB_add', 'sRGB_sub'], combination_mode=combination_mode) self._combination_mode = combination_mode self.n_variates = len(colormaps) self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything. @@ -1332,10 +1330,8 @@ def __call__(self, X, alpha=None, bytes=False, clip=True): f'For the selected colormap the data must have a first dimension ' f'{len(self)}, not {len(X)}') rgba, mask_bad = self[0]._get_rgba_and_mask(X[0], bytes=False) - rgba = np.asarray(rgba) for c, xx in zip(self[1:], X[1:]): sub_rgba, sub_mask_bad = c._get_rgba_and_mask(xx, bytes=False) - sub_rgba = np.asarray(sub_rgba) rgba[..., :3] += sub_rgba[..., :3] # add colors rgba[..., 3] *= sub_rgba[..., 3] # multiply alpha mask_bad |= sub_mask_bad @@ -1419,11 +1415,10 @@ def resampled(self, lutshape): Parameters ---------- - lutshape : tuple of ints or None - The tuple must be of length matching the number of variates, - and each entry is either an int or None. - If an int, the corresponding colorbar is resampled. - If None, the corresponding colorbar is not resampled. + lutshape : tuple of (`int`, `None`) + The tuple must have a length matching the number of variates. + For each element in the tuple, if `int`, the corresponding colorbar + is resampled, if `None`, the corresponding colorbar is not resampled. Returns ------- @@ -1440,22 +1435,24 @@ def resampled(self, lutshape): def with_extremes(self, *, bad=None, under=None, over=None): """ - Return a copy of the MultivarColormap, for which the colors for masked (*bad*) - values has been set and, low (*under*) and high (*over*) out-of-range values, - been set in the component colormaps. Note that *under* and *over* colors - are subject to the mixing rules determined by the *combination_mode*. + Return a copy of the `MultivarColormap` with modified out-of-range attributes. + + The *bad* keyword modifies the copied `MultivarColormap` while *under* and + *over* modifies the attributes of the copied component colormaps. + Note that *under* and *over* colors are subject to the mixing rules determined + by the *combination_mode*. Parameters ---------- bad : None or :mpltype:`color` If Matplotlib color, the bad value is set accordingly in the copy - under : None or tuple of length matching the length of the MultivarColormap + under : None or tuple of :mpltype:`color` If tuple, the `under` value of each component is set with the values from the tuple. - over : None or tuple of length matching the length of the MultivarColormap - If tuple, the `under` value of each component is set with the values + over : None or tuple of :mpltype:`color` + If tuple, the `over` value of each component is set with the values from the tuple. Returns @@ -1528,7 +1525,6 @@ def __init__(self, N=256, M=256, shape='square', origin=(0, 0), The number of RGB quantization levels along the first axis. M : int The number of RGB quantization levels along the second axis. - If None, M = N shape: {'square', 'circle', 'ignore', 'circleignore'} - 'square' each variate is clipped to [0,1] independently @@ -1551,11 +1547,8 @@ def __init__(self, N=256, M=256, shape='square', origin=(0, 0), self.name = name self.N = int(N) # ensure that N is always int self.M = int(M) - if shape in ['square', 'circle', 'ignore', 'circleignore']: - self._shape = shape - else: - raise ValueError("The shape must be a valid string, " - "'square', 'circle', 'ignore', or 'circleignore'") + _api.check_in_list(['square', 'circle', 'ignore', 'circleignore'], shape=shape) + self._shape = shape self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything. self._rgba_outside = (1.0, 0.0, 1.0, 1.0) self._isinit = False @@ -1738,8 +1731,8 @@ def resampled(self, lutshape, transposed=False): The tuple must be of length 2, and each entry is either an int or None. - If an int, the corresponding axis is resampled. - - If -1, the axis is inverted - If negative the corresponding axis is resampled in reverse + - If -1, the axis is inverted - If 1 or None, the corresponding axis is not resampled. transposed : bool @@ -1815,9 +1808,10 @@ def transposed(self): def with_extremes(self, *, bad=None, outside=None, shape=None, origin=None): """ - Return a copy of the BivarColormap, for which the colors for masked (*bad*) - valuesand if shape = 'ignore' or 'circleignore', out-of-range *outside* values, - have been set accordingly. + Return a copy of the `BivarColormap` with modified attributes. + + Note that the *outside* color is only relevantif `shape` = 'ignore' + or 'circleignore'. Parameters ---------- @@ -1855,11 +1849,9 @@ def with_extremes(self, *, bad=None, outside=None, shape=None, origin=None): if outside is not None: new_cm._rgba_outside = to_rgba(outside) if shape is not None: - if shape in ['square', 'circle', 'ignore', 'circleignore']: - new_cm._shape = shape - else: - raise ValueError("The shape must be a valid string, " - "'square', 'circle', 'ignore', or 'circleignore'") + _api.check_in_list(['square', 'circle', 'ignore', 'circleignore'], + shape=shape) + new_cm._shape = shape if origin is not None: new_cm._origin = (float(origin[0]), float(origin[1])) @@ -2014,7 +2006,7 @@ def color_block(color): '
' '
' f'bad {color_block(self.get_bad())}' - '
') + '') def copy(self): """Return a copy of the colormap.""" @@ -2052,6 +2044,7 @@ class SegmentedBivarColormap(BivarColormap): def __init__(self, patch, N=256, shape='square', origin=(0, 0), name='segmented bivariate colormap'): + _api.check_shape((None, None, 3), patch=patch) self.patch = patch super().__init__(N, N, shape, origin, name=name) diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index 0383c21155af..6941e3d5d176 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -140,9 +140,8 @@ class ListedColormap(Colormap): class MultivarColormap: name: str - colormaps: list[Colormap] n_variates: int - def __init__(self, colormaps: list[Colormap], combination_mode: str, name: str = ...) -> None: ... + def __init__(self, colormaps: list[Colormap], combination_mode: Literal['sRGB_add', 'sRGB_sub'], name: str = ...) -> None: ... @overload def __call__( self, X: Sequence[Sequence[float]] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ..., clip: bool = ... @@ -157,6 +156,7 @@ class MultivarColormap: ) -> tuple[float, float, float, float] | np.ndarray: ... def copy(self) -> MultivarColormap: ... def __copy__(self) -> MultivarColormap: ... + def __eq__(self, other: Any) -> bool: ... def __getitem__(self, item: int) -> Colormap: ... def __iter__(self) -> Iterator[Colormap]: ... def __len__(self) -> int: ... @@ -179,7 +179,9 @@ class BivarColormap: N: int M: int n_variates: int - def __init__(self, N: int = ..., M: int | None = ..., shape: str = ..., origin: Sequence[float] = ..., name: str = ... + def __init__( + self, N: int = ..., M: int | None = ..., shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ..., + origin: Sequence[float] = ..., name: str = ... ) -> None: ... @overload def __call__( @@ -199,12 +201,12 @@ class BivarColormap: def shape(self) -> str: ... @property def origin(self) -> tuple[float, float]: ... + def copy(self) -> BivarColormap: ... def __copy__(self) -> BivarColormap: ... def __getitem__(self, item: int) -> Colormap: ... def __eq__(self, other: Any) -> bool: ... def get_bad(self) -> np.ndarray: ... def get_outside(self) -> np.ndarray: ... - def copy(self) -> BivarColormap: ... def resampled(self, lutshape: Sequence[int | None], transposed: bool = ...) -> BivarColormap: ... def transposed(self) -> BivarColormap: ... def reversed(self, axis_0: bool = ..., axis_1: bool = ...) -> BivarColormap: ... @@ -221,11 +223,14 @@ class BivarColormap: class SegmentedBivarColormap(BivarColormap): def __init__( - self, patch: np.ndarray, N: int = ..., shape: str = ..., origin: Sequence[float] = ..., name: str = ... + self, patch: np.ndarray, N: int = ..., shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ..., + origin: Sequence[float] = ..., name: str = ... ) -> None: ... class BivarColormapFromImage(BivarColormap): - def __init__(self, lut: np.ndarray, shape: str = ..., origin: Sequence[float] = ..., name: str = ... + def __init__( + self, lut: np.ndarray, shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ..., + origin: Sequence[float] = ..., name: str = ... ) -> None: ... class Normalize: diff --git a/lib/matplotlib/tests/test_multivariate_colormaps.py b/lib/matplotlib/tests/test_multivariate_colormaps.py index 3b6e02b7dc9b..034f8d7590b2 100644 --- a/lib/matplotlib/tests/test_multivariate_colormaps.py +++ b/lib/matplotlib/tests/test_multivariate_colormaps.py @@ -13,7 +13,7 @@ @image_comparison(["bivariate_cmap_shapes.png"]) def test_bivariate_cmap_shapes(): - x_0 = np.repeat(np.linspace(-0.1, 1.1, 10, dtype='float32'), (10, 1)) + x_0 = np.repeat(np.linspace(-0.1, 1.1, 10, dtype='float32')[None, :], 10, axis=0) x_1 = x_0.T fig, axes = plt.subplots(1, 4, figsize=(10, 2)) @@ -193,7 +193,7 @@ def test_multivar_cmap_call(): def test_multivar_bad_mode(): cmap = mpl.multivar_colormaps['2VarSubA'] - with pytest.raises(ValueError, match="Combination_mode must be 'sRGB_add' or"): + with pytest.raises(ValueError, match="is not a valid value for"): cmap = mpl.colors.MultivarColormap(cmap[:], 'bad') @@ -359,11 +359,11 @@ def test_bivar_cmap_bad_shape(): cmap = mpl.bivar_colormaps['BiCone'] _ = cmap.lut with pytest.raises(ValueError, - match="shape must be a valid string"): + match="is not a valid value for shape"): cmap.with_extremes(shape='bad_shape') with pytest.raises(ValueError, - match="shape must be a valid string"): + match="is not a valid value for shape"): mpl.colors.BivarColormapFromImage(np.ones((3, 3, 4)), shape='bad_shape') From 8e3f3936258b8d7e81f56b7eca32769ada647914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Wed, 7 Aug 2024 16:00:08 +0200 Subject: [PATCH 13/18] removed exposure of multivariate and bivariate colormaps in cm.globals() --- lib/matplotlib/cm.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py index d85554f76f26..025cb84db1d7 100644 --- a/lib/matplotlib/cm.py +++ b/lib/matplotlib/cm.py @@ -241,10 +241,8 @@ def get_cmap(self, cmap): globals().update(_colormaps) _multivar_colormaps = ColormapRegistry(multivar_cmaps) -globals().update(_multivar_colormaps) _bivar_colormaps = ColormapRegistry(bivar_cmaps) -globals().update(_bivar_colormaps) # This is an exact copy of pyplot.get_cmap(). It was removed in 3.9, but apparently From a35bc29e1c1e20c9b676f286124e7261945d6590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Mon, 12 Aug 2024 08:50:19 +0200 Subject: [PATCH 14/18] improved docstring for MultivarColormap --- lib/matplotlib/colors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 9e8e87bc6f4f..4bf0fbc52e97 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1270,9 +1270,9 @@ def __init__(self, colormaps, combination_mode, name='multivariate colormap'): Describe how colormaps are combined in sRGB space - If 'sRGB_add' -> Mixing produces brighter colors - `sRGB = cmap[0][X[0]] + cmap[1][x[1]] + ... + cmap[n-1][x[n-1]]` + `sRGB = sum(colors)` - If 'sRGB_sub' -> Mixing produces darker colors - `sRGB = cmap[0][X[0]] + cmap[1][x[1]] + ... + cmap[n-1][x[n-1]] - n + 1` + `sRGB = 1 - sum(1 - colors)` name : str, optional The name of the colormap family. """ From b73adcfc1e224bf063cd0a6b13690808f27a2e3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Mon, 19 Aug 2024 09:08:54 +0200 Subject: [PATCH 15/18] Apply suggestions from code review Co-authored-by: Elliott Sales de Andrade --- lib/matplotlib/colors.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 4bf0fbc52e97..1318aed389bc 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1525,7 +1525,7 @@ def __init__(self, N=256, M=256, shape='square', origin=(0, 0), The number of RGB quantization levels along the first axis. M : int The number of RGB quantization levels along the second axis. - shape: {'square', 'circle', 'ignore', 'circleignore'} + shape : {'square', 'circle', 'ignore', 'circleignore'} - 'square' each variate is clipped to [0,1] independently - 'circle' the variates are clipped radially to the center @@ -1536,7 +1536,7 @@ def __init__(self, N=256, M=256, shape='square', origin=(0, 0), - 'circleignore' a circular mask is applied, but the data is not clipped and instead assigned the 'outside' color - origin: (float, float) + origin : (float, float) The relative origin of the colormap. Typically (0, 0), for colormaps that are linear on both axis, and (.5, .5) for circular colormaps. Used when getting 1D colormaps from 2D colormaps. @@ -1762,14 +1762,11 @@ def resampled(self, lutshape, transposed=False): lutshape[1] = -lutshape[1] if lutshape[1] == 1: lutshape[1] = self.M - if not inverted[0]: - x_0 = np.linspace(0, 1, lutshape[0])[:, np.newaxis] * np.ones(lutshape) - else: - x_0 = np.linspace(1, 0, lutshape[0])[:, np.newaxis] * np.ones(lutshape) - if not inverted[1]: - x_1 = np.linspace(0, 1, lutshape[1])[np.newaxis, :] * np.ones(lutshape) - else: - x_1 = np.linspace(1, 0, lutshape[1])[np.newaxis, :] * np.ones(lutshape) + x_0, x_1 = np.mgrid[0:1:(lutshape[0] * 1j), 0:1:(lutshape[1] * 1j)] + if inverted[0]: + x_0 = x_0[::-1, :] + if inverted[1]: + x_1 = x_1[:, ::-1] # we need to use shape = 'square' while resampling the colormap. # if the colormap has shape = 'circle' we would otherwise get *outside* in the @@ -1810,7 +1807,7 @@ def with_extremes(self, *, bad=None, outside=None, shape=None, origin=None): """ Return a copy of the `BivarColormap` with modified attributes. - Note that the *outside* color is only relevantif `shape` = 'ignore' + Note that the *outside* color is only relevant if `shape` = 'ignore' or 'circleignore'. Parameters @@ -1833,7 +1830,7 @@ def with_extremes(self, *, bad=None, outside=None, shape=None, origin=None): - If 'circleignore' a circular mask is applied, but the data is not clipped and instead assigned the *outside* color - origin: (float, float) + origin : (float, float) The relative origin of the colormap. Typically (0, 0), for colormaps that are linear on both axis, and (.5, .5) for circular colormaps. Used when getting 1D colormaps from 2D colormaps. @@ -2023,7 +2020,7 @@ class SegmentedBivarColormap(BivarColormap): This patch gets supersampled to a lut of shape (N, M, 4). N : int The number of RGB quantization levels along each axis. - shape: {'square', 'circle', 'ignore', 'circleignore'} + shape : {'square', 'circle', 'ignore', 'circleignore'} - If 'square' each variate is clipped to [0,1] independently - If 'circle' the variates are clipped radially to the center @@ -2033,7 +2030,7 @@ class SegmentedBivarColormap(BivarColormap): 'outside' color - If 'circleignore' a circular mask is applied, but the data is not clipped - origin: (float, float) + origin : (float, float) The relative origin of the colormap. Typically (0, 0), for colormaps that are linear on both axis, and (.5, .5) for circular colormaps. Used when getting 1D colormaps from 2D colormaps. From 438923f5a3d75108eccb1b95ff4821104ca486c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Mon, 19 Aug 2024 09:34:56 +0200 Subject: [PATCH 16/18] test _repr_png_() for MultivarColormap --- lib/matplotlib/tests/test_multivariate_colormaps.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/matplotlib/tests/test_multivariate_colormaps.py b/lib/matplotlib/tests/test_multivariate_colormaps.py index 034f8d7590b2..81a2e6adeb35 100644 --- a/lib/matplotlib/tests/test_multivariate_colormaps.py +++ b/lib/matplotlib/tests/test_multivariate_colormaps.py @@ -478,6 +478,19 @@ def test_bivariate_repr_html(): assert html.endswith('') +def test_multivariate_repr_png(): + cmap = mpl.multivar_colormaps['3VarAddA'] + png = cmap._repr_png_() + assert len(png) > 0 + img = Image.open(BytesIO(png)) + assert img.width > 0 + assert img.height > 0 + assert 'Title' in img.text + assert 'Description' in img.text + assert 'Author' in img.text + assert 'Software' in img.text + + def test_multivariate_repr_html(): cmap = mpl.multivar_colormaps['3VarAddA'] html = cmap._repr_html_() From dcf6f8a1b572058340b7cdb313b9c1b0a21a1271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Mon, 19 Aug 2024 10:36:58 +0200 Subject: [PATCH 17/18] improved docstring for SegmentedBivarColormap() also allows SegmentedBivarColormap to take non-square patches. This was easier that testing that the input is always square. --- lib/matplotlib/colors.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 1318aed389bc..30a5f12923b1 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -760,9 +760,10 @@ def _get_rgba_and_mask(self, X, alpha=None, bytes=False): Returns ------- - colors : array of RGBA values with a shape of ``X.shape + (4, )``. - mask : boolean array with True where the input is ``np.nan`` or - masked. + colors : np.ndarray + Array of RGBA values with a shape of ``X.shape + (4, )``. + mask : np.ndarray + Boolean array with True where the input is ``np.nan`` or masked. """ if not self._isinit: self._init() @@ -2016,8 +2017,9 @@ class SegmentedBivarColormap(BivarColormap): Parameters ---------- - patch : np.array of shape (k, k, 3) - This patch gets supersampled to a lut of shape (N, M, 4). + patch : np.array + Patch is required to have a shape (k, l, 3), and will get supersampled + to a lut of shape (N, N, 4). N : int The number of RGB quantization levels along each axis. shape : {'square', 'circle', 'ignore', 'circleignore'} @@ -2051,7 +2053,7 @@ def _init(self): _patch[:, :, :3] = self.patch _patch[:, :, 3] = 1 transform = mpl.transforms.Affine2D().translate(-0.5, -0.5)\ - .scale(self.N / (s[0] - 1), self.N / (s[0] - 1)) + .scale(self.N / (s[1] - 1), self.N / (s[0] - 1)) self._lut = np.empty((self.N, self.N, 4)) _image.resample(_patch, self._lut, transform, _image.BILINEAR, From f2f2d7151e4ebe43e1e380232c16f8e8eb4b7df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Thu, 22 Aug 2024 10:42:18 +0200 Subject: [PATCH 18/18] updated default arguments in docs for multivar and bivar colormaps based on code review by @story645 --- lib/matplotlib/colors.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 30a5f12923b1..7f29861d48e5 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -682,7 +682,7 @@ class Colormap: Typically, Colormap instances are used to convert data values (floats) from the interval ``[0, 1]`` to the RGBA color that the respective Colormap represents. For scaling of data into the ``[0, 1]`` interval see - `matplotlib.colors.Normalize`. Subclasses of `matplotlib.cm.VectorMappable` + `matplotlib.colors.Normalize`. Subclasses of `matplotlib.cm.ScalarMappable` make heavy use of this ``data -> normalize -> map-to-color`` processing chain. """ @@ -725,7 +725,7 @@ def __call__(self, X, alpha=None, bytes=False): alpha : float or array-like or None Alpha must be a scalar between 0 and 1, a sequence of such floats with shape matching X, or None. - bytes : bool + bytes : bool, default: False If False (default), the returned RGBA values will be floats in the interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the interval ``[0, 255]``. @@ -753,7 +753,7 @@ def _get_rgba_and_mask(self, X, alpha=None, bytes=False): alpha : float or array-like or None Alpha must be a scalar between 0 and 1, a sequence of such floats with shape matching X, or None. - bytes : bool + bytes : bool, default: False If False (default), the returned RGBA values will be floats in the interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the interval ``[0, 255]``. @@ -1259,7 +1259,7 @@ def reversed(self, name=None): class MultivarColormap: """ Class for holding multiple `~matplotlib.colors.Colormap` for use in a - `~matplotlib.cm.VectorMappable` object + `~matplotlib.cm.ScalarMappable` object """ def __init__(self, colormaps, combination_mode, name='multivariate colormap'): """ @@ -1289,7 +1289,7 @@ def __init__(self, colormaps, combination_mode, name='multivariate colormap'): colormaps[i] = mpl.colormaps[cmap] elif not isinstance(cmap, Colormap): raise ValueError("colormaps must be a list of objects that subclass" - " Colormap or valid strings.") + " Colormap or a name found in the colormap registry.") self._colormaps = colormaps _api.check_in_list(['sRGB_add', 'sRGB_sub'], combination_mode=combination_mode) @@ -1313,11 +1313,11 @@ def __call__(self, X, alpha=None, bytes=False, clip=True): alpha : float or array-like or None Alpha must be a scalar between 0 and 1, a sequence of such floats with shape matching *Xi*, or None. - bytes : bool + bytes : bool, default: False If False (default), the returned RGBA values will be floats in the interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the interval ``[0, 255]``. - clip : bool + clip : bool, default: True If True, clip output to 0 to 1 Returns @@ -1445,14 +1445,14 @@ def with_extremes(self, *, bad=None, under=None, over=None): Parameters ---------- - bad : None or :mpltype:`color` + bad: :mpltype:`color`, default: None If Matplotlib color, the bad value is set accordingly in the copy - under : None or tuple of :mpltype:`color` + under tuple of :mpltype:`color`, default: None If tuple, the `under` value of each component is set with the values from the tuple. - over : None or tuple of :mpltype:`color` + over tuple of :mpltype:`color`, default: None If tuple, the `over` value of each component is set with the values from the tuple. @@ -1514,7 +1514,7 @@ class BivarColormap: Base class for all bivariate to RGBA mappings. Designed as a drop-in replacement for Colormap when using a 2D - lookup table. To be used with `~matplotlib.cm.VectorMappable`. + lookup table. To be used with `~matplotlib.cm.ScalarMappable`. """ def __init__(self, N=256, M=256, shape='square', origin=(0, 0), @@ -1522,9 +1522,9 @@ def __init__(self, N=256, M=256, shape='square', origin=(0, 0), """ Parameters ---------- - N : int + N : int, default: 256 The number of RGB quantization levels along the first axis. - M : int + M : int, default: 256 The number of RGB quantization levels along the second axis. shape : {'square', 'circle', 'ignore', 'circleignore'} @@ -1537,7 +1537,7 @@ def __init__(self, N=256, M=256, shape='square', origin=(0, 0), - 'circleignore' a circular mask is applied, but the data is not clipped and instead assigned the 'outside' color - origin : (float, float) + origin : (float, float), default: (0,0) The relative origin of the colormap. Typically (0, 0), for colormaps that are linear on both axis, and (.5, .5) for circular colormaps. Used when getting 1D colormaps from 2D colormaps. @@ -1573,10 +1573,10 @@ def __call__(self, X, alpha=None, bytes=False): - For integers, *X* should be in the interval ``[0, Colormap.N)`` to return RGBA values *indexed* from the Colormap with index ``X``. - alpha : float or array-like or None + alpha : float or array-like or None, default: None Alpha must be a scalar between 0 and 1, a sequence of such floats with shape matching X0, or None. - bytes : bool + bytes : bool, default: False If False (default), the returned RGBA values will be floats in the interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the interval ``[0, 255]``. @@ -1736,7 +1736,7 @@ def resampled(self, lutshape, transposed=False): - If -1, the axis is inverted - If 1 or None, the corresponding axis is not resampled. - transposed : bool + transposed : bool, default: False if True, the axes are swapped after resampling Returns @@ -1870,7 +1870,7 @@ def origin(self): def _clip(self, X): """ For internal use when applying a BivarColormap to data. - i.e. cm.VectorMappable().to_rgba() + i.e. cm.ScalarMappable().to_rgba() Clips X[0] and X[1] according to 'self.shape'. X is modified in-place. @@ -3056,7 +3056,7 @@ def inverse(self, value): class NoNorm(Normalize): """ Dummy replacement for `Normalize`, for the case where we want to use - indices directly in a `~matplotlib.cm.VectorMappable`. + indices directly in a `~matplotlib.cm.ScalarMappable`. """ def __call__(self, value, clip=None): if np.iterable(value):