diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 8a77e5601d8c..dc40ece3d4be 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", ] @@ -1513,4 +1515,6 @@ def inner(ax, *args, data=None, **kwargs): # 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/__init__.pyi b/lib/matplotlib/__init__.pyi index e7208a17c99f..144523c65a2b 100644 --- a/lib/matplotlib/__init__.pyi +++ b/lib/matplotlib/__init__.pyi @@ -112,4 +112,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_bivar.py b/lib/matplotlib/_cm_bivar.py new file mode 100644 index 000000000000..bbe450a24775 --- /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, 256, "square", (.5, .5), name="BiPeak"), + "BiOrangeBlue": SegmentedBivarColormap( + 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 new file mode 100644 index 000000000000..e010c9caf877 --- /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([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/artist.py b/lib/matplotlib/artist.py index 981365d852be..dab487a43f7c 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -12,8 +12,7 @@ import numpy as np import matplotlib as mpl -from . import _api, cbook -from .colors import BoundaryNorm +from . import _api, cbook, cm from .cm import ScalarMappable from .path import Path from .transforms import (BboxBase, Bbox, IdentityTransform, Transform, TransformedBbox, @@ -1346,35 +1345,16 @@ def format_cursor_data(self, data): -------- get_cursor_data """ - if np.ndim(data) == 0 and isinstance(self, ScalarMappable): + if isinstance(self, ScalarMappable): + # Internal classes no longer inherit from ScalarMappable, and this + # block should never be executed by the internal API + # This block logically belongs to ScalarMappable, but can't be - # implemented in it because most ScalarMappable subclasses inherit - # from Artist first and from ScalarMappable second, so + # implemented in it in case custom ScalarMappable subclasses + # inherit from Artist first and from ScalarMappable second, so # Artist.format_cursor_data would always have precedence over # ScalarMappable.format_cursor_data. - n = self.cmap.N - if np.ma.getmask(data): - return "[]" - normed = self.norm(data) - if np.isfinite(normed): - if isinstance(self.norm, BoundaryNorm): - # not an invertible normalization mapping - cur_idx = np.argmin(np.abs(self.norm.boundaries - data)) - neigh_idx = max(0, cur_idx - 1) - # use max diff to prevent delta == 0 - delta = np.diff( - self.norm.boundaries[neigh_idx:cur_idx + 2] - ).max() - - else: - # Midpoints of neighboring color intervals. - neighbors = self.norm.inverse( - (int(normed * n) + np.array([0, 1])) / n) - delta = abs(neighbors - data).max() - g_sig_digits = cbook._g_sig_digits(data, delta) - else: - g_sig_digits = 3 # Consistent with default below. - return f"[{data:-#.{g_sig_digits}g}]" + return self.colorizer._format_cursor_data(data) else: try: data[0] @@ -1417,6 +1397,127 @@ def set_mouseover(self, mouseover): mouseover = property(get_mouseover, set_mouseover) # backcompat. +class ColorizingArtist(Artist): + def __init__(self, norm=None, cmap=None): + """ + Parameters + ---------- + norm : `.Normalize` (or subclass thereof) or str or None + The normalizing object which scales data, typically into the + interval ``[0, 1]``. + If a `str`, a `.Normalize` subclass is dynamically generated based + on the scale with the corresponding name. + If *None*, *norm* defaults to a *colors.Normalize* object which + initializes its scaling based on the first data processed. + cmap : str or `~matplotlib.colors.Colormap` + The colormap used to map normalized data values to RGBA colors. + """ + + Artist.__init__(self) + + self._A = None + if isinstance(norm, cm.Colorizer): + self._colorizer = norm + else: + self._colorizer = cm.Colorizer(cmap, norm) + + # self._id_colorizer = self.colorizer.callbacks.connect('changed', self.changed) + # self.callbacks = cbook.CallbackRegistry(signals=["changed"]) + + def set_array(self, A): + """ + Set the value array from array-like *A*. + + Parameters + ---------- + A : array-like or None + The values that are mapped to colors. + + The base class `.VectorMappable` does not make any assumptions on + the dimensionality and shape of the value array *A*. + """ + if A is None: + self._A = None + return + A = cm._ensure_multivariate_data(self.colorizer.cmap.n_variates, A) + + A = cbook.safe_masked_invalid(A, copy=True) + if not np.can_cast(A.dtype, float, "same_kind"): + if A.dtype.fields is None: + raise TypeError(f"Image data of dtype {A.dtype} cannot be " + f"converted to float") + else: + for key in A.dtype.fields: + if not np.can_cast(A[key].dtype, float, "same_kind"): + raise TypeError(f"Image data of dtype {A.dtype} cannot be " + f"converted to a sequence of floats") + self._A = A + self.colorizer.autoscale_None(A) + + def get_array(self): + """ + Return the array of values, that are mapped to colors. + + The base class `.VectorMappable` does not make any assumptions on + the dimensionality and shape of the array. + """ + return self._A + + @property + def colorizer(self): + return self._colorizer + + @colorizer.setter + def colorizer(self, colorizer): + self._set_colorizer(colorizer) + + def _set_colorizer(self, colorizer): + if isinstance(colorizer, cm.Colorizer): + if self._A is not None: + if not colorizer.cmap.n_variates == self.colorizer.cmap.n_variates: + raise ValueError('The new Colorizer object must have the same' + ' number of variates as the existing data.') + else: + # self.colorizer.callbacks.disconnect(self._id_colorizer) + self._colorizer = colorizer + # self._id_colorizer = colorizer.callbacks.connect('changed', + # self.changed) + # self.changed() + else: + raise ValueError('Only a Colorizer object can be set to colorizer.') + + def _get_colorizer(self): + """ + Function to get the colorizer. + Useful in edge cases where you want a standalone variable with the colorizer, + but also want the colorizer to update if the colorizer on the artist changes. + + used in `contour.ContourLabeler.label_colorizer` + """ + return self._colorizer + + ''' + def changed(self): + """ + Call this whenever the mappable is changed to notify all the + callbackSM listeners to the 'changed' signal. + """ + self.callbacks.process('changed') + self.stale = True + ''' + def format_cursor_data(self, data): + """ + Return a string representation of *data*. + + Uses the colorbar's formatter to format the data. + + See Also + -------- + get_cursor_data + """ + return self.colorizer._format_cursor_data(data) + + def _get_tightbbox_for_layout_only(obj, *args, **kwargs): """ Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b42d5267012e..c29b3a7d7e0f 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -34,6 +34,7 @@ from matplotlib import _api, _docstring, _preprocess_data from matplotlib.axes._base import ( _AxesBase, _TransformedBoundsLocator, _process_plot_format) +from matplotlib.cm import _ensure_cmap, _ensure_multivariate_params, Colorizer from matplotlib.axes._secondary_axes import SecondaryAxis from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer @@ -4939,9 +4940,9 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, collection.set_transform(mtransforms.IdentityTransform()) if colors is None: collection.set_array(c) - collection.set_cmap(cmap) - collection.set_norm(norm) - collection._scale_norm(norm, vmin, vmax) + collection.colorizer.cmap = cmap + collection.colorizer.norm = norm + collection.colorizer._scale_norm(norm, vmin, vmax, collection.get_array()) else: extra_kwargs = { 'cmap': cmap, 'norm': norm, 'vmin': vmin, 'vmax': vmax @@ -5265,14 +5266,6 @@ def reduce_C_function(C: array) -> float else: polygons = [polygon] - collection = mcoll.PolyCollection( - polygons, - edgecolors=edgecolors, - linewidths=linewidths, - offsets=offsets, - offset_transform=mtransforms.AffineDeltaTransform(self.transData) - ) - # Set normalizer if bins is 'log' if cbook._str_equal(bins, 'log'): if norm is not None: @@ -5283,6 +5276,26 @@ def reduce_C_function(C: array) -> float vmin = vmax = None bins = None + collection = mcoll.PolyCollection( + polygons, + edgecolors=edgecolors, + linewidths=linewidths, + offsets=offsets, + offset_transform=mtransforms.AffineDeltaTransform(self.transData), + norm=norm, + cmap=cmap + ) + + if marginals: + if vmin is None \ + and vmax is None \ + and not isinstance(norm, mcolors.Normalize)\ + and not isinstance(norm, Colorizer)\ + and 'clim' not in kwargs: + marginals_share_colorizer = False + else: + marginals_share_colorizer = True + if bins is not None: if not np.iterable(bins): minimum, maximum = min(accum), max(accum) @@ -5292,16 +5305,16 @@ def reduce_C_function(C: array) -> float accum = bins.searchsorted(accum) collection.set_array(accum) - collection.set_cmap(cmap) - collection.set_norm(norm) collection.set_alpha(alpha) collection._internal_update(kwargs) - collection._scale_norm(norm, vmin, vmax) + collection.colorizer._scale_norm(norm, vmin, vmax, collection.get_array()) + ''' # autoscale the norm with current accum values if it hasn't been set if norm is not None: if collection.norm.vmin is None and collection.norm.vmax is None: collection.norm.autoscale() + ''' corners = ((xmin, ymin), (xmax, ymax)) self.update_datalim(corners) @@ -5346,24 +5359,28 @@ def reduce_C_function(C: array) -> float values = values[mask] trans = getattr(self, f"get_{zname}axis_transform")(which="grid") - bar = mcoll.PolyCollection( - verts, transform=trans, edgecolors="face") + if marginals_share_colorizer: + bar = mcoll.PolyCollection( + verts, transform=trans, edgecolors="face", + norm=collection.colorizer) + else: + bar = mcoll.PolyCollection( + verts, transform=trans, edgecolors="face") + bar.colorizer.cmap = cmap + bar.colorizer.norm = norm bar.set_array(values) - bar.set_cmap(cmap) - bar.set_norm(norm) bar.set_alpha(alpha) bar._internal_update(kwargs) bars.append(self.add_collection(bar, autolim=False)) collection.hbar, collection.vbar = bars - def on_changed(collection): - collection.hbar.set_cmap(collection.get_cmap()) - collection.hbar.set_cmap(collection.get_cmap()) - collection.vbar.set_clim(collection.get_clim()) - collection.vbar.set_clim(collection.get_clim()) + if not marginals_share_colorizer: + def on_changed(): + collection.vbar.set_cmap(collection.get_cmap()) + collection.hbar.set_cmap(collection.get_cmap()) - collection.callbacks.connect('changed', on_changed) + collection.colorizer.callbacks.connect('changed', on_changed) return collection @@ -5755,6 +5772,7 @@ def imshow(self, X, cmap=None, norm=None, *, aspect=None, - (M, N): an image with scalar data. The values are mapped to colors using normalization and a colormap. See parameters *norm*, *cmap*, *vmin*, *vmax*. + - (v, M, N): if coupled with a cmap that supports v scalars - (M, N, 3): an image with RGB values (0-1 float or 0-255 int). - (M, N, 4): an image with RGBA values (0-1 float or 0-255 int), i.e. including transparency. @@ -5938,6 +5956,10 @@ def imshow(self, X, cmap=None, norm=None, *, aspect=None, `~matplotlib.pyplot.imshow` expects RGB images adopting the straight (unassociated) alpha representation. """ + cmap = _ensure_cmap(cmap) + data, norm, vmin, vmax = _ensure_multivariate_params(cmap.n_variates, X, + norm, vmin, vmax) + im = mimage.AxesImage(self, cmap=cmap, norm=norm, interpolation=interpolation, origin=origin, extent=extent, filternorm=filternorm, @@ -5957,7 +5979,7 @@ def imshow(self, X, cmap=None, norm=None, *, aspect=None, if im.get_clip_path() is None: # image does not already have clipping set, clip to Axes patch im.set_clip_path(self.patch) - im._scale_norm(norm, vmin, vmax) + im.colorizer._scale_norm(norm, vmin, vmax, im.get_array()) im.set_url(url) # update ax.dataLim, and, if autoscaling, set viewLim @@ -5967,7 +5989,8 @@ def imshow(self, X, cmap=None, norm=None, *, aspect=None, self.add_image(im) return im - def _pcolorargs(self, funcname, *args, shading='auto', **kwargs): + def _pcolorargs(self, funcname, *args, n_variates=1, + shading='auto', **kwargs): # - create X and Y if not present; # - reshape X and Y as needed if they are 1-D; # - check for proper sizes based on `shading` kwarg; @@ -5985,7 +6008,11 @@ def _pcolorargs(self, funcname, *args, shading='auto', **kwargs): if len(args) == 1: C = np.asanyarray(args[0]) - nrows, ncols = C.shape[:2] + if n_variates == 1: + nrows, ncols = C.shape[:2] + else: + nrows, ncols = C.shape[1:3] + if shading in ['gouraud', 'nearest']: X, Y = np.meshgrid(np.arange(ncols), np.arange(nrows)) else: @@ -6008,7 +6035,10 @@ def _pcolorargs(self, funcname, *args, shading='auto', **kwargs): 'x and y arguments to pcolormesh cannot have ' 'non-finite values or be of type ' 'numpy.ma.MaskedArray with masked values') - nrows, ncols = C.shape[:2] + if n_variates == 1: + nrows, ncols = C.shape[:2] + else: + nrows, ncols = C.shape[1:3] else: raise _api.nargs_error(funcname, takes="1 or 3", given=len(args)) @@ -6231,8 +6261,13 @@ def pcolor(self, *args, shading=None, alpha=None, norm=None, cmap=None, if shading is None: shading = mpl.rcParams['pcolor.shading'] shading = shading.lower() + + cmap = _ensure_cmap(cmap) X, Y, C, shading = self._pcolorargs('pcolor', *args, shading=shading, - kwargs=kwargs) + n_variates=cmap.n_variates, kwargs=kwargs) + data, norm, vmin, vmax = _ensure_multivariate_params(cmap.n_variates, C, + norm, vmin, vmax) + linewidths = (0.25,) if 'linewidth' in kwargs: kwargs['linewidths'] = kwargs.pop('linewidth') @@ -6268,7 +6303,7 @@ def pcolor(self, *args, shading=None, alpha=None, norm=None, cmap=None, collection = mcoll.PolyQuadMesh( coords, array=C, cmap=cmap, norm=norm, alpha=alpha, **kwargs) - collection._scale_norm(norm, vmin, vmax) + collection.colorizer._scale_norm(norm, vmin, vmax, collection.get_array()) # Transform from native to data coordinates? t = collection._transform @@ -6326,6 +6361,7 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, - (M, N) or M*N: a mesh with scalar data. The values are mapped to colors using normalization and a colormap. See parameters *norm*, *cmap*, *vmin*, *vmax*. + - (v, M, N): if coupled with a cmap that supports v scalars - (M, N, 3): an image with RGB values (0-1 float or 0-255 int). - (M, N, 4): an image with RGBA values (0-1 float or 0-255 int), i.e. including transparency. @@ -6489,8 +6525,12 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, shading = shading.lower() kwargs.setdefault('edgecolors', 'none') - X, Y, C, shading = self._pcolorargs('pcolormesh', *args, - shading=shading, kwargs=kwargs) + cmap = _ensure_cmap(cmap) + X, Y, C, shading = self._pcolorargs('pcolormesh', *args, shading=shading, + n_variates=cmap.n_variates, kwargs=kwargs) + data, norm, vmin, vmax = _ensure_multivariate_params(cmap.n_variates, C, + norm, vmin, vmax) + coords = np.stack([X, Y], axis=-1) kwargs.setdefault('snap', mpl.rcParams['pcolormesh.snap']) @@ -6498,7 +6538,7 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, collection = mcoll.QuadMesh( coords, antialiased=antialiased, shading=shading, array=C, cmap=cmap, norm=norm, alpha=alpha, **kwargs) - collection._scale_norm(norm, vmin, vmax) + collection.colorizer._scale_norm(norm, vmin, vmax, collection.get_array()) coords = coords.reshape(-1, 2) # flatten the grid structure; keep x, y @@ -6698,7 +6738,7 @@ def pcolorfast(self, *args, alpha=None, norm=None, cmap=None, vmin=None, ret = im if np.ndim(C) == 2: # C.ndim == 3 is RGB(A) so doesn't need scaling. - ret._scale_norm(norm, vmin, vmax) + ret.colorizer._scale_norm(norm, vmin, vmax, C) if ret.get_clip_path() is None: # image does not already have clipping set, clip to Axes patch diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index 186177576067..1a856a3894ad 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -11,7 +11,7 @@ from matplotlib.collections import ( EventCollection, QuadMesh, ) -from matplotlib.colors import Colormap, Normalize +from matplotlib.colors import Colormap, BivarColormap, MultivarColormap, Normalize from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer from matplotlib.contour import ContourSet, QuadContourSet from matplotlib.image import AxesImage, PcolorImage @@ -482,7 +482,7 @@ class Axes(_AxesBase): def imshow( self, X: ArrayLike | PIL.Image.Image, - cmap: str | Colormap | None = ..., + cmap: str | Colormap | BivarColormap | MultivarColormap | None = ..., norm: str | Normalize | None = ..., *, aspect: Literal["equal", "auto"] | float | None = ..., @@ -506,7 +506,7 @@ class Axes(_AxesBase): shading: Literal["flat", "nearest", "auto"] | None = ..., alpha: float | None = ..., norm: str | Normalize | None = ..., - cmap: str | Colormap | None = ..., + cmap: str | Colormap | BivarColormap | MultivarColormap | None = ..., vmin: float | None = ..., vmax: float | None = ..., data=..., @@ -517,7 +517,7 @@ class Axes(_AxesBase): *args: ArrayLike, alpha: float | None = ..., norm: str | Normalize | None = ..., - cmap: str | Colormap | None = ..., + cmap: str | Colormap | BivarColormap | MultivarColormap | None = ..., vmin: float | None = ..., vmax: float | None = ..., shading: Literal["flat", "nearest", "gouraud", "auto"] | None = ..., diff --git a/lib/matplotlib/axes/_base.pyi b/lib/matplotlib/axes/_base.pyi index 1fdc0750f0bc..c24b5a6de5e1 100644 --- a/lib/matplotlib/axes/_base.pyi +++ b/lib/matplotlib/axes/_base.pyi @@ -9,7 +9,7 @@ from matplotlib.backend_bases import RendererBase, MouseButton, MouseEvent from matplotlib.cbook import CallbackRegistry from matplotlib.container import Container from matplotlib.collections import Collection -from matplotlib.cm import ScalarMappable +from matplotlib.cm import VectorMappable from matplotlib.legend import Legend from matplotlib.lines import Line2D from matplotlib.gridspec import SubplotSpec, GridSpec @@ -399,7 +399,7 @@ class _AxesBase(martist.Artist): def get_xticklines(self, minor: bool = ...) -> list[Line2D]: ... def get_ygridlines(self) -> list[Line2D]: ... def get_yticklines(self, minor: bool = ...) -> list[Line2D]: ... - def _sci(self, im: ScalarMappable) -> None: ... + def _sci(self, im: VectorMappable) -> None: ... def get_autoscalex_on(self) -> bool: ... def get_autoscaley_on(self) -> bool: ... def set_autoscalex_on(self, b: bool) -> None: ... diff --git a/lib/matplotlib/backends/qt_editor/figureoptions.py b/lib/matplotlib/backends/qt_editor/figureoptions.py index c36bbeb62641..4f8677156c6b 100644 --- a/lib/matplotlib/backends/qt_editor/figureoptions.py +++ b/lib/matplotlib/backends/qt_editor/figureoptions.py @@ -146,12 +146,27 @@ def prepare_data(d, init): continue labeled_mappables.append((label, mappable)) mappables = [] - cmaps = [(cmap, name) for name, cmap in sorted(cm._colormaps.items())] for label, mappable in labeled_mappables: - cmap = mappable.get_cmap() - if cmap not in cm._colormaps.values(): + cmap = mappable.colorizer.cmap + if isinstance(cmap, mcolors.Colormap): + cmaps = [(cmap, name) for name, cmap in sorted(cm._colormaps.items())] + cvals = cm._colormaps.values() + elif isinstance(cmap, mcolors.BivarColormap): + cmaps = [(cmap, name) for name, cmap in sorted(cm._bivar_colormaps.items())] + cvals = cm._bivar_colormaps.values() + else: # isinstance(mappable.colorizer.cmap, mcolors.MultivarColormap): + cmaps = [(cmap, name) for name, cmap + in sorted(cm._multivar_colormaps.items())] + cvals = cm._multivar_colormaps.values() + if cmap not in cvals: cmaps = [(cmap, cmap.name), *cmaps] - low, high = mappable.get_clim() + low, high = mappable.colorizer.get_clim() + if len(low) == 1: + low = low[0] + high = high[0] + else: + low = str(low)[1:-1] + high = str(high)[1:-1] mappabledata = [ ('Label', label), ('Colormap', [cmap.name] + cmaps), @@ -242,6 +257,9 @@ def apply_callback(data): label, cmap, low, high = mappable_settings mappable.set_label(label) mappable.set_cmap(cmap) + if isinstance(low, str): + low = [float(l) for l in low.split(',')] + high = [float(l) for l in high.split(',')] mappable.set_clim(*sorted([low, high])) # re-generate legend, if checkbox is checked @@ -263,7 +281,6 @@ def apply_callback(data): if getattr(axes, f"get_{name}lim")() != orig_limits[name]: figure.canvas.toolbar.push_current() break - _formlayout.fedit( datalist, title="Figure options", parent=parent, icon=QtGui.QIcon( diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index e4f60aac37a8..4e97c2b13bc6 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -739,7 +739,17 @@ def safe_masked_invalid(x, copy=False): try: xm = np.ma.masked_where(~(np.isfinite(x)), x, copy=False) except TypeError: - return x + if len(x.dtype.descr) > 1: + # in case of a dtype with multiple fields: + try: + mask = np.empty(x.shape, dtype=np.dtype('bool, '*len(x.dtype.descr))) + for dd, dm in zip(x.dtype.descr, mask.dtype.descr): + mask[dm[0]] = ~(np.isfinite(x[dd[0]])) + xm = np.ma.array(x, mask=mask, copy=False) + except TypeError: + return x + else: + return x return xm @@ -2254,7 +2264,13 @@ def _g_sig_digits(value, delta): if delta == 0: # delta = 0 may occur when trying to format values over a tiny range; # in that case, replace it by the distance to the closest float. - delta = abs(np.spacing(value)) + if value == 0: + # In this case the closest float is typically 325 digits away. + # However, with both the value and delta at zero, it is more + # reasonable to simply display 0.0 rather than 325 zeros. + delta = 0.1 + else: + delta = abs(np.spacing(value)) # If e.g. value = 45.67 and delta = 0.02, then we want to round to 2 digits # after the decimal point (floor(log10(0.02)) = -2); 45.67 contributes 2 # digits before the decimal point (floor(log10(45.67)) + 1 = 2): the total diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py index 071c93f9f0b3..00c9daf751be 100644 --- a/lib/matplotlib/cm.py +++ b/lib/matplotlib/cm.py @@ -1,5 +1,6 @@ """ -Builtin colormaps, colormap handling utilities, and the `ScalarMappable` mixin. +Builtin colormaps, colormap handling utilities, and the `VectorMappable` and +`ScalarMappable` mixin. .. seealso:: @@ -19,12 +20,15 @@ import numpy as np from numpy import ma +import numbers import matplotlib as mpl 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 +from .typing import ColorMapType _LUTSIZE = mpl.rcParams['image.lut'] @@ -219,8 +223,8 @@ def get_cmap(self, cmap): if cmap is None: return self[mpl.rcParams["image.cmap"]] - # if the user passed in a Colormap, simply return it - if isinstance(cmap, colors.Colormap): + # if the user passed in a valid colormap type, simply return it + if isinstance(cmap, ColorMapType): return cmap if isinstance(cmap, str): _api.check_in_list(sorted(_colormaps), cmap=cmap) @@ -238,6 +242,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 @@ -305,58 +315,88 @@ def _auto_norm_from_scale(scale_cls): return type(norm) -class ScalarMappable: +class Colorizer(): """ - A mixin class to map scalar data to RGBA. - - The ScalarMappable applies data normalization before returning RGBA colors - from the given colormap. + Class that holds (multiple) norm and (one) colormap object. """ + def __init__(self, cmap=None, norm=None): + + self._cmap = None + self._set_cmap(cmap) + + self._id_norm = [None] * self.cmap.n_variates + self._norm = [None] * self.cmap.n_variates + self.norm = norm # The Normalize instance of this Colorizer - def __init__(self, norm=None, cmap=None): - """ - Parameters - ---------- - norm : `.Normalize` (or subclass thereof) or str or None - The normalizing object which scales data, typically into the - interval ``[0, 1]``. - If a `str`, a `.Normalize` subclass is dynamically generated based - on the scale with the corresponding name. - If *None*, *norm* defaults to a *colors.Normalize* object which - initializes its scaling based on the first data processed. - cmap : str or `~matplotlib.colors.Colormap` - The colormap used to map normalized data values to RGBA colors. - """ - self._A = None - self._norm = None # So that the setter knows we're initializing. - self.set_norm(norm) # The Normalize instance of this ScalarMappable. - self.cmap = None # So that the setter knows we're initializing. - self.set_cmap(cmap) # The Colormap instance of this ScalarMappable. - #: The last colorbar associated with this ScalarMappable. May be None. - self.colorbar = None self.callbacks = cbook.CallbackRegistry(signals=["changed"]) + self.colorbar = None - def _scale_norm(self, norm, vmin, vmax): + # @property + # def n_variates(self): + # return self.cmap.n_variates + + @property + def norm(self): + return self._norm + + @norm.setter + def norm(self, norm): + norm = _ensure_multivariate_norm(self.cmap.n_variates, norm) + + changed = False + for i, n in enumerate(norm): + _api.check_isinstance((colors.Normalize, str, None), norm=n) + if n is None: + n = colors.Normalize() + elif isinstance(n, str): + try: + scale_cls = scale._scale_mapping[n] + except KeyError: + raise ValueError( + "Invalid norm str name; the following values are " + f"supported: {', '.join(scale._scale_mapping)}" + ) from None + n = _auto_norm_from_scale(scale_cls)() + + if n is self._norm[i]: + continue + + if self._norm[i] is not None: + # Remove the current callback and connect to the new one + self._norm[i].callbacks.disconnect(self._id_norm[i]) + # emit changed if we are changing norm + # do not emit during initialization (self.norm[i] is None) + changed = True + self._norm[i] = n + self._id_norm[i] = self._norm[i].callbacks.connect('changed', + self.changed) + if changed: + self.changed() + + def _scale_norm(self, norm, vmin, vmax, A): """ Helper for initial scaling. - Used by public functions that create a ScalarMappable and support + Used by public functions that create a VectorMappable and support parameters *vmin*, *vmax* and *norm*. This makes sure that a *norm* will take precedence over *vmin*, *vmax*. Note that this method does not set the norm. """ - if vmin is not None or vmax is not None: - self.set_clim(vmin, vmax) - if isinstance(norm, colors.Normalize): - raise ValueError( - "Passing a Normalize instance simultaneously with " - "vmin/vmax is not supported. Please pass vmin/vmax " - "directly to the norm when creating it.") + norm = _ensure_multivariate_norm(self.cmap.n_variates, norm) + vmin, vmax = _ensure_multivariate_clim(self.cmap.n_variates, vmin, vmax) + for i, _ in enumerate(self._norm): + if vmin[i] is not None or vmax[i] is not None: + if isinstance(norm[i], colors.Normalize): + raise ValueError( + "Passing a Normalize instance simultaneously with " + "vmin/vmax is not supported. Please pass vmin/vmax " + "directly to the norm when creating it.") + self._set_clim_i(i, vmin[i], vmax[i]) # always resolve the autoscaling so we have concrete limits # rather than deferring to draw time. - self.autoscale_None() + self.autoscale_None(A) def to_rgba(self, x, alpha=None, bytes=False, norm=True): """ @@ -364,7 +404,7 @@ def to_rgba(self, x, alpha=None, bytes=False, norm=True): In the normal case, *x* is a 1D or 2D sequence of scalars, and the corresponding `~numpy.ndarray` of RGBA values will be returned, - based on the norm and colormap set for this ScalarMappable. + based on the norm and colormap set for this VectorMappable. There is one special case, for handling images that are already RGB or RGBA, such as might have been read from an image file. @@ -390,97 +430,379 @@ def to_rgba(self, x, alpha=None, bytes=False, norm=True): """ # First check for special case, image input: try: - if x.ndim == 3: - if x.shape[2] == 3: - if alpha is None: - alpha = 1 - if x.dtype == np.uint8: - alpha = np.uint8(alpha * 255) - m, n = x.shape[:2] - xx = np.empty(shape=(m, n, 4), dtype=x.dtype) - xx[:, :, :3] = x - xx[:, :, 3] = alpha - elif x.shape[2] == 4: - xx = x - else: - raise ValueError("Third dimension must be 3 or 4") - if xx.dtype.kind == 'f': - # If any of R, G, B, or A is nan, set to 0 - if np.any(nans := np.isnan(x)): - if x.shape[2] == 4: - xx = xx.copy() - xx[np.any(nans, axis=2), :] = 0 - - if norm and (xx.max() > 1 or xx.min() < 0): - raise ValueError("Floating point image RGB values " - "must be in the 0..1 range.") - if bytes: - xx = (xx * 255).astype(np.uint8) - elif xx.dtype == np.uint8: - if not bytes: - xx = xx.astype(np.float32) / 255 - else: - raise ValueError("Image RGB array must be uint8 or " - "floating point; found %s" % xx.dtype) - # Account for any masked entries in the original array - # If any of R, G, B, or A are masked for an entry, we set alpha to 0 - if np.ma.is_masked(x): - xx[np.any(np.ma.getmaskarray(x), axis=2), 3] = 0 - return xx + if self.cmap.n_variates == 1 and x.ndim == 3: + # looks like imega data, try to process it without cmap + return self._pass_image_data(x, alpha, bytes, norm) except AttributeError: # e.g., x is not an ndarray; so try mapping it pass - # This is the normal case, mapping a scalar array: - x = ma.asarray(x) - if norm: - x = self.norm(x) - rgba = self.cmap(x, alpha=alpha, bytes=bytes) + # This is the normal case, mapping a scalar/vector array: + if self.cmap.n_variates == 1: + x = ma.asarray(x) + if norm: + x = self.normalize(x) + rgba = self.cmap(x, alpha=alpha, bytes=bytes) + else: + if norm: + x = self.normalize(x) + rgba = self.cmap(x, alpha=alpha, bytes=bytes) return rgba - def set_array(self, A): + @staticmethod + def _pass_image_data(x, alpha=None, bytes=False, norm=True): """ - Set the value array from array-like *A*. + Helper function to pass ndarray of shape (...,3) or (..., 4) + through `to_rgba()`, see `to_rgba()` for docstring. + """ + if x.shape[2] == 3: + if alpha is None: + alpha = 1 + if x.dtype == np.uint8: + alpha = np.uint8(alpha * 255) + m, n = x.shape[:2] + xx = np.empty(shape=(m, n, 4), dtype=x.dtype) + xx[:, :, :3] = x + xx[:, :, 3] = alpha + elif x.shape[2] == 4: + xx = x + else: + raise ValueError("Third dimension must be 3 or 4") + if xx.dtype.kind == 'f': + # If any of R, G, B, or A is nan, set to 0 + if np.any(nans := np.isnan(x)): + if x.shape[2] == 4: + xx = xx.copy() + xx[np.any(nans, axis=2), :] = 0 + + if norm and (xx.max() > 1 or xx.min() < 0): + raise ValueError("Floating point image RGB values " + "must be in the 0..1 range.") + if bytes: + xx = (xx * 255).astype(np.uint8) + elif xx.dtype == np.uint8: + if not bytes: + xx = xx.astype(np.float32) / 255 + else: + raise ValueError("Image RGB array must be uint8 or " + "floating point; found %s" % xx.dtype) + # Account for any masked entries in the original array + # If any of R, G, B, or A are masked for an entry, we set alpha to 0 + if np.ma.is_masked(x): + xx[np.any(np.ma.getmaskarray(x), axis=2), 3] = 0 + return xx + + def normalize(self, x): + """ + Normalize the data in x. Parameters ---------- - A : array-like or None - The values that are mapped to colors. + x : np.array or sequence of arrays. Must be compatible with the number + of variates (`Colorizer.n_variates`). + + - If there is a single norm, x may be of any shape. + + - If there are two norms x may be a sequce of length 2, an array with + complex numbers, or an array with a dtype containing two fields + + - If there more than two norms, x may be a sequce of length n, or an array + with a dtype containing n fields. + + Returns + ------- + np.array, or if more than one variate, a list of np.arrays. - The base class `.ScalarMappable` does not make any assumptions on - the dimensionality and shape of the value array *A*. + """ + if self.cmap.n_variates == 1: + return self._norm[0](x) + elif hasattr(x, 'dtype') and len(x.dtype.descr) > 1: + x = _iterable_variates_in_data(x) + elif np.iscomplexobj(x): + # NOTE: when data is passed to plotting methods, i.e. + # imshow(data), and the data is complex, it is converted + # to a dtype with two fields. + # Therefore, complex data should only arrive here if + # the user invokes VectorMappable.to_rgba(data) or + # Colorizer.to_rgba(data) etc. with complex data directly. + x = [x.real, x.imag] + return [norm(xx) for norm, xx in zip(self._norm, x)] + + def autoscale(self, A): + """ + Autoscale the scalar limits on the norm instance using the + current array """ if A is None: - self._A = None - return + raise TypeError('You must first set_array for mappable') + # If the norm's limits are updated self.changed() will be called + # through the callbacks attached to the norm + for n, a in zip(self._norm, _iterable_variates_in_data(A)): + n.autoscale(a) - A = cbook.safe_masked_invalid(A, copy=True) - if not np.can_cast(A.dtype, float, "same_kind"): - raise TypeError(f"Image data of dtype {A.dtype} cannot be " - "converted to float") + def autoscale_None(self, A): + """ + Autoscale the scalar limits on the norm instance using the + current array, changing only limits that are None + """ + if A is None: + raise TypeError('You must first set_array for mappable') + # If the norm's limits are updated self.changed() will be called + # through the callbacks attached to the norm + for n, a in zip(self._norm, _iterable_variates_in_data(A)): + n.autoscale_None(a) - self._A = A - if not self.norm.scaled(): - self.norm.autoscale_None(A) + def _set_cmap(self, cmap): + """ + Set the colormap for luminance data. - def get_array(self): + Parameters + ---------- + cmap : `.Colormap` or str or None """ - Return the array of values, that are mapped to colors. + in_init = self._cmap is None + cmap = _ensure_cmap(cmap) + if not in_init: + if not cmap.n_variates == self.cmap.n_variates: + raise ValueError('The selected colormap does not have' + ' the correct number of variates') + self._cmap = cmap + if not in_init: + self.changed() # Things are not set up properly yet. - The base class `.ScalarMappable` does not make any assumptions on - the dimensionality and shape of the array. + @property + def cmap(self): + return self._cmap + + @cmap.setter + def cmap(self, cmap): + self._set_cmap(cmap) + + def _set_clim_i(self, i, vmin, vmax): """ - return self._A + Set the norm limits for the norm at index i + """ + if vmin is not None and vmax is not None: + # this block exists to avoid calling _changed twice. + vmin = colors._sanitize_extrema(vmin) + vmax = colors._sanitize_extrema(vmax) + if vmin != self._norm[i]._vmin or vmax != self._norm[i]._vmax: + self._norm[i]._vmin = vmin + self._norm[i]._vmax = vmax + self._norm[i]._changed() + else: + if vmin is not None: + self._norm[i].vmin = vmin + if vmax is not None: + self._norm[i].vmax = vmax + + def set_clim(self, vmin=None, vmax=None): + """ + Set the norm limits for image scaling. + + Parameters + ---------- + vmin, vmax : float + The limits. + + .. ACCEPTS: (vmin: float, vmax: float) + """ + # If the norm's limits are updated self.changed() will be called + # through the callbacks attached to the norm + + # we can add logic here to avoid multiple calls to self.changed() if the + # data is multivariate. This would be a minor efficiency improvement, but + # the use case of repeated calls to multivariate set_clim is limited, + # so the performance improvement is not prioritized at this moment. + vmin, vmax = _ensure_multivariate_clim(self.cmap.n_variates, vmin, vmax) + for i, _ in enumerate(self._norm): + self._set_clim_i(i, vmin[i], vmax[i]) + + def get_clim(self): + """ + Return the values (min, max) that are mapped to the colormap limits. + """ + return [n.vmin for n in self._norm], [n.vmax for n in self._norm] + + def changed(self): + """ + Call this whenever the mappable is changed to notify all the + callbackSM listeners to the 'changed' signal. + """ + self.callbacks.process('changed') + self.stale = True + + @property + def vmin(self): + return self.get_clim[0] + + @vmin.setter + def vmin(self, vmin): + if not np.iterable(vmin): + vmin = [vmin] + self.set_clim(vmin=vmin) + + @property + def vmax(self): + return self.get_clim[1] + + @vmax.setter + def vmax(self, vmax): + if not np.iterable(vmax): + vmax = [vmax] + self.set_clim(vmax=vmax) + + @property + def clip(self): + return [n.clip for n in self._norm] + + @clip.setter + def clip(self, clip): + if not np.iterable(clip): + clip = [clip]*len(self._norm) + for n, c in zip(self._norm, clip): + n.clip = c + + def __getitem__(self, index): + """ + Returns a Colorizer object containing the norm and colormap for one axis + """ + if self.cmap.n_variates > 1: + if index >= 0 and index < self.cmap.n_variates: + part = Colorizer(cmap=self._cmap[index], norm=self._norm[index]) + part._super_colorizer = self + part._super_colorizer_index = index + part._id_parent_cmap = id(self.cmap) + part._id_parent_norm = id(self._norm[index]) + self.callbacks.connect('changed', part._check_update_super_colorizer) + return part + elif self.cmap.n_variates == 1 and index == 0: + return self + raise ValueError(f'Only 0..{self.cmap.n_variates-1} are valid indexes' + ' for this Colorizer object.') + + def _check_update_super_colorizer(self): + """ + If this `Colorizer` object was created by __getitem__ it is a + one-dimensional component of another `Colorizer`. + In this case, `self._super_colorizer` is the Colorizer this was generated from. + + This function propagetes changes from the `self._super_colorizer` to `self`. + """ + if hasattr(self, '_super_colorizer'): + # _super_colorizer, the colorizer this is a component of + if id(self._super_colorizer.cmap) != self._id_parent_cmap: + self.cmap = self._super_colorizer.cmap[self._super_colorizer_index] + super_colorizer_norm =\ + self._super_colorizer._norm[self._super_colorizer_index] + if id(super_colorizer_norm) != self._id_parent_norm: + self.norm = [super_colorizer_norm] + + def _format_cursor_data(self, data): + """ + Return a string representation of *data*. + + Uses the colorbar's formatter to format the data. + """ + if (np.ndim(data) == 0 or len(data) == self.cmap.n_variates): + if self.cmap.n_variates == 1: + # This if test is equivalent to `isinstance(self.cmap, Colormap)` + data = [data] + num_colors = [self.cmap.N] + else: + if isinstance(self.cmap, colors.BivarColormap): + num_colors = [self.cmap.N, self.cmap.M] + else: # i.e. a MultivarColormap object + num_colors = [component.N for component in self.cmap] + + out_str = '[' + for nn, dd, nc in zip(self._norm, data, num_colors): + if np.ma.getmask(dd): + out_str += ", " + else: + # Figure out a reasonable amount of significant digits + normed = nn(dd) + if np.isfinite(normed): + if isinstance(nn, colors.BoundaryNorm): + # not an invertible normalization mapping + cur_idx = np.argmin(np.abs(nn.boundaries - dd)) + neigh_idx = max(0, cur_idx - 1) + # use max diff to prevent delta == 0 + delta = np.diff( + nn.boundaries[neigh_idx:cur_idx + 2] + ).max() + else: + # Midpoints of neighboring color intervals. + neighbors = nn.inverse( + (int(normed * nc) + np.array([0, 1])) / nc) + delta = abs(neighbors - dd).max() + g_sig_digits = cbook._g_sig_digits(dd, delta) + else: + g_sig_digits = 3 # Consistent with default below. + out_str += f"{dd:-#.{g_sig_digits}g}, " + return out_str[:-2] + ']' + else: + # This point is reacehd if the number of colormaps does not match the length + # of data. This happens in the as-of-yet hypothetical case that the norm + # converts from one data field to two. + raise ValueError + try: + data[0] + except (TypeError, IndexError): + data = [data] + data_str = ', '.join(f'{item:0.3g}' for item in data + if isinstance(item, numbers.Number)) + return "[" + data_str + "]" + + +class ColorizerShim: + + def _scale_norm(self, norm, vmin, vmax): + self.colorizer._scale_norm(norm, vmin, vmax, self._A) + + def to_rgba(self, x, alpha=None, bytes=False, norm=True): + """ + Return a normalized RGBA array corresponding to *x*. + + In the normal case, *x* is a 1D or 2D sequence of scalars, and + the corresponding `~numpy.ndarray` of RGBA values will be returned, + based on the norm and colormap set for this VectorMappable. + + There is one special case, for handling images that are already + RGB or RGBA, such as might have been read from an image file. + If *x* is an `~numpy.ndarray` with 3 dimensions, + and the last dimension is either 3 or 4, then it will be + treated as an RGB or RGBA array, and no mapping will be done. + The array can be `~numpy.uint8`, or it can be floats with + values in the 0-1 range; otherwise a ValueError will be raised. + Any NaNs or masked elements will be set to 0 alpha. + If the last dimension is 3, the *alpha* kwarg (defaulting to 1) + will be used to fill in the transparency. If the last dimension + is 4, the *alpha* kwarg is ignored; it does not + replace the preexisting alpha. A ValueError will be raised + if the third dimension is other than 3 or 4. + + In either case, if *bytes* is *False* (default), the RGBA + array will be floats in the 0-1 range; if it is *True*, + the returned RGBA array will be `~numpy.uint8` in the 0 to 255 range. + + If norm is False, no normalization of the input data is + performed, and it is assumed to be in the range (0-1). + + """ + return self.colorizer.to_rgba(x, alpha=alpha, bytes=bytes, norm=norm) def get_cmap(self): """Return the `.Colormap` instance.""" - return self.cmap + return self.colorizer.cmap def get_clim(self): """ Return the values (min, max) that are mapped to the colormap limits. """ - return self.norm.vmin, self.norm.vmax + if self.colorizer.cmap.n_variates == 1: + return self.colorizer._norm[0].vmin, self.colorizer._norm[0].vmax + return self.colorizer.get_clim() def set_clim(self, vmin=None, vmax=None): """ @@ -491,22 +813,19 @@ def set_clim(self, vmin=None, vmax=None): vmin, vmax : float The limits. - The limits may also be passed as a tuple (*vmin*, *vmax*) as a - single positional argument. + For scalar data, the limits may also be passed as a + tuple (*vmin*, *vmax*) as a single positional argument. .. ACCEPTS: (vmin: float, vmax: float) """ # If the norm's limits are updated self.changed() will be called # through the callbacks attached to the norm - if vmax is None: + if self.cmap.n_variates == 1: try: vmin, vmax = vmin except (TypeError, ValueError): pass - if vmin is not None: - self.norm.vmin = colors._sanitize_extrema(vmin) - if vmax is not None: - self.norm.vmax = colors._sanitize_extrema(vmax) + self.colorizer.set_clim(vmin, vmax) def get_alpha(self): """ @@ -518,6 +837,14 @@ def get_alpha(self): # This method is intended to be overridden by Artist sub-classes return 1. + @property + def cmap(self): + return self.colorizer.cmap + + @cmap.setter + def cmap(self, cmap): + self.colorizer.cmap = cmap + def set_cmap(self, cmap): """ Set the colormap for luminance data. @@ -526,44 +853,17 @@ def set_cmap(self, cmap): ---------- cmap : `.Colormap` or str or None """ - in_init = self.cmap is None - - self.cmap = _ensure_cmap(cmap) - if not in_init: - self.changed() # Things are not set up properly yet. + self.colorizer.cmap = cmap @property def norm(self): - return self._norm + if self.cmap.n_variates == 1: + return self.colorizer.norm[0] + return self.colorizer.norm @norm.setter def norm(self, norm): - _api.check_isinstance((colors.Normalize, str, None), norm=norm) - if norm is None: - norm = colors.Normalize() - elif isinstance(norm, str): - try: - scale_cls = scale._scale_mapping[norm] - except KeyError: - raise ValueError( - "Invalid norm str name; the following values are " - f"supported: {', '.join(scale._scale_mapping)}" - ) from None - norm = _auto_norm_from_scale(scale_cls)() - - if norm is self.norm: - # We aren't updating anything - return - - in_init = self.norm is None - # Remove the current callback and connect to the new one - if not in_init: - self.norm.callbacks.disconnect(self._id_norm) - self._norm = norm - self._id_norm = self.norm.callbacks.connect('changed', - self.changed) - if not in_init: - self.changed() + self.colorizer.norm = norm def set_norm(self, norm): """ @@ -586,22 +886,121 @@ def autoscale(self): Autoscale the scalar limits on the norm instance using the current array """ - if self._A is None: - raise TypeError('You must first set_array for mappable') - # If the norm's limits are updated self.changed() will be called - # through the callbacks attached to the norm - self.norm.autoscale(self._A) + self.colorizer.autoscale(self._A) def autoscale_None(self): """ Autoscale the scalar limits on the norm instance using the current array, changing only limits that are None """ - if self._A is None: - raise TypeError('You must first set_array for mappable') - # If the norm's limits are updated self.changed() will be called - # through the callbacks attached to the norm - self.norm.autoscale_None(self._A) + self.colorizer.autoscale_None(self._A) + + def _parse_multivariate_data(self, data): + """ + Parse data to a dtype with self.cmap.n_variates. + + Input data of shape (n_variates, n, m) is converted to an array of shape + (n, m) with data type np.dtype(f'{data.dtype}, ' * n_variates) + + Complex data is returned as a view with dtype np.dtype('float64, float64') + or np.dtype('float32, float32') + + If n_variates is 1 and data is not of type np.ndarray (i.e. PIL.Image), + the data is returned unchanged. + + If data is None, the function returns None + + Parameters + ---------- + data : np.ndarray, PIL.Image or None + + Returns + ------- + np.ndarray, PIL.Image or None + """ + return _ensure_multivariate_data(self.cmap.n_variates, data) + + @property + def colorbar(self): + return self.colorizer.colorbar + + @colorbar.setter + def colorbar(self, colorbar): + self.colorizer.colorbar = colorbar + + +class ScalarMappable(ColorizerShim): + """ + A mixin class to map one or multiple sets of scalar data to RGBA. + + The VectorMappable applies data normalization before returning RGBA colors + from the given `~matplotlib.colors.Colormap`, `~matplotlib.colors.BivarColormap`, + or `~matplotlib.colors.MultivarColormap`. + """ + + def __init__(self, norm=None, cmap=None): + """ + Parameters + ---------- + norm : `.Normalize` (or subclass thereof) or str or None + The normalizing object which scales data, typically into the + interval ``[0, 1]``. + If a `str`, a `.Normalize` subclass is dynamically generated based + on the scale with the corresponding name. + If *None*, *norm* defaults to a *colors.Normalize* object which + initializes its scaling based on the first data processed. + cmap : str or `~matplotlib.colors.Colormap` + The colormap used to map normalized data values to RGBA colors. + """ + self._A = None + if isinstance(norm, Colorizer): + self.colorizer = norm + else: + self.colorizer = Colorizer(cmap, norm) + + self._id_colorizer = self.colorizer.callbacks.connect('changed', self.changed) + self.callbacks = cbook.CallbackRegistry(signals=["changed"]) + + self.get_alpha = lambda: 1 + + def set_array(self, A): + """ + Set the value array from array-like *A*. + + Parameters + ---------- + A : array-like or None + The values that are mapped to colors. + + The base class `.VectorMappable` does not make any assumptions on + the dimensionality and shape of the value array *A*. + """ + if A is None: + self._A = None + return + A = _ensure_multivariate_data(self.cmap.n_variates, A) + + A = cbook.safe_masked_invalid(A, copy=True) + if not np.can_cast(A.dtype, float, "same_kind"): + if A.dtype.fields is None: + raise TypeError(f"Image data of dtype {A.dtype} cannot be " + f"converted to float") + else: + for key in A.dtype.fields: + if not np.can_cast(A[key].dtype, float, "same_kind"): + raise TypeError(f"Image data of dtype {A.dtype} cannot be " + f"converted to a sequence of floats") + self._A = A + self.colorizer.autoscale_None(A) + + def get_array(self): + """ + Return the array of values, that are mapped to colors. + + The base class `.VectorMappable` does not make any assumptions on + the dimensionality and shape of the array. + """ + return self._A def changed(self): """ @@ -642,30 +1041,274 @@ def changed(self): ) -def _ensure_cmap(cmap): +def _ensure_cmap(cmap, accept_multivariate=True, colorizer=None): """ - Ensure that we have a `.Colormap` object. + For internal use to preserve type stability of errors, and + to ensure that we have a `~matplotlib.colors.Colormap`, + `~matplotlib.colors.MultivarColormap` or + `~matplotlib.colors.BivarColormap` object. + This is necessary in order to know the number of variates. - For internal use to preserve type stability of errors. + objects, strings in mpl.colormaps, or None. Parameters ---------- cmap : None, str, Colormap - - if a `Colormap`, return it - - if a string, look it up in mpl.colormaps + - if a `~matplotlib.colors.Colormap`, + `~matplotlib.colors.MultivarColormap` or + `~matplotlib.colors.BivarColormap`, + return it + - if a string, look it up in three corresponding databases + when not found: raise an error based on the expected shape - if None, look up the default color map in mpl.colormaps + accept_multivariate : bool, default True + + - if False, accept only Colormap, string in mpl.colormaps or None + Returns ------- - Colormap - + Colormap, MultivarColormap or BivarColormap """ - if isinstance(cmap, colors.Colormap): + if isinstance(colorizer, Colorizer): + if cmap is not None: + raise ValueError('A Colorizer object cannot be passed' + ' simultaneously with a cmap.') + return colorizer.cmap + if not accept_multivariate: + if isinstance(cmap, colors.Colormap): + return cmap + cmap_name = cmap if cmap is not None else mpl.rcParams["image.cmap"] + # use check_in_list to ensure type stability of the exception raised by + # the internal usage of this (ValueError vs KeyError) + if cmap_name not in _colormaps: + _api.check_in_list(sorted(_colormaps), cmap=cmap_name) + + if isinstance(cmap, (colors.Colormap, + colors.BivarColormap, + colors.MultivarColormap)): return cmap + cmap_name = cmap if cmap is not None else mpl.rcParams["image.cmap"] - # use check_in_list to ensure type stability of the exception raised by - # the internal usage of this (ValueError vs KeyError) - if cmap_name not in _colormaps: - _api.check_in_list(sorted(_colormaps), cmap=cmap_name) - return mpl.colormaps[cmap_name] + if cmap_name in mpl.colormaps: + return mpl.colormaps[cmap_name] + if cmap_name in mpl.multivar_colormaps: + return mpl.multivar_colormaps[cmap_name] + if cmap_name in mpl.bivar_colormaps: + return mpl.bivar_colormaps[cmap_name] + + # this error message is a variant of _api.check_in_list but gives + # additional hints as to how to access multivariate colormaps + + msg = f"{cmap!r} is not a valid value for cmap" + msg += "; supported values for scalar colormaps are " + msg += f"{', '.join(map(repr, sorted(mpl.colormaps)))}\n" + msg += "See matplotlib.bivar_colormaps() and" + msg += " matplotlib.multivar_colormaps() for" + msg += " bivariate and multivariate colormaps." + + raise ValueError(msg) + + +def _iterable_variates_in_data(data): + """ + Provides an iterable over the variates contained in the data. + + The returned list has length 1 if 'data' contains scalar data. + If 'data' has a dtype type with multiple fields, the returned list + has a length equal to the number of field in the data type. + + Often used with VectorMappable._norm, which similarly has a list + of norms equal to the number of variates + + Parameters + ---------- + data : np.ndarray + + + Returns + ------- + list of np.ndarray + + """ + if isinstance(data, np.ndarray) and data.dtype.fields is not None: + return [data[descriptor[0]] for descriptor in data.dtype.descr] + else: + return [data] + + +def _ensure_multivariate_norm(n_variates, norm): + """ + Ensure that the nor + m has the correct number of elements. + If n_variates > 1: A single argument for norm will be repeated n + times in the output. + + Parameters + ---------- + n_variates : int + - number of variates in the data + norm : `.Normalize` (or subclass thereof) or str or None or iterable + + - If iterable, the length must be equal to n_variates + + Returns + ------- + if n_variates == 1: + norm returned unchanged + if n_variates > 1: + an iterable of length n_variates + """ + if isinstance(norm, Colorizer): + # we do not need to test for consistency here + # (norm.n_variates == n_variates) + # because that is done in _ensure_cmap() + # which should always be called before + # _ensure_multivariate_params() + # which then calls + # _ensure_multivariate_norm() + return norm + + if isinstance(norm, str) or norm is None: + norm = [norm for i in range(n_variates)] + elif not np.iterable(norm): + if n_variates == 1: + norm = [norm] + else: + raise ValueError( + 'When using a colormap with more than one variate,' + ' norm must be None, a valid string, a sequence of strings,' + ' or a sequence of mpl.Colors.Normalize objects.') + else: + if len(norm) != n_variates: + raise ValueError( + f'Unable to map the input for norm ({norm}) to {n_variates} ' + f'variables.') + return norm + + +def _ensure_multivariate_data(n_variates, data): + """ + Ensure that the data has dtype with n_variates. + + Input data of shape (n_variates, n, m) is converted to an array of shape + (n, m) with data type np.dtype(f'{data.dtype}, ' * n_variates) + + Complex data is returned as a view with dtype np.dtype('float64, float64') + or np.dtype('float32, float32') + + If n_variates is 1 and data is not of type np.ndarray (i.e. PIL.Image), + the data is returned unchanged. + + If data is None, the function returns None + + Parameters + ---------- + n_variates : int + - number of variates in the data + data : np.ndarray, PIL.Image or None + + Returns + ------- + np.ndarray, PIL.Image or None + + """ + + if isinstance(data, np.ndarray): + if len(data.dtype.descr) == n_variates: + return data + elif data.dtype in [np.complex64, np.complex128]: + if data.dtype == np.complex128: + dt = np.dtype('float64, float64') + else: + dt = np.dtype('float32, float32') + reconstructed = np.ma.frombuffer(data.data, dtype=dt).reshape(data.shape) + if np.ma.is_masked(data): + for descriptor in dt.descr: + reconstructed[descriptor[0]][data.mask] = np.ma.masked + return reconstructed + + if n_variates > 1 and len(data) == n_variates: + # convert data from shape (n_variates, n, m) + # to (n,m) with a new dtype + data = [np.ma.array(part, copy=False) for part in data] + dt = np.dtype(', '.join([f'{part.dtype}' for part in data])) + fields = [descriptor[0] for descriptor in dt.descr] + reconstructed = np.ma.empty(data[0].shape, dtype=dt) + for i, f in enumerate(fields): + if data[i].shape != reconstructed.shape: + raise ValueError("For multivariate data all variates must have same " + f"shape, not {data[0].shape} and {data[i].shape}") + reconstructed[f] = data[i] + if np.ma.is_masked(data[i]): + reconstructed[f][data[i].mask] = np.ma.masked + return reconstructed + + if data is None: + return data + + if n_variates == 1: + # PIL.Image also gets passed here + return data + elif n_variates == 2: + raise ValueError("Invalid data entry for mutlivariate data. The data" + " must contain complex numbers, or have a first dimension 2," + " or be of a dtype with 2 fields") + else: + raise ValueError("Invalid data entry for mutlivariate data. The shape" + f" of the data must have a first dimension {n_variates}" + f" or be of a dtype with {n_variates} fields") + + +def _ensure_multivariate_clim(n_variates, vmin=None, vmax=None): + """ + Ensure that vmin and vmax have the correct number of elements. + If n_variates > 1: A single argument for vmin/vmax will be repeated n + times in the output. + + Parameters + ---------- + n_variates : int + - number of variates in the data + vmin and vmax : float or iterable + - if iterable, the length must be n_variates + + Returns + ------- + vmin, vmax as iterables of length n_variates + """ + if not np.iterable(vmin): + vmin = [vmin for i in range(n_variates)] + else: + if len(vmin) != n_variates: + raise ValueError( + f'Unable to map the input for vmin ({vmin}) to {n_variates} ' + f'variables.') + + if not np.iterable(vmax): + vmax = [vmax for i in range(n_variates)] + else: + if len(vmax) != n_variates: + raise ValueError( + f'Unable to map the input for vmax ({vmax}) to {n_variates} ' + f'variables.') + vmax = vmax + + return vmin, vmax + + +def _ensure_multivariate_params(n_variates, data, norm, vmin, vmax): + """ + Ensure that the data, norm, vmin and vmax have the correct number of elements. + If n_variates == 1, the norm, vmin and vmax are returned as lists of length 1. + If n_variates > 1, the length of each input is checked for consistency and + single arguments are repeated as necessary to form lists of length n_variates. + Scalar data is returned unchanged, but multivariate data is restructured to a dtype + with n_variate fields. + See the component functions for details. + """ + norm = _ensure_multivariate_norm(n_variates, norm) + vmin, vmax = _ensure_multivariate_clim(n_variates, vmin, vmax) + data = _ensure_multivariate_data(n_variates, data) + return data, norm, vmin, vmax diff --git a/lib/matplotlib/cm.pyi b/lib/matplotlib/cm.pyi index be8f10b39cb6..8c7341bd5457 100644 --- a/lib/matplotlib/cm.pyi +++ b/lib/matplotlib/cm.pyi @@ -1,34 +1,38 @@ -from collections.abc import Iterator, Mapping +from collections.abc import Iterator, Mapping, Sequence from matplotlib import cbook, colors from matplotlib.colorbar import Colorbar import numpy as np from numpy.typing import ArrayLike +from typing import Union +from .typing import ColorMapType -class ColormapRegistry(Mapping[str, colors.Colormap]): - def __init__(self, cmaps: Mapping[str, colors.Colormap]) -> None: ... - def __getitem__(self, item: str) -> colors.Colormap: ... +class ColormapRegistry(Mapping[str, ColorMapType]): + def __init__(self, cmaps: Mapping[str, ColorMapType]) -> None: ... + def __getitem__(self, item: str) -> ColorMapType: ... def __iter__(self) -> Iterator[str]: ... def __len__(self) -> int: ... def __call__(self) -> list[str]: ... def register( - self, cmap: colors.Colormap, *, name: str | None = ..., force: bool = ... + self, cmap: ColorMapType, *, name: str | None = ..., force: bool = ... ) -> None: ... def unregister(self, name: str) -> None: ... - def get_cmap(self, cmap: str | colors.Colormap) -> colors.Colormap: ... + def get_cmap(self, cmap: str | ColorMapType) -> ColorMapType: ... _colormaps: ColormapRegistry = ... +_multivar_colormaps: ColormapRegistry = ... +_bivar_colormaps: ColormapRegistry = ... def get_cmap(name: str | colors.Colormap | None = ..., lut: int | None = ...) -> colors.Colormap: ... -class ScalarMappable: - cmap: colors.Colormap | None +class VectorMappable: + cmap: ColorMapType | None colorbar: Colorbar | None callbacks: cbook.CallbackRegistry def __init__( self, - norm: colors.Normalize | None = ..., - cmap: str | colors.Colormap | None = ..., + norm: str | colors.Normalize | None | Sequence[str | colors.Normalize | None]= ..., + cmap: str | ColorMapType | None = ..., ) -> None: ... def to_rgba( self, @@ -39,16 +43,24 @@ class ScalarMappable: ) -> np.ndarray: ... def set_array(self, A: ArrayLike | None) -> None: ... def get_array(self) -> np.ndarray | None: ... - def get_cmap(self) -> colors.Colormap: ... + def get_cmap(self) -> ColorMapType: ... def get_clim(self) -> tuple[float, float]: ... - def set_clim(self, vmin: float | tuple[float, float] | None = ..., vmax: float | None = ...) -> None: ... + def set_clim(self, vmin: float | tuple[float, float] | None | Sequence[float | None] = ..., vmax: float | None | Sequence[float | None] = ...) -> None: ... def get_alpha(self) -> float | None: ... - def set_cmap(self, cmap: str | colors.Colormap) -> None: ... + def set_cmap(self, cmap: str | ColorMapType) -> None: ... @property - def norm(self) -> colors.Normalize: ... + def norm(self) -> colors.Normalize | Sequence[colors.Normalize]: ... @norm.setter - def norm(self, norm: colors.Normalize | str | None) -> None: ... - def set_norm(self, norm: colors.Normalize | str | None) -> None: ... + def norm(self, norm: colors.Normalize | str | None | Sequence[colors.Normalize | str | None]) -> None: ... + def set_norm(self, norm: colors.Normalize | str | None | Sequence[colors.Normalize | str | None]) -> None: ... def autoscale(self) -> None: ... def autoscale_None(self) -> None: ... def changed(self) -> None: ... + +class ScalarMappable(VectorMappable): + def __init__( + self, + norm: colors.Normalize | None = ..., + cmap: str | colors.Colormap | None = ..., + ) -> None: ... + def set_cmap(self, cmap: str | colors.Colormap) -> None: ... # type: ignore[override] diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 00146cec3cb0..ece923a25dd7 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -32,7 +32,7 @@ "linewidth": ["linewidths", "lw"], "offset_transform": ["transOffset"], }) -class Collection(artist.Artist, cm.ScalarMappable): +class Collection(artist.ColorizingArtist, cm.ColorizerShim): r""" Base class for Collections. Must be subclassed to be usable. @@ -58,7 +58,7 @@ class Collection(artist.Artist, cm.ScalarMappable): Each Collection can optionally be used as its own `.ScalarMappable` by passing the *norm* and *cmap* parameters to its constructor. If the Collection's `.ScalarMappable` matrix ``_A`` has been set (via a call - to `.Collection.set_array`), then at draw time this internal scalar + to `.Collection.set_array`), then at draw time this internal vector mappable will be used to set the ``facecolors`` and ``edgecolors``, ignoring those that were manually passed in. """ @@ -155,8 +155,9 @@ def __init__(self, *, Remaining keyword arguments will be used to set properties as ``Collection.set_{key}(val)`` for each key-value pair in *kwargs*. """ - artist.Artist.__init__(self) - cm.ScalarMappable.__init__(self, norm, cmap) + artist.ColorizingArtist.__init__(self, norm, cmap) + # artist.Artist.__init__(self) + # cm.ScalarMappable.__init__(self, norm, cmap) # list of un-scaled dash patterns # this is needed scaling the dash pattern by linewidth self._us_linestyles = [(0, None)] @@ -917,7 +918,7 @@ def update_scalarmappable(self): 'array is dropped.') # pcolormesh, scatter, maybe others flatten their _A self._alpha = self._alpha.reshape(self._A.shape) - self._mapped_colors = self.to_rgba(self._A, self._alpha) + self._mapped_colors = self.colorizer.to_rgba(self._A, self._alpha) if self._face_is_mapped: self._facecolors = self._mapped_colors @@ -953,8 +954,7 @@ def update_from(self, other): # update_from for scalarmappable self._A = other._A - self.norm = other.norm - self.cmap = other.cmap + self.colorizer = other.colorizer self.stale = True @@ -1157,7 +1157,8 @@ def legend_elements(self, prop="colors", num="auto", for val, lab in zip(values, label_values): if prop == "colors": - color = self.cmap(self.norm(val)) + # color = self.colorizer.cmap(self.colorizer.norm(val)) + color = self.colorizer.to_rgba(val) elif prop == "sizes": size = np.sqrt(val) if np.isclose(size, 0.0): @@ -2009,6 +2010,7 @@ def set_array(self, A): h, w = height - 1, width - 1 else: h, w = height, width + A = cm._ensure_multivariate_data(self.colorizer.cmap.n_variates, A) ok_shapes = [(h, w, 3), (h, w, 4), (h, w), (h * w,)] if A is not None: shape = np.shape(A) @@ -2269,7 +2271,11 @@ def _get_unmasked_polys(self): arr = self.get_array() if arr is not None: arr = np.ma.getmaskarray(arr) - if arr.ndim == 3: + if self.colorizer.cmap.n_variates > 1: + # multivar case + for a in cm._iterable_variates_in_data(arr): + mask |= np.any(a, axis=0) + elif arr.ndim == 3: # RGB(A) case mask |= np.any(arr, axis=-1) elif arr.ndim == 2: diff --git a/lib/matplotlib/collections.pyi b/lib/matplotlib/collections.pyi index e4c46229517f..0af7666cec37 100644 --- a/lib/matplotlib/collections.pyi +++ b/lib/matplotlib/collections.pyi @@ -7,7 +7,7 @@ from numpy.typing import ArrayLike, NDArray from . import artist, cm, transforms from .backend_bases import MouseEvent from .artist import Artist -from .colors import Normalize, Colormap +from .colors import Normalize, Colormap, BivarColormap, MultivarColormap from .lines import Line2D from .path import Path from .patches import Patch @@ -15,7 +15,7 @@ from .ticker import Locator, Formatter from .tri import Triangulation from .typing import ColorType, LineStyleType, CapStyleType, JoinStyleType -class Collection(artist.Artist, cm.ScalarMappable): +class Collection(artist.Artist, cm.VectorMappable): def __init__( self, *, @@ -29,7 +29,7 @@ class Collection(artist.Artist, cm.ScalarMappable): offsets: tuple[float, float] | Sequence[tuple[float, float]] | None = ..., offset_transform: transforms.Transform | None = ..., norm: Normalize | None = ..., - cmap: Colormap | None = ..., + cmap: Colormap | BivarColormap | MultivarColormap | None = ..., pickradius: float = ..., hatch: str | None = ..., urls: Sequence[str] | None = ..., diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 296f072a4af1..cda9d7205722 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -291,16 +291,21 @@ def __init__(self, ax, mappable=None, *, cmap=None, location=None, ): + filled = True if mappable is None: - mappable = cm.ScalarMappable(norm=norm, cmap=cmap) + self.colorizer = cm.Colorizer(norm=norm, cmap=cmap) + self.mappable = None + elif isinstance(mappable, cm.Colorizer): + self.colorizer = mappable + self.mappable = None + else: + self.colorizer = mappable.colorizer + self.mappable = mappable - self.mappable = mappable - cmap = mappable.cmap - norm = mappable.norm + self.norm_id = id(self.norm) - filled = True - if isinstance(mappable, contour.ContourSet): - cs = mappable + if isinstance(self.mappable, contour.ContourSet): + cs = self.mappable alpha = cs.get_alpha() boundaries = cs._levels values = cs.cvalues @@ -308,11 +313,12 @@ def __init__(self, ax, mappable=None, *, cmap=None, filled = cs.filled if ticks is None: ticks = ticker.FixedLocator(cs.levels, nbins=10) - elif isinstance(mappable, martist.Artist): + elif isinstance(self.mappable, martist.Artist): alpha = mappable.get_alpha() - mappable.colorbar = self - mappable.colorbar_cid = mappable.callbacks.connect( + self.colorizer.colorbar = self + + self.colorizer.colorbar_cid = self.colorizer.callbacks.connect( 'changed', self.update_normal) location_orientation = _get_orientation_from_location(location) @@ -336,18 +342,16 @@ def __init__(self, ax, mappable=None, *, cmap=None, self.ax._axes_locator = _ColorbarAxesLocator(self) if extend is None: - if (not isinstance(mappable, contour.ContourSet) - and getattr(cmap, 'colorbar_extend', False) is not False): - extend = cmap.colorbar_extend - elif hasattr(norm, 'extend'): - extend = norm.extend + if (not isinstance(self.mappable, contour.ContourSet) + and getattr(self.cmap, 'colorbar_extend', False) is not False): + extend = self.cmap.colorbar_extend + elif hasattr(self.norm, 'extend'): + extend = self.norm.extend else: extend = 'neither' self.alpha = None # Call set_alpha to handle array-like alphas properly self.set_alpha(alpha) - self.cmap = cmap - self.norm = norm self.values = values self.boundaries = boundaries self.extend = extend @@ -406,8 +410,8 @@ def __init__(self, ax, mappable=None, *, cmap=None, self._formatter = format # Assume it is a Formatter or None self._draw_all() - if isinstance(mappable, contour.ContourSet) and not mappable.filled: - self.add_lines(mappable) + if isinstance(self.mappable, contour.ContourSet) and not self.mappable.filled: + self.add_lines(self.mappable) # Link the Axes and Colorbar for interactive use self.ax._colorbar = self @@ -429,6 +433,19 @@ def __init__(self, ax, mappable=None, *, cmap=None, self._extend_cid2 = self.ax.callbacks.connect( "ylim_changed", self._do_extends) + @property + def cmap(self): + return self.colorizer.cmap + + @cmap.setter + def cmap(self, cmap): + if not self.colorizer.cmap == cmap: + self.colorizer.cmap = cmap + + @property + def norm(self): + return self.colorizer.norm[0] + @property def locator(self): """Major tick `.Locator` for the colorbar.""" @@ -477,7 +494,7 @@ def _cbar_cla(self): del self.ax.cla self.ax.cla() - def update_normal(self, mappable): + def update_normal(self, mappable=None): """ Update solid patches, lines, etc. @@ -489,17 +506,30 @@ def update_normal(self, mappable): they will need to be customized again. However, if the norm only changes values of *vmin*, *vmax* or *cmap* then the old formatter and locator will be preserved. + + Note: An update on a ColorableArtist calls cb.update_normal(), while + an update on a ScalarMappable calls cb.update_normal(self). """ - _log.debug('colorbar update normal %r %r', mappable.norm, self.norm) - self.mappable = mappable - self.set_alpha(mappable.get_alpha()) - self.cmap = mappable.cmap - if mappable.norm != self.norm: - self.norm = mappable.norm + if mappable is None: + mappable = self.mappable + else: + self.mappable = mappable + + if self.mappable: + self.colorizer = self.mappable.colorizer + + _log.debug('colorbar update normal %r %r', self.colorizer.norm, self.norm) # + # The above debug log should be changed, it will now always give the same + # norm twice. However, at this point the old norm should be out of scope + # and we only have the new, and the id of the old in terms of debugging + if self.mappable: + self.set_alpha(mappable.get_alpha()) + if not self.norm_id == id(self.norm): self._reset_locator_formatter_scale() + self.norm_id = id(self.norm) self._draw_all() - if isinstance(self.mappable, contour.ContourSet): + if self.mappable and isinstance(self.mappable, contour.ContourSet): CS = self.mappable if not CS.filled: self.add_lines(CS) @@ -572,7 +602,7 @@ def _add_solids(self, X, Y, C): self._add_solids_patches(X, Y, C, mappable) else: self.solids = self.ax.pcolormesh( - X, Y, C, cmap=self.cmap, norm=self.norm, alpha=self.alpha, + X, Y, C, norm=self.colorizer, alpha=self.alpha, edgecolors='none', shading='flat') if not self.drawedges: if len(self._y) >= self.n_rasterize: @@ -753,7 +783,7 @@ def add_lines(self, *args, **kwargs): # TODO: Make colorbar lines auto-follow changes in contour lines. return self.add_lines( cs.levels, - cs.to_rgba(cs.cvalues, cs.alpha), + cs.colorizer.to_rgba(cs.cvalues, cs.alpha), cs.get_linewidths(), erase=erase) else: @@ -1020,9 +1050,9 @@ def remove(self): self.ax.remove() - self.mappable.callbacks.disconnect(self.mappable.colorbar_cid) - self.mappable.colorbar = None - self.mappable.colorbar_cid = None + self.colorizer.callbacks.disconnect(self.colorizer.colorbar_cid) + self.colorizer.colorbar = None + self.colorizer.colorbar_cid = None # Remove the extension callbacks self.ax.callbacks.disconnect(self._extend_cid1) self.ax.callbacks.disconnect(self._extend_cid2) @@ -1078,8 +1108,8 @@ def _process_values(self): b = np.hstack((b, b[-1] + 1)) # transform from 0-1 to vmin-vmax: - if self.mappable.get_array() is not None: - self.mappable.autoscale_None() + if self.mappable and self.mappable.get_array() is not None: + self.colorizer.autoscale_None(self.mappable.get_array()) if not self.norm.scaled(): # If we still aren't scaled after autoscaling, use 0, 1 as default self.norm.vmin = 0 diff --git a/lib/matplotlib/colorbar.pyi b/lib/matplotlib/colorbar.pyi index f71c5759fc55..13d970f21c3a 100644 --- a/lib/matplotlib/colorbar.pyi +++ b/lib/matplotlib/colorbar.pyi @@ -21,7 +21,7 @@ class _ColorbarSpine(mspines.Spines): class Colorbar: n_rasterize: int - mappable: cm.ScalarMappable + mappable: cm.VectorMappable ax: Axes alpha: float | None cmap: colors.Colormap @@ -43,7 +43,7 @@ class Colorbar: def __init__( self, ax: Axes, - mappable: cm.ScalarMappable | None = ..., + mappable: cm.VectorMappable | None = ..., *, cmap: str | colors.Colormap | None = ..., norm: colors.Normalize | None = ..., @@ -78,7 +78,7 @@ class Colorbar: def minorformatter(self) -> Formatter: ... @minorformatter.setter def minorformatter(self, fmt: Formatter) -> None: ... - def update_normal(self, mappable: cm.ScalarMappable) -> None: ... + def update_normal(self, mappable: cm.VectorMappable) -> None: ... @overload def add_lines(self, CS: contour.ContourSet, erase: bool = ...) -> None: ... @overload diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 177557b371a6..93cc27d0d9e5 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(): @@ -681,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.ScalarMappable` + `matplotlib.colors.Normalize`. Subclasses of `matplotlib.cm.VectorMappable` make heavy use of this ``data -> normalize -> map-to-color`` processing chain. """ @@ -704,6 +705,7 @@ 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 @@ -726,16 +728,44 @@ 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 ------- 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() + mask_bad = _get_mask([X]) xa = np.array(X, copy=True) if not xa.dtype.isnative: # Native byteorder is faster. @@ -749,7 +779,6 @@ def __call__(self, X, alpha=None, bytes=False): mask_under = xa < 0 mask_over = xa >= self.N # If input was masked, get the bad mask from it; else mask out nans. - mask_bad = X.mask if np.ma.is_masked(X) else np.isnan(xa) with np.errstate(invalid="ignore"): # We need this cast for unsigned ints as well as floats xa = xa.astype(int) @@ -778,7 +807,7 @@ def __call__(self, X, alpha=None, bytes=False): if not np.iterable(X): rgba = tuple(rgba) - return rgba + return rgba, mask_bad def __copy__(self): cls = self.__class__ @@ -960,7 +989,7 @@ def color_block(color): '' '
' f'over {color_block(self.get_over())}' - '
') + '') def copy(self): """Return a copy of the colormap.""" @@ -1225,6 +1254,876 @@ def reversed(self, name=None): return new_cmap +class MultivarColormap: + """ + Class for holding multiple `~matplotlib.colors.Colormap` for use in a + `~matplotlib.cm.VectorMappable` object + """ + def __init__(self, colormaps, combination_mode, name='multivariate colormap'): + """ + Parameters + ---------- + colormaps: list or tuple of `~matplotlib.colors.Colormap` objects + The individual colormaps that are combined + combination_mode: str, 'sRGB_add' or 'sRGB_sub' + 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]]` + - 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 + + if not np.iterable(colormaps) \ + or len(colormaps) == 1 \ + or isinstance(colormaps, str): + 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 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']: + 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) + 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, clip=True): + 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]``. + clip : bool + If True, clip output to 0 to 1 + + 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]._get_rgba_and_mask(X[0], bytes=False) + rgba = np.asarray(rgba) + for c, xx in zip(self[1:], X[1:]): + # because the components already calculate the mask + # we do not call `_get_mask(X)` here, but instead combine the + # existing masks. + 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 + + if self.combination_mode == 'sRGB_sub': + rgba[..., :3] -= len(self) - 1 + + rgba[mask_bad] = self.get_bad() + + if clip: + rgba = np.clip(rgba, 0, 1) + + if alpha is not None: + 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 " + f"not match that of X[0] {np.array(X[0]).shape}") + 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]): + 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 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: + [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: + [c.set_over(b) for c, b in zip(new_cm, over)] + return new_cm + + @property + def combination_mode(self): + return self._combination_mode + + def _repr_png_(self): + """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: + """ + 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, N=256, M=256, shape='square', origin=(0, 0), + name='bivariate colormap'): + """ + Parameters + ---------- + 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' + + - '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 + - 'ignore' the variates are not clipped, but instead assigned the + 'outside' color + - '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 + 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'") + 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 = (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 + #: `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 = _get_mask([X0, X1]) + + 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 get_outside(self): + """Get the color for out-of-range values.""" + return self._rgba_outside + + 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: + new_cm._origin = (float(origin[0]), float(origin[1])) + + return new_cm + + def _init(self): + """Generate the lookup table, ``self._lut``.""" + raise NotImplementedError("Abstract class only") + + @property + def shape(self): + return self._shape + + @property + def origin(self): + return self._origin + + 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") + 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 __getitem__(self, item): + """Creates and returns a colorbar along the selected axis""" + if not self._isinit: + self._init() + if item == 0: + 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: + 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" + f" valid keys for BivarColormap, not {item!r}") + new_cmap._rgba_bad = self._rgba_bad + if self.shape in ['ignore', 'circleignore']: + 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.""" + if not self._isinit: + self._init() + 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' + 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) + 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 + + 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. + """ + + def __init__(self, patch, N=256, shape='square', origin=(0, 0), + name='segmented bivariate colormap'): + self.patch = patch + super().__init__(N, N, shape, origin, name=name) + + 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 + 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 + + 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. + + """ + + 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. + # 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__(lut.shape[0], lut.shape[1], shape, origin, name=name) + + def _init(self): + self._isinit = True + + class Normalize: """ A class which, when called, maps values within the interval @@ -1827,6 +2726,28 @@ def autoscale_None(self, A): return Norm +def _get_mask(X): + """ + Helper function to get the mask. Marked values and np.nan are + true in the output + + Parameters + ---------- + X : iterable of np.arrays + each array must be numpy array or masked array of shape + (n, m) or (n) + + Returns + ------- + mask, bool or boolean np.array of shape (n,m) or (n) + """ + mask_bad = X[0].mask if np.ma.is_masked(X[0]) else np.isnan(X[0]) + if len(X) > 1: + for xx in X[1:]: + mask_bad |= xx.mask if np.ma.is_masked(xx) else np.isnan(xx) + return mask_bad + + def _create_empty_object_of_class(cls): return cls.__new__(cls) @@ -2167,7 +3088,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..824081d20a8d 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -138,6 +138,96 @@ class ListedColormap(Colormap): def resampled(self, lutsize: int) -> ListedColormap: ... def reversed(self, name: str | None = ...) -> ListedColormap: ... +class MultivarColormap: + name: str + colormaps: list[Colormap] + n_variates: int + 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 = ... + ) -> np.ndarray: ... + @overload + def __call__( + 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 = ..., clip: 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 resampled(self, lutshape: Sequence[int | None]) -> MultivarColormap: ... + def with_extremes( + self, + *, + bad: ColorType | None = ..., + 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: ... + +class BivarColormap: + name: str + N: int + M: int + n_variates: int + def __init__(self, N: int = ..., M: int | None = ..., shape: str = ..., origin: Sequence[float] = ..., name: 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: ... + @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, 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( + self, + *, + 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, N: int = ..., shape: str = ..., origin: Sequence[float] = ..., name: str = ... + ) -> None: ... + +class BivarColormapFromImage(BivarColormap): + def __init__(self, lut: np.ndarray, shape: str = ..., origin: Sequence[float] = ..., name: str = ... + ) -> None: ... + class Normalize: callbacks: cbook.CallbackRegistry def __init__( diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 0e6068c64b62..8613ba4e6ec1 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -180,13 +180,16 @@ def clabel(self, levels=None, *, self._label_font_props = font_manager.FontProperties(size=fontsize) if colors is None: - self.labelMappable = self + self.label_colorizer = self._get_colorizer self.labelCValueList = np.take(self.cvalues, self.labelIndiceList) else: cmap = mcolors.ListedColormap(colors, N=len(self.labelLevelList)) self.labelCValueList = list(range(len(self.labelLevelList))) - self.labelMappable = cm.ScalarMappable(cmap=cmap, - norm=mcolors.NoNorm()) + # self.labelMappable = cm.Colorizer(cmap=cmap, + # norm=mcolors.NoNorm()) + # if not hasattr(self, 'colorizer'): + self.label_colorizer = lambda: cm.Colorizer(cmap=cmap, + norm=mcolors.NoNorm()) self.labelXYs = [] @@ -506,7 +509,7 @@ def add_label(self, x, y, rotation, lev, cvalue): rotation=rotation, horizontalalignment='center', verticalalignment='center', zorder=self._clabel_zorder, - color=self.labelMappable.to_rgba(cvalue, alpha=self.get_alpha()), + color=self.label_colorizer().to_rgba(cvalue, alpha=self.get_alpha()), fontproperties=self._label_font_props, clip_box=self.axes.bbox) if self._use_clabeltext: @@ -850,15 +853,16 @@ def __init__(self, ax, *args, self.labelTexts = [] self.labelCValues = [] - self.set_cmap(cmap) + self.colorizer.cmap = cmap if norm is not None: - self.set_norm(norm) - with self.norm.callbacks.blocked(signal="changed"): - if vmin is not None: - self.norm.vmin = vmin - if vmax is not None: - self.norm.vmax = vmax - self.norm._changed() + self.colorizer.norm = norm + # with self.colorizer.norm.callbacks.blocked(signal="changed"): + # if vmin is not None: + # self.colorizer.vmin = vmin + # if vmax is not None: + # self.colorizer.vmax = vmax + # self.colorizer.norm._changed() + self.colorizer.set_clim(vmin, vmax) self._process_colors() if self._paths is None: @@ -907,7 +911,7 @@ def __init__(self, ax, *args, [subp.codes for subp in p._iter_connected_components()] for p in self.get_paths()]) tcolors = _api.deprecated("3.8")(property(lambda self: [ - (tuple(rgba),) for rgba in self.to_rgba(self.cvalues, self.alpha)])) + (tuple(rgba),) for rgba in self.colorizer.to_rgba(self.cvalues, self.alpha)])) tlinewidths = _api.deprecated("3.8")(property(lambda self: [ (w,) for w in self.get_linewidths()])) alpha = property(lambda self: self.get_alpha()) @@ -1103,18 +1107,18 @@ def _get_lowers_and_uppers(self): def changed(self): if not hasattr(self, "cvalues"): self._process_colors() # Sets cvalues. - # Force an autoscale immediately because self.to_rgba() calls + # Force an autoscale immediately because self.colorizer.to_rgba() calls # autoscale_None() internally with the data passed to it, # so if vmin/vmax are not set yet, this would override them with # content from *cvalues* rather than levels like we want - self.norm.autoscale_None(self.levels) + self.colorizer.autoscale_None(np.array(self.levels)) self.set_array(self.cvalues) self.update_scalarmappable() alphas = np.broadcast_to(self.get_alpha(), len(self.cvalues)) for label, cv, alpha in zip(self.labelTexts, self.labelCValues, alphas): label.set_alpha(alpha) - label.set_color(self.labelMappable.to_rgba(cv)) - super().changed() + label.set_color(self.colorizer.to_rgba(cv)) + # super().colorizer.changed() def _autolev(self, N): """ @@ -1244,7 +1248,7 @@ def _process_colors(self): of the regions. """ - self.monochrome = self.cmap.monochrome + self.monochrome = self.colorizer.cmap.monochrome if self.colors is not None: # Generate integers for direct indexing. i0, i1 = 0, len(self.levels) @@ -1256,14 +1260,14 @@ def _process_colors(self): if self.extend in ('both', 'max'): i1 += 1 self.cvalues = list(range(i0, i1)) - self.set_norm(mcolors.NoNorm()) + self.colorizer.norm = mcolors.NoNorm() else: self.cvalues = self.layers - self.norm.autoscale_None(self.levels) + self.colorizer.autoscale_None(np.array(self.levels)) self.set_array(self.cvalues) self.update_scalarmappable() if self.extend in ('both', 'max', 'min'): - self.norm.clip = False + self.colorizer.clip = False def _process_linewidths(self, linewidths): Nlev = len(self.levels) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 41d4b6078223..925030908614 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -3069,7 +3069,7 @@ def figimage(self, X, xo=0, yo=0, alpha=None, norm=None, cmap=None, im.set_array(X) im.set_alpha(alpha) if norm is None: - im.set_clim(vmin, vmax) + im.colorizer.set_clim(vmin, vmax) self.images.append(im) im._remove_method = self.images.remove self.stale = True diff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi index 27366e83bc4d..f8db25eccd4c 100644 --- a/lib/matplotlib/figure.pyi +++ b/lib/matplotlib/figure.pyi @@ -15,7 +15,7 @@ from matplotlib.backend_bases import ( ) from matplotlib.colors import Colormap, Normalize from matplotlib.colorbar import Colorbar -from matplotlib.cm import ScalarMappable +from matplotlib.cm import ScalarMappable, VectorMappable from matplotlib.gridspec import GridSpec, SubplotSpec, SubplotParams as SubplotParams from matplotlib.image import _ImageBase, FigureImage from matplotlib.layout_engine import LayoutEngine @@ -158,7 +158,7 @@ class FigureBase(Artist): ) -> Text: ... def colorbar( self, - mappable: ScalarMappable, + mappable: VectorMappable, cax: Axes | None = ..., ax: Axes | Iterable[Axes] | None = ..., use_gridspec: bool = ..., @@ -205,7 +205,7 @@ class FigureBase(Artist): def add_subfigure(self, subplotspec: SubplotSpec, **kwargs) -> SubFigure: ... def sca(self, a: Axes) -> Axes: ... def gca(self) -> Axes: ... - def _gci(self) -> ScalarMappable | None: ... + def _gci(self) -> VectorMappable | None: ... def _process_projection_requirements( self, *, axes_class=None, polar=False, projection=None, **kwargs ) -> tuple[type[Axes], dict[str, Any]]: ... diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 95994201b94e..a5c2f26b39ff 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -229,14 +229,16 @@ def _rgb_to_rgba(A): return rgba -class _ImageBase(martist.Artist, cm.ScalarMappable): +class _ImageBase(martist.ColorizingArtist, cm.ColorizerShim): """ Base class for images. interpolation and cmap default to their rc settings - cmap is a colors.Colormap instance - norm is a colors.Normalize instance to map luminance to 0-1 + cmap is a colors.Colormap, colors.MultivarColormap or + colors.BivarColormap instance + norm is a colors.Normalize instance to map luminance to 0-1 or a + sequence of colors.Normalize objects extent is data axes (left, right, bottom, top) for making image plots registered with data plots. Default is to label the pixel @@ -258,8 +260,10 @@ def __init__(self, ax, interpolation_stage=None, **kwargs ): - martist.Artist.__init__(self) - cm.ScalarMappable.__init__(self, norm, cmap) + + martist.ColorizingArtist.__init__(self, norm, cmap) + # martist.Artist.__init__(self) + # cm.ScalarMappable.__init__(self, norm, cmap) if origin is None: origin = mpl.rcParams['image.origin'] _api.check_in_list(["upper", "lower"], origin=origin) @@ -292,11 +296,12 @@ def get_size(self): def get_shape(self): """ - Return the shape of the image as tuple (numrows, numcols, channels). + Return the shape of the image as tuple. + (numrows, numcols, channels) if RGB/RGBA + (numrows, numcols) if scalar or multivariate data """ if self._A is None: raise RuntimeError('You must first set the image array') - return self._A.shape def set_alpha(self, alpha): @@ -331,7 +336,42 @@ def changed(self): Call this whenever the mappable is changed so observers can update. """ self._imcache = None - cm.ScalarMappable.changed(self) + martist.ColorizingArtist.changed(self) # cm.ScalarMappable.changed(self) + + def _resample_and_norm(self, A, norm, out_shape, out_mask, t): + """ + Parameters + ---------- + A : 2D numpy array to be resampled + norm : cm.Normalize object + out_shape : tuple of ints (out_height, out_width) + out_mask : 2D numpy array of shape (out_height, out_width) + t : Affine2D transform + + Returns + ------- + resampled_arr2D : resampled and normalized 2D numpy array + """ + if A.dtype.kind == 'f': # Float dtype: scale to same dtype. + scaled_dtype = np.dtype("f8" if A.dtype.itemsize > 4 else "f4") + if scaled_dtype.itemsize < A.dtype.itemsize: + _api.warn_external(f"Casting input data from {A.dtype}" + f" to {scaled_dtype} for imshow.") + else: # Int dtype, likely. + # TODO slice input array first + # Scale to appropriately sized float: use float32 if the + # dynamic range is small, to limit the memory footprint. + da = A.max().astype("f8") - A.min().astype("f8") + scaled_dtype = "f8" if da > 1e8 else "f4" + # resample the input data to the correct resolution and shape + A_resampled = _resample(self, A.astype(scaled_dtype), out_shape, t) + # if using NoNorm, cast back to the original datatype + if isinstance(norm, mcolors.NoNorm): + A_resampled = A_resampled.astype(A.dtype) + # mask and run through the norm + resampled_masked = np.ma.masked_array(A_resampled, out_mask) + output = norm(resampled_masked) + return output def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, unsampled=False, round_to_pixel_border=True): @@ -388,7 +428,6 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, "this method is called.") clipped_bbox = Bbox.intersection(out_bbox, clip_bbox) - if clipped_bbox is None: return None, 0, 0, None @@ -450,84 +489,85 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, interpolation_stage = 'rgba' else: interpolation_stage = 'data' - - if A.ndim == 2 and interpolation_stage == 'data': - # if we are a 2D array, then we are running through the - # norm + colormap transformation. However, in general the - # input data is not going to match the size on the screen so we - # have to resample to the correct number of pixels - - if A.dtype.kind == 'f': # Float dtype: scale to same dtype. - scaled_dtype = np.dtype("f8" if A.dtype.itemsize > 4 else "f4") - if scaled_dtype.itemsize < A.dtype.itemsize: - _api.warn_external(f"Casting input data from {A.dtype}" - f" to {scaled_dtype} for imshow.") - else: # Int dtype, likely. - # TODO slice input array first - # Scale to appropriately sized float: use float32 if the - # dynamic range is small, to limit the memory footprint. - da = A.max().astype("f8") - A.min().astype("f8") - scaled_dtype = "f8" if da > 1e8 else "f4" - - # resample the input data to the correct resolution and shape - A_resampled = _resample(self, A.astype(scaled_dtype), out_shape, t) - - # if using NoNorm, cast back to the original datatype - if isinstance(self.norm, mcolors.NoNorm): - A_resampled = A_resampled.astype(A.dtype) - - # Compute out_mask (what screen pixels include "bad" data - # pixels) and out_alpha (to what extent screen pixels are - # covered by data pixels: 0 outside the data extent, 1 inside - # (even for bad data), and intermediate values at the edges). - mask = (np.where(A.mask, np.float32(np.nan), np.float32(1)) - if A.mask.shape == A.shape # nontrivial mask - else np.ones_like(A, np.float32)) - # we always have to interpolate the mask to account for - # non-affine transformations - out_alpha = _resample(self, mask, out_shape, t, resample=True) - del mask # Make sure we don't use mask anymore! - out_mask = np.isnan(out_alpha) - out_alpha[out_mask] = 1 - # Apply the pixel-by-pixel alpha values if present - alpha = self.get_alpha() - if alpha is not None and np.ndim(alpha) > 0: - out_alpha *= _resample(self, alpha, out_shape, t, resample=True) - # mask and run through the norm - resampled_masked = np.ma.masked_array(A_resampled, out_mask) - output = self.norm(resampled_masked) - else: - if A.ndim == 2: # interpolation_stage = 'rgba' - self.norm.autoscale_None(A) - A = self.to_rgba(A) - alpha = self._get_scalar_alpha() + scalar_alpha = self._get_scalar_alpha() + if A.ndim == 2: + if interpolation_stage == 'rgba': + # run norm -> colormap transformation and then rescale + self.colorizer.autoscale_None(A) + unscaled_rgba = self.colorizer.to_rgba(A) + output = _resample( # resample rgba channels + self, unscaled_rgba, out_shape, t, alpha=scalar_alpha) + # transforming to bytes *after* resampling gives improved results + if output.dtype.kind == 'f': + output = (output * 255).astype(np.uint8) + else: # if _interpolation_stage != 'rgba': + # In general the input data is not going to match the size on the + # screen so we have to resample to the correct number of pixels + + # First compute out_mask (what screen pixels include "bad" data + # pixels) and out_alpha (to what extent screen pixels are + # covered by data pixels: 0 outside the data extent, 1 inside + # (even for bad data), and intermediate values at the edges). + mask = mcolors._get_mask(cm._iterable_variates_in_data(A)) + if mask.shape == A.shape: + # we always have to interpolate the mask to account for + # non-affine transformations + # To get all pixels where partially covered by the mask + # we run _resample with an array with np.nan + nan_mask = np.where(mask, np.float32(np.nan), np.float32(1)) + out_alpha = _resample(self, nan_mask, out_shape, t, + resample=True) + else: + out_alpha = np.ones(out_shape, np.float32) + del mask, nan_mask # Make sure we don't use mask anymore! + out_mask = np.isnan(out_alpha) + out_alpha[out_mask] = 1 + # Apply the pixel-by-pixel alpha values if present + alpha = self.get_alpha() + if alpha is not None and np.ndim(alpha) > 0: + out_alpha *= _resample(self, alpha, out_shape, + t, resample=True) + # Resample and norm data + if self.colorizer.cmap.n_variates == 1: + normed_resampled =\ + self._resample_and_norm(A, + self.colorizer._norm[0], + out_shape, + out_mask, t) + else: + normed_resampled = [self._resample_and_norm(a, + self.colorizer._norm[i], + out_shape, + out_mask, t) + for i, a in + enumerate(cm._iterable_variates_in_data(A))] + output = self.colorizer.cmap(normed_resampled, bytes=True) + # Apply alpha *after* if the input was greyscale without a mask + alpha_channel = output[:, :, 3] + alpha_channel[:] = ( # Assignment will cast to uint8. + alpha_channel.astype(np.float32) * out_alpha * scalar_alpha) + + else: # A.ndim == 3 if A.shape[2] == 3: # No need to resample alpha or make a full array; NumPy will expand # this out and cast to uint8 if necessary when it's assigned to the # alpha channel below. - output_alpha = (255 * alpha) if A.dtype == np.uint8 else alpha + output_alpha = (255 * scalar_alpha) if A.dtype == np.uint8 \ + else scalar_alpha else: output_alpha = _resample( # resample alpha channel - self, A[..., 3], out_shape, t, alpha=alpha) + self, A[..., 3], out_shape, t, alpha=scalar_alpha) + output = _resample( # resample rgb channels - self, _rgb_to_rgba(A[..., :3]), out_shape, t, alpha=alpha) + self, _rgb_to_rgba(A[..., :3]), out_shape, t, alpha=scalar_alpha) output[..., 3] = output_alpha # recombine rgb and alpha - - # output is now either a 2D array of normed (int or float) data - # or an RGBA array of re-sampled input - output = self.to_rgba(output, bytes=True, norm=False) + if output.dtype.kind == 'f': + output = (output * 255).astype(np.uint8) # output is now a correctly sized RGBA array of uint8 - - # Apply alpha *after* if the input was greyscale without a mask - if A.ndim == 2: - alpha = self._get_scalar_alpha() - alpha_channel = output[:, :, 3] - alpha_channel[:] = ( # Assignment will cast to uint8. - alpha_channel.astype(np.float32) * out_alpha * alpha) - - else: + else: # if unsampled: if self._imcache is None: - self._imcache = self.to_rgba(A, bytes=True, norm=(A.ndim == 2)) + self._imcache = self.colorizer.to_rgba(A, bytes=True, + norm=(A.ndim == 2)) output = self._imcache # Subset the input image to only the part that will be displayed. @@ -622,41 +662,61 @@ def contains(self, mouseevent): def write_png(self, fname): """Write the image to png file *fname*.""" - im = self.to_rgba(self._A[::-1] if self.origin == 'lower' else self._A, - bytes=True, norm=True) + im = self.colorizer.to_rgba( + self._A[::-1] if self.origin == 'lower' else self._A, + bytes=True, norm=True) PIL.Image.fromarray(im).save(fname, format="png") @staticmethod - def _normalize_image_array(A): + def _normalize_image_array(A, n_variates): """ Check validity of image-like input *A* and normalize it to a format suitable for Image subclasses. """ - A = cbook.safe_masked_invalid(A, copy=True) - if A.dtype != np.uint8 and not np.can_cast(A.dtype, float, "same_kind"): - raise TypeError(f"Image data of dtype {A.dtype} cannot be " - f"converted to float") - if A.ndim == 3 and A.shape[-1] == 1: - A = A.squeeze(-1) # If just (M, N, 1), assume scalar and apply colormap. - if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in [3, 4]): - raise TypeError(f"Invalid shape {A.shape} for image data") - if A.ndim == 3: - # If the input data has values outside the valid range (after - # normalisation), we issue a warning and then clip X to the bounds - # - otherwise casting wraps extreme values, hiding outliers and - # making reliable interpretation impossible. - high = 255 if np.issubdtype(A.dtype, np.integer) else 1 - if A.min() < 0 or high < A.max(): - _log.warning( - 'Clipping input data to the valid range for imshow with ' - 'RGB data ([0..1] for floats or [0..255] for integers). ' - 'Got range [%s..%s].', - A.min(), A.max() - ) - A = np.clip(A, 0, high) - # Cast unsupported integer types to uint8 - if A.dtype != np.uint8 and np.issubdtype(A.dtype, np.integer): - A = A.astype(np.uint8) + if n_variates == 1: + A = cbook.safe_masked_invalid(A, copy=True) + if A.dtype != np.uint8 and not np.can_cast(A.dtype, float, "same_kind"): + raise TypeError(f"Image data of dtype {A.dtype} cannot be " + f"converted to float") + if A.ndim == 3 and A.shape[-1] == 1: + A = A.squeeze(-1) # If just (M, N, 1), assume scalar and apply colormap + if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in [3, 4]): + if A.ndim == 3 and A.shape[0] == 2: + raise TypeError(f"Invalid shape {A.shape} for image data." + " For multivariate data a valid colormap must be" + " explicitly declared, for example" + f" cmap='BiOrangeBlue' or cmap='2VarAddA'") + if A.ndim == 3 and A.shape[0] > 2 and A.shape[0] <= 8: + raise TypeError(f"Invalid shape {A.shape} for image data." + " For multivariate data a multivariate colormap" + " must be explicitly declared, for example" + f" cmap='{A.shape[0]}VarAddA'") + raise TypeError(f"Invalid shape {A.shape} for image data") + if A.ndim == 3: + # If the input data has values outside the valid range (after + # normalisation), we issue a warning and then clip X to the bounds + # - otherwise casting wraps extreme values, hiding outliers and + # making reliable interpretation impossible. + high = 255 if np.issubdtype(A.dtype, np.integer) else 1 + if A.min() < 0 or high < A.max(): + _log.warning( + 'Clipping input data to the valid range for imshow with ' + 'RGB data ([0..1] for floats or [0..255] for integers). ' + 'Got range [%s..%s].', + A.min(), A.max() + ) + A = np.clip(A, 0, high) + # Cast unsupported integer types to uint8 + if A.dtype != np.uint8 and np.issubdtype(A.dtype, np.integer): + A = A.astype(np.uint8) + else: # n_variates > 1 + A = cm._ensure_multivariate_data(n_variates, A) + + A = cbook.safe_masked_invalid(A, copy=True) + for key in A.dtype.fields: + if not np.can_cast(A[key].dtype, float, "same_kind"): + raise TypeError(f"Image data of dtype {A.dtype} cannot be " + f"converted to a sequence of floats") return A def set_data(self, A): @@ -671,7 +731,7 @@ def set_data(self, A): """ if isinstance(A, PIL.Image.Image): A = pil_to_array(A) # Needed e.g. to apply png palette. - self._A = self._normalize_image_array(A) + self._A = self._normalize_image_array(A, self.colorizer.cmap.n_variates) self._imcache = None self.stale = True @@ -812,10 +872,12 @@ class AxesImage(_ImageBase): Parameters ---------- ax : `~matplotlib.axes.Axes` - The Axes the image will belong to. - cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap` + The axes the image will belong to. + cmap : str or `~matplotlib.colors.Colormap`, or \ + `~matplotlib.colors.MultivarColormap` or \ + `~matplotlib.colors.BivarColormap`, default: :rc:`image.cmap` The Colormap instance or registered colormap name used to map scalar - data to colors. + or multivariate data to colors. norm : str or `~matplotlib.colors.Normalize` Maps luminance to 0-1. interpolation : str, default: :rc:`image.interpolation` @@ -1020,7 +1082,7 @@ def make_image(self, renderer, magnification=1.0, unsampled=False): A = self._A if A.ndim == 2: if A.dtype != np.uint8: - A = self.to_rgba(A, bytes=True) + A = self.colorizer.to_rgba(A, bytes=True) else: A = np.repeat(A[:, :, np.newaxis], 4, 2) A[:, :, 3] = 255 @@ -1098,7 +1160,7 @@ def set_data(self, x, y, A): (M, N) `~numpy.ndarray` or masked array of values to be colormapped, or (M, N, 3) RGB array, or (M, N, 4) RGBA array. """ - A = self._normalize_image_array(A) + A = self._normalize_image_array(A, self.colorizer.cmap.n_variates) x = np.array(x, np.float32) y = np.array(y, np.float32) if not (x.ndim == y.ndim == 1 and A.shape[:2] == y.shape + x.shape): @@ -1212,7 +1274,7 @@ def make_image(self, renderer, magnification=1.0, unsampled=False): raise ValueError('unsampled not supported on PColorImage') if self._imcache is None: - A = self.to_rgba(self._A, bytes=True) + A = self.colorizer.to_rgba(self._A, bytes=True) self._imcache = np.pad(A, [(1, 1), (1, 1), (0, 0)], "constant") padded_A = self._imcache bg = mcolors.to_rgba(self.axes.patch.get_facecolor(), 0) @@ -1258,7 +1320,7 @@ def set_data(self, x, y, A): - (M, N, 3): RGB array - (M, N, 4): RGBA array """ - A = self._normalize_image_array(A) + A = self._normalize_image_array(A, self.colorizer.cmap.n_variates) x = np.arange(0., A.shape[1] + 1) if x is None else np.array(x, float).ravel() y = np.arange(0., A.shape[0] + 1) if y is None else np.array(y, float).ravel() if A.shape[:2] != (y.size - 1, x.size - 1): @@ -1351,7 +1413,9 @@ def make_image(self, renderer, magnification=1.0, unsampled=False): def set_data(self, A): """Set the image array.""" - cm.ScalarMappable.set_array(self, A) + A = self._normalize_image_array(A, self.colorizer.cmap.n_variates) + martist.ColorizingArtist.set_array(self, A) + # cm.ScalarMappable.set_array(self, A) self.stale = True @@ -1582,7 +1646,7 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, # as is, saving a few operations. rgba = arr else: - sm = cm.ScalarMappable(cmap=cmap) + sm = cm.Colorizer(cmap=cmap) sm.set_clim(vmin, vmax) rgba = sm.to_rgba(arr, bytes=True) if pil_kwargs is None: diff --git a/lib/matplotlib/image.pyi b/lib/matplotlib/image.pyi index f4a90ed94386..ff64334d59ca 100644 --- a/lib/matplotlib/image.pyi +++ b/lib/matplotlib/image.pyi @@ -11,7 +11,7 @@ import matplotlib.artist as martist from matplotlib.axes import Axes from matplotlib import cm from matplotlib.backend_bases import RendererBase, MouseEvent -from matplotlib.colors import Colormap, Normalize +from matplotlib.colors import Colormap, BivarColormap, MultivarColormap, Normalize from matplotlib.figure import Figure from matplotlib.transforms import Affine2D, BboxBase, Bbox, Transform @@ -58,14 +58,14 @@ def composite_images( images: Sequence[_ImageBase], renderer: RendererBase, magnification: float = ... ) -> tuple[np.ndarray, float, float]: ... -class _ImageBase(martist.Artist, cm.ScalarMappable): +class _ImageBase(martist.Artist, cm.VectorMappable): zorder: float origin: Literal["upper", "lower"] axes: Axes def __init__( self, ax: Axes, - cmap: str | Colormap | None = ..., + cmap: str | Colormap | BivarColormap | MultivarColormap | None = ..., norm: str | Normalize | None = ..., interpolation: str | None = ..., origin: Literal["upper", "lower"] | None = ..., @@ -104,7 +104,7 @@ class AxesImage(_ImageBase): self, ax: Axes, *, - cmap: str | Colormap | None = ..., + cmap: str | Colormap | BivarColormap | MultivarColormap | None = ..., norm: str | Normalize | None = ..., interpolation: str | None = ..., origin: Literal["upper", "lower"] | None = ..., @@ -158,7 +158,7 @@ class FigureImage(_ImageBase): self, fig: Figure, *, - cmap: str | Colormap | None = ..., + cmap: str | Colormap | BivarColormap | MultivarColormap | None = ..., norm: str | Normalize | None = ..., offsetx: int = ..., offsety: int = ..., 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/pyplot.py b/lib/matplotlib/pyplot.py index d54a25056175..2ad6b63c1e08 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -76,7 +76,8 @@ from matplotlib.scale import get_scale_names # noqa: F401 from matplotlib.cm import _colormaps -from matplotlib.colors import _color_sequences, Colormap +from matplotlib.colors import _color_sequences, Colormap, \ + BivarColormap, MultivarColormap import numpy as np @@ -97,7 +98,7 @@ from matplotlib.axis import Tick from matplotlib.axes._base import _AxesBase from matplotlib.backend_bases import RendererBase, Event - from matplotlib.cm import ScalarMappable + from matplotlib.cm import VectorMappable from matplotlib.contour import ContourSet, QuadContourSet from matplotlib.collections import ( Collection, @@ -2514,7 +2515,7 @@ def _get_pyplot_commands() -> list[str]: @_copy_docstring_and_deprecators(Figure.colorbar) def colorbar( - mappable: ScalarMappable | None = None, + mappable: VectorMappable | None = None, cax: matplotlib.axes.Axes | None = None, ax: matplotlib.axes.Axes | Iterable[matplotlib.axes.Axes] | None = None, **kwargs @@ -2538,7 +2539,7 @@ def clim(vmin: float | None = None, vmax: float | None = None) -> None: will be used for color scaling. If you want to set the clim of multiple images, use - `~.ScalarMappable.set_clim` on every image, for example:: + `~.VectorMappable.set_clim` on every image, for example:: for im in gca().get_images(): im.set_clim(0, 0.5) @@ -2574,10 +2575,14 @@ def get_cmap(name: Colormap | str | None = None, lut: int | None = None) -> Colo if isinstance(name, Colormap): return name _api.check_in_list(sorted(_colormaps), name=name) + + # A `cm.ColormapRegistry` can contain Colormap, BivarColormap or MultivarColormap + # objects, but `_colormaps` only contains Colormap objects. + # We therefore need to use ignore[arg-type, return-value] if lut is None: - return _colormaps[name] + return _colormaps[name] # type: ignore[return-value] else: - return _colormaps[name].resampled(lut) + return _colormaps[name].resampled(lut) # type: ignore[arg-type, return-value] def set_cmap(cmap: Colormap | str) -> None: @@ -2757,7 +2762,7 @@ def gca() -> Axes: # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Figure._gci) -def gci() -> ScalarMappable | None: +def gci() -> VectorMappable | None: return gcf()._gci() @@ -3556,7 +3561,7 @@ def hlines( @_copy_docstring_and_deprecators(Axes.imshow) def imshow( X: ArrayLike | PIL.Image.Image, - cmap: str | Colormap | None = None, + cmap: str | Colormap | BivarColormap | MultivarColormap | None = None, norm: str | Normalize | None = None, *, aspect: Literal["equal", "auto"] | float | None = None, @@ -3674,7 +3679,7 @@ def pcolor( shading: Literal["flat", "nearest", "auto"] | None = None, alpha: float | None = None, norm: str | Normalize | None = None, - cmap: str | Colormap | None = None, + cmap: str | Colormap | BivarColormap | MultivarColormap | None = None, vmin: float | None = None, vmax: float | None = None, data=None, @@ -3701,7 +3706,7 @@ def pcolormesh( *args: ArrayLike, alpha: float | None = None, norm: str | Normalize | None = None, - cmap: str | Colormap | None = None, + cmap: str | Colormap | BivarColormap | MultivarColormap | None = None, vmin: float | None = None, vmax: float | None = None, shading: Literal["flat", "nearest", "gouraud", "auto"] | None = None, @@ -4017,7 +4022,7 @@ def spy( origin=origin, **kwargs, ) - if isinstance(__ret, cm.ScalarMappable): + if isinstance(__ret, cm.VectorMappable): sci(__ret) return __ret @@ -4345,7 +4350,7 @@ def xcorr( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes._sci) -def sci(im: ScalarMappable) -> None: +def sci(im: VectorMappable) -> None: gca()._sci(im) diff --git a/lib/matplotlib/streamplot.py b/lib/matplotlib/streamplot.py index 84f99732c709..e367a2072079 100644 --- a/lib/matplotlib/streamplot.py +++ b/lib/matplotlib/streamplot.py @@ -232,8 +232,8 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, lc.sticky_edges.y[:] = [grid.y_origin, grid.y_origin + grid.height] if use_multicolor_lines: lc.set_array(np.ma.hstack(line_colors)) - lc.set_cmap(cmap) - lc.set_norm(norm) + lc.colorizer.cmap = cmap + lc.colorizer.norm = norm axes.add_collection(lc) ac = mcollections.PatchCollection(arrows) diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_axes/bivar_cmap_from_image.png b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/bivar_cmap_from_image.png new file mode 100644 index 000000000000..7395b33ab35d Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/bivar_cmap_from_image.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_axes/bivariate_cmap_call.png b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/bivariate_cmap_call.png new file mode 100644 index 000000000000..117b856c0a32 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/bivariate_cmap_call.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_axes/bivariate_cmap_shapes.png b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/bivariate_cmap_shapes.png new file mode 100644 index 000000000000..1cc1b7558ede Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/bivariate_cmap_shapes.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_axes/bivariate_visualizations.png b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/bivariate_visualizations.png new file mode 100644 index 000000000000..d53e76b97b91 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/bivariate_visualizations.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivar_cmap_call.png b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivar_cmap_call.png new file mode 100644 index 000000000000..2a3b6edd1142 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivar_cmap_call.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_figimage.png b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_figimage.png new file mode 100644 index 000000000000..8e10bddaa5ed Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_figimage.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_imshow_alpha.png b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_imshow_alpha.png new file mode 100644 index 000000000000..b37ce878dec1 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_imshow_alpha.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_imshow_norm.png b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_imshow_norm.png new file mode 100644 index 000000000000..2fe279670721 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_imshow_norm.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_pcolormesh_alpha.png b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_pcolormesh_alpha.png new file mode 100644 index 000000000000..580ea19bd233 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_pcolormesh_alpha.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_pcolormesh_norm.png b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_pcolormesh_norm.png new file mode 100644 index 000000000000..c00f5a862618 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_pcolormesh_norm.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_visualizations.png b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_visualizations.png new file mode 100644 index 000000000000..791d4ff23492 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_axes/multivariate_visualizations.png differ 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 000000000000..f22b446fc84b Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/bivariate_cmap_shapes.png differ 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 000000000000..415dfda291dc Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/multivar_alpha_mixing.png differ diff --git a/lib/matplotlib/tests/meson.build b/lib/matplotlib/tests/meson.build index 107066636f31..22d5bb1b9513 100644 --- a/lib/matplotlib/tests/meson.build +++ b/lib/matplotlib/tests/meson.build @@ -57,6 +57,8 @@ python_sources = [ 'test_marker.py', 'test_mathtext.py', 'test_matplotlib.py', + 'test_multivariate_axes.py', + 'test_multivariate_colormaps.py', 'test_mlab.py', 'test_offsetbox.py', 'test_patches.py', diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index dfacfccb3e0e..9ec9ae356d7c 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -1123,11 +1123,13 @@ def test_image_cursor_formatting(): # Create a dummy image to be able to call format_cursor_data im = ax.imshow(np.zeros((4, 4))) - data = np.ma.masked_array([0], mask=[True]) + # im.get_cursor_data(event) provides single values, + # we therefore also test with single values + data = np.ma.masked_array(0, mask=[True]) assert im.format_cursor_data(data) == '[]' - data = np.ma.masked_array([0], mask=[False]) - assert im.format_cursor_data(data) == '[0]' + data = np.ma.masked_array(0, mask=[False]) + assert im.format_cursor_data(data) == '[0.0]' data = np.nan assert im.format_cursor_data(data) == '[nan]' diff --git a/lib/matplotlib/tests/test_multivariate_axes.py b/lib/matplotlib/tests/test_multivariate_axes.py new file mode 100644 index 000000000000..70426715afcf --- /dev/null +++ b/lib/matplotlib/tests/test_multivariate_axes.py @@ -0,0 +1,585 @@ +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.colors as mcolors +import matplotlib as mpl +import pytest +import re + + +@image_comparison(["bivariate_visualizations.png"]) +def test_bivariate_visualizations(): + x_0 = np.arange(25, dtype='float32').reshape(5, 5) % 5 + x_1 = np.arange(25, dtype='float32').reshape(5, 5).T % 5 + + fig, axes = plt.subplots(1, 6, figsize=(10, 2)) + + axes[0].imshow((x_0, x_1), cmap='BiPeak', interpolation='nearest') + axes[1].matshow((x_0, x_1), cmap='BiPeak') + axes[2].pcolor((x_0, x_1), cmap='BiPeak') + axes[3].pcolormesh((x_0, x_1), cmap='BiPeak') + + x = np.arange(5) + y = np.arange(5) + X, Y = np.meshgrid(x, y) + axes[4].pcolormesh(X, Y, (x_0, x_1), cmap='BiPeak') + + patches = [ + mpl.patches.Wedge((.3, .7), .1, 0, 360), # Full circle + mpl.patches.Wedge((.7, .8), .2, 0, 360, width=0.05), # Full ring + mpl.patches.Wedge((.8, .3), .2, 0, 45), # Full sector + mpl.patches.Wedge((.8, .3), .2, 22.5, 90, width=0.10), # Ring sector + ] + colors_0 = np.arange(len(patches)) // 2 + colors_1 = np.arange(len(patches)) % 2 + p = mpl.collections.PatchCollection(patches, cmap='BiPeak', alpha=0.5) + p.set_array((colors_0, colors_1)) + axes[5].add_collection(p) + + remove_ticks_and_titles(fig) + + +@image_comparison(["multivariate_visualizations.png"]) +def test_multivariate_visualizations(): + x_0 = np.arange(25, dtype='float32').reshape(5, 5) % 5 + x_1 = np.arange(25, dtype='float32').reshape(5, 5).T % 5 + x_2 = np.arange(25, dtype='float32').reshape(5, 5) % 6 + + fig, axes = plt.subplots(1, 6, figsize=(10, 2)) + + axes[0].imshow((x_0, x_1, x_2), cmap='3VarAddA', interpolation='nearest') + axes[1].matshow((x_0, x_1, x_2), cmap='3VarAddA') + axes[2].pcolor((x_0, x_1, x_2), cmap='3VarAddA') + axes[3].pcolormesh((x_0, x_1, x_2), cmap='3VarAddA') + + x = np.arange(5) + y = np.arange(5) + X, Y = np.meshgrid(x, y) + axes[4].pcolormesh(X, Y, (x_0, x_1, x_2), cmap='3VarAddA') + + patches = [ + mpl.patches.Wedge((.3, .7), .1, 0, 360), # Full circle + mpl.patches.Wedge((.7, .8), .2, 0, 360, width=0.05), # Full ring + mpl.patches.Wedge((.8, .3), .2, 0, 45), # Full sector + mpl.patches.Wedge((.8, .3), .2, 22.5, 90, width=0.10), # Ring sector + ] + colors_0 = np.arange(len(patches)) // 2 + colors_1 = np.arange(len(patches)) % 2 + colors_2 = np.arange(len(patches)) % 3 + p = mpl.collections.PatchCollection(patches, cmap='3VarAddA', alpha=0.5) + p.set_array((colors_0, colors_1, colors_2)) + axes[5].add_collection(p) + + remove_ticks_and_titles(fig) + + +@image_comparison(["multivariate_pcolormesh_alpha.png"]) +def test_multivariate_pcolormesh_alpha(): + """ + Check that the the alpha keyword works for pcolormesh + + This test covers all plotting modes that use the same pipeline + (inherit from Collection). + """ + x_0 = np.arange(25, dtype='float32').reshape(5, 5) % 5 + x_1 = np.arange(25, dtype='float32').reshape(5, 5).T % 5 + x_2 = np.arange(25, dtype='float32').reshape(5, 5) % 6 + + fig, axes = plt.subplots(2, 3) + + axes[0, 0].pcolormesh(x_1, alpha=0.5) + axes[0, 1].pcolormesh((x_0, x_1), cmap='BiPeak', alpha=0.5) + axes[0, 2].pcolormesh((x_0, x_1, x_2), cmap='3VarAddA', alpha=0.5) + + al = np.arange(25, dtype='float32').reshape(5, 5)[::-1].T % 6 / 5 + + axes[1, 0].pcolormesh(x_1, alpha=al) + axes[1, 1].pcolormesh((x_0, x_1), cmap='BiPeak', alpha=al) + axes[1, 2].pcolormesh((x_0, x_1, x_2), cmap='3VarAddA', alpha=al) + + remove_ticks_and_titles(fig) + + +@image_comparison(["multivariate_pcolormesh_norm.png"]) +def test_multivariate_pcolormesh_norm(): + """ + Test vmin, vmax and norm + Norm is checked via a LogNorm, as this converts + A LogNorm converts the input to a masked array, masking for X <= 0 + By using a LogNorm, this functionality is also tested. + + This test covers all plotting modes that use the same pipeline + (inherit from Collection). + """ + x_0 = np.arange(25, dtype='float32').reshape(5, 5) % 5 + x_1 = np.arange(25, dtype='float32').reshape(5, 5).T % 5 + x_2 = np.arange(25, dtype='float32').reshape(5, 5) % 6 + + fig, axes = plt.subplots(3, 5) + + axes[0, 0].pcolormesh(x_1) + axes[0, 1].pcolormesh((x_0, x_1), cmap='BiPeak') + axes[0, 2].pcolormesh((x_0, x_1, x_2), cmap='3VarAddA') + axes[0, 3].pcolormesh((x_0, x_1), cmap='BiPeak') + axes[0, 4].pcolormesh((x_0, x_1, x_2), cmap='3VarAddA') + + vmin = 1 + vmax = 3 + axes[1, 0].pcolormesh(x_1, vmin=vmin, vmax=vmax) + axes[1, 1].pcolormesh((x_0, x_1), cmap='BiPeak', vmin=vmin, vmax=vmax) + axes[1, 2].pcolormesh((x_0, x_1, x_2), cmap='3VarAddA', + vmin=vmin, vmax=vmax) + axes[1, 3].pcolormesh((x_0, x_1), cmap='BiPeak', + vmin=(None, vmin), vmax=(None, vmax)) + axes[1, 4].pcolormesh((x_0, x_1, x_2), cmap='3VarAddA', + vmin=(None, vmin, None), vmax=(None, vmax, None)) + + n = mcolors.LogNorm(vmin=1, vmax=5) + axes[2, 0].pcolormesh(x_1, norm=n) + axes[2, 1].pcolormesh((x_0, x_1), cmap='BiPeak', norm=(n, n)) + axes[2, 2].pcolormesh((x_0, x_1, x_2), cmap='3VarAddA', norm=(n, n, n)) + axes[2, 3].pcolormesh((x_0, x_1), cmap='BiPeak', norm=(None, n)) + axes[2, 4].pcolormesh((x_0, x_1, x_2), cmap='3VarAddA', + norm=(None, n, None)) + + remove_ticks_and_titles(fig) + + +@image_comparison(["multivariate_imshow_alpha.png"]) +def test_multivariate_imshow_alpha(): + """ + Check that the the alpha keyword works for pcolormesh + """ + x_0 = np.arange(25, dtype='float32').reshape(5, 5) % 5 + x_1 = np.arange(25, dtype='float32').reshape(5, 5).T % 5 + x_2 = np.arange(25, dtype='float32').reshape(5, 5) % 6 + + fig, axes = plt.subplots(2, 3) + + # interpolation='nearest' to reduce size of baseline image + axes[0, 0].imshow(x_1, interpolation='nearest', alpha=0.5) + axes[0, 1].imshow((x_0, x_1), interpolation='nearest', cmap='BiPeak', alpha=0.5) + axes[0, 2].imshow((x_0, x_1, x_2), interpolation='nearest', + cmap='3VarAddA', alpha=0.5) + + al = np.arange(25, dtype='float32').reshape(5, 5)[::-1].T % 6 / 5 + + axes[1, 0].imshow(x_1, interpolation='nearest', alpha=al) + axes[1, 1].imshow((x_0, x_1), interpolation='nearest', cmap='BiPeak', alpha=al) + axes[1, 2].imshow((x_0, x_1, x_2), interpolation='nearest', + cmap='3VarAddA', alpha=al) + + remove_ticks_and_titles(fig) + + +@image_comparison(["multivariate_imshow_norm.png"]) +def test_multivariate_imshow_norm(): + """ + Test vmin, vmax and norm + Norm is checked via a LogNorm. + A LogNorm converts the input to a masked array, masking for X <= 0 + By using a LogNorm, this functionality is also tested. + """ + x_0 = np.arange(25, dtype='float32').reshape(5, 5) % 5 + x_1 = np.arange(25, dtype='float32').reshape(5, 5).T % 5 + x_2 = np.arange(25, dtype='float32').reshape(5, 5) % 6 + + fig, axes = plt.subplots(3, 5, dpi=10) + + # interpolation='nearest' to reduce size of baseline image and + # removes ambiguity when using masked array (from LogNorm) + axes[0, 0].imshow(x_1, interpolation='nearest') + axes[0, 1].imshow((x_0, x_1), interpolation='nearest', cmap='BiPeak') + axes[0, 2].imshow((x_0, x_1, x_2), interpolation='nearest', cmap='3VarAddA') + axes[0, 3].imshow((x_0, x_1), interpolation='nearest', cmap='BiPeak') + axes[0, 4].imshow((x_0, x_1, x_2), interpolation='nearest', cmap='3VarAddA') + + vmin = 1 + vmax = 3 + axes[1, 0].imshow(x_1, interpolation='nearest', vmin=vmin, vmax=vmax) + axes[1, 1].imshow((x_0, x_1), interpolation='nearest', cmap='BiPeak', + vmin=vmin, vmax=vmax) + axes[1, 2].imshow((x_0, x_1, x_2), interpolation='nearest', cmap='3VarAddA', + vmin=vmin, vmax=vmax) + axes[1, 3].imshow((x_0, x_1), interpolation='nearest', cmap='BiPeak', + vmin=(None, vmin), vmax=(None, vmax)) + axes[1, 4].imshow((x_0, x_1, x_2), interpolation='nearest', cmap='3VarAddA', + vmin=(None, vmin, None), vmax=(None, vmax, None)) + + n = mcolors.LogNorm(vmin=1, vmax=5) + axes[2, 0].imshow(x_1, interpolation='nearest', norm=n) + axes[2, 1].imshow((x_0, x_1), interpolation='nearest', cmap='BiPeak', norm=(n, n)) + axes[2, 2].imshow((x_0, x_1, x_2), interpolation='nearest', cmap='3VarAddA', + norm=(n, n, n)) + axes[2, 3].imshow((x_0, x_1), interpolation='nearest', cmap='BiPeak', + norm=(None, n)) + axes[2, 4].imshow((x_0, x_1, x_2), interpolation='nearest', cmap='3VarAddA', + norm=(None, n, None)) + + remove_ticks_and_titles(fig) + + +@image_comparison(["bivariate_cmap_shapes.png"]) +def test_bivariate_cmap_shapes(): + x_0 = np.arange(100, dtype='float32').reshape(10, 10) % 10 + x_1 = np.arange(100, dtype='float32').reshape(10, 10).T % 10 + + fig, axes = plt.subplots(1, 4, figsize=(10, 2)) + + # shape = square + axes[0].imshow((x_0, x_1), cmap='BiPeak', vmin=1, vmax=8, interpolation='nearest') + # shape = cone + axes[1].imshow((x_0, x_1), cmap='BiCone', vmin=0.5, vmax=8.5, + interpolation='nearest') + + # shape = ignore + cmap = mpl.bivar_colormaps['BiCone'] + cmap = cmap.with_extremes(shape='ignore') + axes[2].imshow((x_0, x_1), cmap=cmap, vmin=1, vmax=8, interpolation='nearest') + + # shape = circleignore + cmap = mpl.bivar_colormaps['BiCone'] + cmap = cmap.with_extremes(shape='circleignore') + axes[3].imshow((x_0, x_1), cmap=cmap, vmin=0.5, vmax=8.5, interpolation='nearest') + remove_ticks_and_titles(fig) + + +@image_comparison(["multivariate_figimage.png"]) +def test_multivariate_figimage(): + fig = plt.figure(figsize=(2, 2), dpi=100) + x, y = np.ix_(np.arange(100) / 100.0, np.arange(100) / 100) + z = np.sin(x**2 + y**2 - x*y) + c = np.sin(20*x**2 + 50*y**2) + img = np.stack((z, c)) + + fig.figimage(img, xo=0, yo=0, origin='lower', cmap='BiPeak') + fig.figimage(img[:, ::-1, :], xo=0, yo=100, origin='lower', cmap='BiPeak') + fig.figimage(img[:, :, ::-1], xo=100, yo=0, origin='lower', cmap='BiPeak') + fig.figimage(img[:, ::-1, ::-1], xo=100, yo=100, origin='lower', cmap='BiPeak') + + +@image_comparison(["multivar_cmap_call.png"]) +def test_multivar_cmap_call(): + """ + This evaluates manual calls to a bivariate colormap + The figure exists because implementing an image comparison + is easier than anumeraical comparisons for mulitdimensional arrays + """ + x_0 = np.arange(100, dtype='float32').reshape(10, 10) % 10 + x_1 = np.arange(100, dtype='float32').reshape(10, 10).T % 10 + + fig, axes = plt.subplots(1, 5, figsize=(10, 2)) + + cmap = mpl.multivar_colormaps['2VarAddA'] + + # call with 0D + axes[0].scatter(3, 6, c=[cmap((0.5, 0.5))]) + + # call with 1D + im = cmap((x_0[0]/9, x_1[::-1, 0]/9)) + axes[0].scatter(np.arange(10), np.arange(10), c=im) + + # call with 2D + cmap = mpl.multivar_colormaps['2VarSubA'] + im = cmap((x_0/9, x_1/9)) + axes[1].imshow(im, interpolation='nearest') + + # call with 3D array + im = cmap(((x_0/9, x_0/9), + (x_1/9, x_1/9))) + axes[2].imshow(im.reshape((20, 10, 4)), interpolation='nearest') + + # call with constant alpha, and data of type int + im = cmap((x_0.astype('int')*25, x_1.astype('int')*25), alpha=0.5) + axes[3].imshow(im, interpolation='nearest') + + # call with variable alpha + im = cmap((x_0/9, x_1/9), alpha=(x_0/9)**2, bytes=True) + axes[4].imshow(im, interpolation='nearest') + + # call with wrong shape + with pytest.raises(ValueError, match="must have a first dimension 2"): + cmap((0, 0, 0)) + + # call with wrong shape of alpha + with pytest.raises(ValueError, match=r"shape \(2,\) does not match " + r"that of X\[0\] \(\)"): + cmap((0, 0), alpha=(1, 1)) + remove_ticks_and_titles(fig) + + +def test_multivar_cmap_call_tuple(): + 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) + + +@image_comparison(["bivariate_cmap_call.png"]) +def test_bivariate_cmap_call(): + """ + This evaluates manual calls to a bivariate colormap + The figure exists because implementing an image comparison + is easier than anumeraical comparisons for mulitdimensional arrays + """ + x_0 = np.arange(100, dtype='float32').reshape(10, 10) % 10 + x_1 = np.arange(100, dtype='float32').reshape(10, 10).T % 10 + + fig, axes = plt.subplots(1, 5, figsize=(10, 2)) + + cmap = mpl.bivar_colormaps['BiCone'] + + # call with 1D + im = cmap((x_0[0]/9, x_1[::-1, 0]/9)) + axes[0].scatter(np.arange(10), np.arange(10), c=im) + + # call with 2D + im = cmap((x_0/9, x_1/9)) + axes[1].imshow(im, interpolation='nearest') + + # call with 3D array + im = cmap(((x_0/9, x_0/9), + (x_1/9, x_1/9))) + axes[2].imshow(im.reshape((20, 10, 4)), interpolation='nearest') + + cmap = mpl.bivar_colormaps['BiOrangeBlue'] + # call with constant alpha, and data of type int + im = cmap((x_0.astype('int')*25, x_1.astype('int')*25), alpha=0.5) + axes[3].imshow(im, interpolation='nearest') + + # call with variable alpha + im = cmap((x_0/9, x_1/9), alpha=(x_0/9)**2, bytes=True) + axes[4].imshow(im, interpolation='nearest') + + # call with wrong shape + with pytest.raises(ValueError, match="must have a first dimension 2"): + cmap((0, 0, 0)) + + # call with wrong shape of alpha + with pytest.raises(ValueError, match=r"shape \(2,\) does not match " + r"that of X\[0\] \(\)"): + cmap((0, 0), alpha=(1, 1)) + + remove_ticks_and_titles(fig) + + +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) + + +@image_comparison(["bivar_cmap_from_image.png"]) +def test_bivar_cmap_from_image(): + """ + This tests the creation and use of a bivariate colormap + generated from an image + """ + # create bivariate colormap + im = np.ones((10, 12, 4)) + im[:, :, 0] = np.arange(10)[:, np.newaxis]/10 + im[:, :, 1] = np.arange(12)[np.newaxis, :]/12 + fig, axes = plt.subplots(1, 2) + axes[0].imshow(im, interpolation='nearest') + + # use bivariate colormap + data_0 = np.arange(12).reshape((3, 4)) + data_1 = np.arange(12).reshape((4, 3)).T + cmap = mpl.colors.BivarColormapFromImage(im) + axes[1].imshow((data_0, data_1), cmap=cmap, + interpolation='nearest') + + remove_ticks_and_titles(fig) + + +def test_wrong_multivar_clim_shape(): + fig, ax = plt.subplots() + im = np.arange(24).reshape((2, 3, 4)) + with pytest.raises(ValueError, match="Unable to map the input for vmin"): + ax.imshow(im, cmap='BiPeak', vmin=(None, None, None)) + with pytest.raises(ValueError, match="Unable to map the input for vmax"): + ax.imshow(im, cmap='BiPeak', vmax=(None, None, None)) + + +def test_wrong_multivar_norm_shape(): + fig, ax = plt.subplots() + im = np.arange(24).reshape((2, 3, 4)) + with pytest.raises(ValueError, match="Unable to map the input for norm"): + ax.imshow(im, cmap='BiPeak', norm=(None, None, None)) + + +def test_wrong_multivar_data_shape(): + fig, ax = plt.subplots() + im = np.arange(12).reshape((1, 3, 4)) + with pytest.raises(ValueError, match="The data must contain complex numbers, or"): + ax.imshow(im, cmap='BiPeak') + im = np.arange(12).reshape((3, 4)) + with pytest.raises(ValueError, match="The data must contain complex numbers, or"): + ax.imshow(im, cmap='BiPeak') + + +def test_missing_multivar_cmap_imshow(): + fig, ax = plt.subplots() + im = np.arange(200).reshape((2, 10, 10)) + with pytest.raises(TypeError, + match=("a valid colormap must be explicitly declared" + + ", for example cmap='BiOrangeBlue'")): + ax.imshow(im) + im = np.arange(300).reshape((3, 10, 10)) + with pytest.raises(TypeError, + match=("multivariate colormap must be explicitly declared" + + ", for example cmap='3VarAddA")): + ax.imshow(im) + im = np.arange(1000).reshape((10, 10, 10)) + with pytest.raises(TypeError, + match=re.escape("Invalid shape (10, 10, 10) for image data")): + ax.imshow(im) + + +def test_setting_A_on_ScalarMappable(): + # correct use + vm = mpl.cm.ScalarMappable(cmap='3VarAddA') + data = np.arange(3*25).reshape((3, 5, 5)) + vm.set_array(data) + + # attempting to set wrong shape of data + with pytest.raises(ValueError, match=re.escape( + " must have a first dimension 3 or be of" + )): + data = np.arange(2*25).reshape((2, 5, 5)) + vm.set_array(data) + + +def test_setting_norm_on_ScalarMappable(): + # correct use + vm = mpl.cm.ScalarMappable(cmap='3VarAddA') + vm.set_norm('linear') + vm.set_norm(['linear', 'log', 'asinh']) + + # attempting to set wrong shape of norm + with pytest.raises(ValueError, match=re.escape( + "Unable to map the input for norm (('None', 'None')) to 3 variables" + )): + vm.set_norm(('None', 'None')) + + +def test_setting_clim_on_ScalarMappable(): + # correct use + vm = mpl.cm.ScalarMappable(cmap='3VarAddA') + vm.set_clim(0, 1) + vm.set_clim([0, 0, 0], [1, 2, 3]) + # attempting to set wrong shape of vmin/vmax + with pytest.raises(ValueError, match=re.escape( + "Unable to map the input for vmin ([0, 0]) to 3 variables" + )): + vm.set_clim(vmin=[0, 0]) + with pytest.raises(ValueError, match=re.escape( + "Unable to map the input for vmax ([1, 2]) to 3 variables" + )): + vm.set_clim(vmax=[1, 2]) + + +def test_bivar_eq(): + """ + Tests equality between bivariate colormaps + """ + cmap_0 = mpl.bivar_colormaps['BiOrangeBlue'] + + cmap_1 = mpl.bivar_colormaps['BiOrangeBlue'] + 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['BiPeak'] + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiOrangeBlue'] + cmap_1 = cmap_1.with_extremes(bad='k') + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiOrangeBlue'] + cmap_1 = cmap_1.with_extremes(outside='k') + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiOrangeBlue'] + cmap_1 = cmap_1.with_extremes(shape='circle') + assert (cmap_0 == cmap_1) is False + + +def test_cmap_error(): + data = np.ones((2, 4, 4)) + for call in (plt.imshow, plt.pcolor, plt.pcolormesh): + with pytest.raises(ValueError, match=re.escape( + "See matplotlib.bivar_colormaps() and matplotlib.multivar_colormaps()" + " for bivariate and multivariate colormaps." + )): + call(data, cmap='not_a_cmap') + + with pytest.raises(ValueError, match=re.escape( + "See matplotlib.bivar_colormaps() and matplotlib.multivar_colormaps()" + " for bivariate and multivariate colormaps." + )): + mpl.collections.PatchCollection([], cmap='not_a_cmap') + + +def test_artist_format_cursor_data_multivar(): + + X = np.zeros((3, 3)) + X[0, 0] = 0.9 + X[0, 1] = 0.99 + X[0, 2] = 0.999 + X[1, 0] = -1 + X[1, 1] = 0 + X[1, 2] = 1 + X[2, 0] = 0.09 + X[2, 1] = 0.009 + X[2, 2] = 0.0009 + + labels_list = [ + "[0.9, 0.0]", + "[1., 0.0]", + "[1., 0.0]", + "[-1.0, 0.0]", + "[0.0, 0.0]", + "[1.0, 0.0]", + "[0.09, 0.0]", + "[0.009, 0.0]", + "[0.0009, 0.0]", + ] + + pos = [[0, 0], [1, 0], [2, 0], + [0, 1], [1, 1], [2, 1], + [0, 2], [1, 2], [2, 2]] + + from matplotlib.backend_bases import MouseEvent + + for cmap in ['BiOrangeBlue', '2VarAddA']: + fig, ax = plt.subplots() + norm = mpl.colors.BoundaryNorm(np.linspace(-1, 1, 20), 256) + data = (X, np.zeros(X.shape)) + im = ax.imshow(data, cmap=cmap, norm=(norm, None)) + + for v, text in zip(pos, labels_list): + xdisp, ydisp = ax.transData.transform(v) + event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) + assert im.format_cursor_data(im.get_cursor_data(event)) == text + + +def test_multivariate_safe_masked_invalid(): + from matplotlib import cbook + dt = np.dtype('float32, float32').newbyteorder('>') + x = np.zeros(2, dtype=dt) + x['f0'][0] = np.nan + xm = cbook.safe_masked_invalid(x) + assert (xm['f0'].mask == (True, False)).all() + assert (xm['f1'].mask == (False, False)).all() + assert ' 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: + 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 = cmap_1.with_extremes(bad='k') + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.bivar_colormaps['BiPeak'] + cmap_1 = cmap_1.with_extremes(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 = cmap_1.with_extremes(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([cmap_0[0]]*2, + 'sRGB_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 = cmap_1.with_extremes(bad='k') + assert (cmap_0 == cmap_1) is False + + cmap_1 = mpl.multivar_colormaps['2VarAddA'] + cmap_1 = mpl.colors.MultivarColormap(cmap_1[:], 'sRGB_sub') + assert (cmap_0 == cmap_1) is False + + +def test_vectormappable(): + vm = mpl.cm.ScalarMappable(cmap='2VarAddA') + X = (np.linspace(0, 1, 3)[:, np.newaxis] * np.ones((3, 3)), + np.linspace(0, 1, 3)[np.newaxis, :] * np.ones((3, 3))) + res = np.array([[[0., 0., 0., 1.], + [0.38551765, 0.24977647, 0.13414118, 1.], + [0.813, 0.423, 0.105, 1.]], + [[0.17331765, 0.30909412, 0.42372941, 1.], + [0.55883529, 0.55887059, 0.55787059, 1.], + [0.98631765, 0.73209412, 0.52872941, 1.]], + [[0.187, 0.577, 0.895, 1.], + [0.57251765, 0.82677647, 1., 1.], + [1., 1., 1., 1.]]]) + assert_allclose(vm.to_rgba(X), res) + vm = mpl.cm.ScalarMappable(cmap='BiCone') + res = np.array([[[0.30631738, 0.74203125, 0.89724219, 1.], + [0.11054785, 0.78253223, 0.67161328, 1.], + [0.48632812, 0.75336328, 0.39531641, 1.]], + [[0.61757812, 0.65564453, 0.99548438, 1.], + [0.99269824, 0.97310156, 0.96819922, 1.], + [0.78989453, 0.65757422, 0.21412500, 1.]], + [[0.83592969, 0.55910547, 0.91921094, 1.], + [0.96039453, 0.50968750, 0.66529297, 1.], + [0.95374219, 0.54730859, 0.35664844, 1.]]]) + assert_allclose(vm.to_rgba(X), res) + + X_complex128 = np.empty(X[0].shape, dtype=np.complex128) + X_complex128[:, :] = X[0]+X[1]*1j + assert_allclose(vm.to_rgba(X_complex128), res) + X_complex64 = np.empty(X[0].shape, dtype=np.complex64) + X_complex64[:, :] = X[0]+X[1]*1j + assert_allclose(vm.to_rgba(X_complex64), res) + + dt = np.dtype('float32, float64') + X_dt = np.empty(X[0].shape, dtype=dt) + X_dt['f0'] = X[0] + X_dt['f1'] = X[1] + assert_allclose(vm.to_rgba(X_dt), res) + + # test norm=False + assert_allclose(vm.to_rgba(X, norm=False), res) + + # test set_array + dt = np.dtype('str, float64') + X_dt = np.empty(X[0].shape, dtype=dt) + X_dt['f1'] = X[1] + + with pytest.raises(TypeError, match='cannot be converted to a sequence of float'): + vm.set_array(X_dt) + + # get clim + vm = mpl.cm.ScalarMappable(cmap='BiCone') + vm.set_array(X) + assert_allclose(vm.get_clim(), ([0, 0], [1, 1])) diff --git a/lib/matplotlib/tri/_tripcolor.py b/lib/matplotlib/tri/_tripcolor.py index 1ac6c48a2d7c..4ebdd81e9854 100644 --- a/lib/matplotlib/tri/_tripcolor.py +++ b/lib/matplotlib/tri/_tripcolor.py @@ -135,7 +135,7 @@ def tripcolor(ax, *args, alpha=1.0, norm=None, cmap=None, vmin=None, collection = PolyCollection(verts, alpha=alpha, array=colors, cmap=cmap, norm=norm, **kwargs) - collection._scale_norm(norm, vmin, vmax) + collection.colorizer._scale_norm(norm, vmin, vmax, collection.get_array()) ax.grid(False) minx = tri.x.min() diff --git a/lib/matplotlib/typing.py b/lib/matplotlib/typing.py index b70b4cc264dc..2dd80c05b892 100644 --- a/lib/matplotlib/typing.py +++ b/lib/matplotlib/typing.py @@ -11,9 +11,9 @@ """ from collections.abc import Hashable, Sequence import pathlib -from typing import Any, Literal, TypeAlias, TypeVar +from typing import Any, Literal, TypeAlias, TypeVar, Union -from . import path +from . import path, colors from ._enums import JoinStyle, CapStyle from .markers import MarkerStyle @@ -34,6 +34,7 @@ RGBAColourType: TypeAlias = RGBAColorType ColourType: TypeAlias = ColorType +ColorMapType = Union[colors.Colormap, colors.BivarColormap, colors.MultivarColormap] LineStyleType: TypeAlias = str | tuple[float, Sequence[float]] DrawStyleType: TypeAlias = Literal["default", "steps", "steps-pre", "steps-mid", "steps-post"] diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index c31398fb8260..f95fe161b3ed 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -2103,7 +2103,7 @@ def test_scalarmap_update(fig_test, fig_ref): # force a draw fig_test.canvas.draw() # mark it as "stale" - sc_test.changed() + sc_test.colorizer.changed() # ref ax_ref = fig_ref.add_subplot(111, projection='3d') diff --git a/tools/boilerplate.py b/tools/boilerplate.py index d4f8a01d0493..56286aeb81ce 100644 --- a/tools/boilerplate.py +++ b/tools/boilerplate.py @@ -305,7 +305,7 @@ def boilerplate_gen(): 'hist2d': 'sci(__ret[-1])', 'imshow': 'sci(__ret)', 'spy': ( - 'if isinstance(__ret, cm.ScalarMappable):\n' + 'if isinstance(__ret, cm.VectorMappable):\n' ' sci(__ret)' ), 'quiver': 'sci(__ret)',