From 1a30f6a9d369b9612d81bee920b1ed49aeea0cc3 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 20 Sep 2020 18:27:48 -0400 Subject: [PATCH 01/15] MNT: move creating interal ScalarMappable to where it is used This make it clearer that we only need this in one of the code paths and avoids creating the un-used ScalarMappable in the png savefig code path. --- lib/matplotlib/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index ca5b7da5f808..1b7b551b1905 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1621,8 +1621,6 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, else: # Don't bother creating an image; this avoids rounding errors on the # size when dividing and then multiplying by dpi. - sm = cm.ScalarMappable(cmap=cmap) - sm.set_clim(vmin, vmax) if origin is None: origin = mpl.rcParams["image.origin"] if origin == "lower": @@ -1635,6 +1633,8 @@ 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.set_clim(vmin, vmax) rgba = sm.to_rgba(arr, bytes=True) if pil_kwargs is None: pil_kwargs = {} From 0aa85863f403cb1f908e038d1de1e8094cb0c449 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 14 Sep 2020 23:07:28 -0400 Subject: [PATCH 02/15] DOC: draft of a full explanation of norm + colormap interactions --- doc/api/colors_api.rst | 149 +++++++++++++++++++++++++++++++++++------ 1 file changed, 128 insertions(+), 21 deletions(-) diff --git a/doc/api/colors_api.rst b/doc/api/colors_api.rst index e7b6da70f641..3f80e11bcb56 100644 --- a/doc/api/colors_api.rst +++ b/doc/api/colors_api.rst @@ -14,42 +14,149 @@ :no-members: :no-inherited-members: -Classes -------- +Color Conversion tools +---------------------- + +.. autosummary:: + :toctree: _as_gen/ + :template: autosummary.rst + + is_color_like + same_color + + hsv_to_rgb + rgb_to_hsv + + to_hex + to_rgb + to_rgba + to_rgba_array + + get_named_colors_mapping + + +Normalization and Colormapping +------------------------------ + +Some Artists can map an array of input data to RGBA values, (ex +`~.axes.Axes.scatter` or `~.axes.Axes.imshow`). The machinery for +this is implemented via the `~.cm.ScalarMappable` base class in `~.cm` +and the `~.Normalize` and `~.Colormap` classes in this module. + +At the core, colormapping is going from a scalar value to a RGB(A) tuple +(formally :math:`f(x) : ℝ^1 \rightarrow ℝ^3`), that is we are tracing some +1D path through the 3D RGB space. This mapping can be separated into two +orthogonal parts: + +1. the path through color space +2. the mapping between the users data to distance along the curve. + +The first step is expressed in Matplotlib via the `.Colormap` family +of classes and the second step is expressed through the `.Normalize` family +of classes. This allows us to fully independently pick what colors to use (by +selecting the colormap), what data range to show (via the ``vmin`` and ``vmax`` +attributes on `.Normalize` or the `.cm.ScalarMappable.set_clim` method), and +the functional transform (ex linear vs log) from data space to distance along the +curve space. + +In addition to the colors in the map `.Colormap` objects carry three additional colors + +- over (`.Colormap.set_over` / `.Colormap.get_over`) +- under (`.Colormap.set_under` / `.Colormap.get_under`) +- bad (`.Colormap.set_bad` / `.Colormap.get_bad`) + +The first two (over / under) control what should be done for values +that are greater or less than the data range set by the user. By +default these are equal to the top and bottom colors of the color map. +The "bad" value is used for masked or non-finite values (e.g. nan and +inf) and defaults to transparent. + + +.. note:: + + Using `.cm.get_cmap` may return to you a reference to a globally + visible instance of the colormap (rather than a new instance). If + you plan to set the over/under/bad values we recommend you first + make a copy :: + + from copy import copy + import matplotlib.cm as mcm + + my_cmap = copy(mcm.get_cmap('viridis')) + + +Both `.Colormap` and `.Normalize` are implemented as `callable classes +`__ which +allows use to bind some (mutable) state to a function call. The `.Colormap.__call__` +signature when passed floats :: + + def map(X: NormedData, alpha:Optional[float] =None, bytes:Bool=False) -> RGBA: + ... + +Takes data in a "normalized" space and: + +- maps values in the closed set ``[0, 1]`` to that fraction along the curve +- maps any value greater than 1 to the "over" color +- maps any value less than 0 to the "under" color +- maps any non-finite or masked value to the "bad" color + +broadcasting to match the input shape (scalar to tuple, n-D array to +(n+1)-D array). This can be useful to get a set of colors drawn from +a color map :: + + import matplotlib.cm as mcm + import numpy as np + + cmap = mcm.get_cmap('viridis') + list_of_colors = cmap(np.arange(0, 1, 5)) + + .. autosummary:: :toctree: _as_gen/ :template: autosummary.rst - BoundaryNorm Colormap - CenteredNorm - LightSource LinearSegmentedColormap ListedColormap - LogNorm - NoNorm + + +.. autosummary:: + :toctree: _as_gen/ + :template: autosummary.rst + Normalize + LogNorm + CenteredNorm + BoundaryNorm + TwoSlopeNorm PowerNorm + NoNorm SymLogNorm - TwoSlopeNorm - FuncNorm - -Functions ---------- .. autosummary:: :toctree: _as_gen/ :template: autosummary.rst from_levels_and_colors - hsv_to_rgb - rgb_to_hsv - to_hex - to_rgb - to_rgba - to_rgba_array - is_color_like - same_color - get_named_colors_mapping make_norm_from_scale + + +.. inheritance-diagram:: matplotlib.colors.Colormap matplotlib.colors.LinearSegmentedColormap matplotlib.colors.ListedColormap + :parts: 1 + :private-bases: + + +.. inheritance-diagram:: matplotlib.colors.Normalize matplotlib.colors.LogNorm matplotlib.colors.PowerNorm matplotlib.colors.NoNorm matplotlib.colors.TwoSlopeNorm matplotlib.colors.SymLogNorm matplotlib.colors.BoundaryNorm + :parts: 1 + :private-bases: + + +Hill Shading +------------ + +.. autosummary:: + :toctree: _as_gen/ + :template: autosummary.rst + + LightSource From e5a255db1ea51b304de82dbf201e682ff21a7372 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 20 Sep 2020 18:27:37 -0400 Subject: [PATCH 03/15] DOC: draft text on resampling --- doc/api/image_api.rst | 88 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/doc/api/image_api.rst b/doc/api/image_api.rst index df3177395eef..a0c42702d1a1 100644 --- a/doc/api/image_api.rst +++ b/doc/api/image_api.rst @@ -2,7 +2,89 @@ ``matplotlib.image`` ******************** +.. currentmodule:: matplotlib.image + .. automodule:: matplotlib.image - :members: - :undoc-members: - :show-inheritance: + :no-members: + :no-inherited-members: + + +Image Artists +------------- + +.. inheritance-diagram:: matplotlib.image._ImageBase matplotlib.image.BboxImage matplotlib.image.FigureImage matplotlib.image.PcolorImage matplotlib.image.AxesImage matplotlib.image.NonUniformImage + :parts: 1 + :private-bases: + + +.. autosummary:: + :toctree: _as_gen/ + :template: autosummary.rst + + _ImageBase + AxesImage + NonUniformImage + PcolorImage + FigureImage + BboxImage + + +Resampling +~~~~~~~~~~ + +When Matplotlib rasterizes an image when saving / displaying a Figure +we need to, in general, resample the data (either up or down) in +addition to normalizing and color mapping it. This is because the +exact size of the input, in "data" pixels, will not match the size, in +"screen" pixels, of the output. The details of how we do the +resampling is controlled by the *interpolation* specified. This +resampling process can introduce a variety of artifacts and the +default interpolation is chosen to avoid aliasis in common cases (see +:doc:`/gallery/images_contours_and_fields/image_antialiasing`). + +Floating point and you +---------------------- + +The processing steps for rendering a pseudo color image are: + +1. rasample to user input to the required dimensions +2. normalize the user data via a `~.colors.Normalize` instance +3. color map from the normalized data to RGBA via a `~.colors.ColorMap` instance + +Prior to Matplotlib 2.0 we re + + + +Helper functions +~~~~~~~~~~~~~~~~ + + + +.. autosummary:: + :toctree: _as_gen/ + :template: autosummary.rst + + + composite_images + pil_to_array + + + +Image I/O functions +------------------- + +This functions can be used to read, save, and generate thumbnails of +files on disk. These are here for historical reasons, and while it is +unlikely we will remove them, please prefer to use a dedicated image +I/O library (such as `imageio `__, `pillow +`__, or `tifffile +`__) instead. + + +.. autosummary:: + :toctree: _as_gen/ + :template: autosummary.rst + + imread + imsave + thumbnail From 83209c807e84068e995258d41fc3540ccbdb6fbb Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 7 Oct 2020 00:11:11 -0400 Subject: [PATCH 04/15] DOC: draft text on floating point and images resampling --- doc/api/colors_api.rst | 2 + doc/api/image_api.rst | 102 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 95 insertions(+), 9 deletions(-) diff --git a/doc/api/colors_api.rst b/doc/api/colors_api.rst index 3f80e11bcb56..cb7cfb86da6e 100644 --- a/doc/api/colors_api.rst +++ b/doc/api/colors_api.rst @@ -35,6 +35,8 @@ Color Conversion tools get_named_colors_mapping +.. _norms_and_colormaps: + Normalization and Colormapping ------------------------------ diff --git a/doc/api/image_api.rst b/doc/api/image_api.rst index a0c42702d1a1..034146afedc7 100644 --- a/doc/api/image_api.rst +++ b/doc/api/image_api.rst @@ -2,8 +2,6 @@ ``matplotlib.image`` ******************** -.. currentmodule:: matplotlib.image - .. automodule:: matplotlib.image :no-members: :no-inherited-members: @@ -42,21 +40,107 @@ resampling process can introduce a variety of artifacts and the default interpolation is chosen to avoid aliasis in common cases (see :doc:`/gallery/images_contours_and_fields/image_antialiasing`). -Floating point and you ----------------------- +Colormapping +~~~~~~~~~~~~ The processing steps for rendering a pseudo color image are: 1. rasample to user input to the required dimensions 2. normalize the user data via a `~.colors.Normalize` instance -3. color map from the normalized data to RGBA via a `~.colors.ColorMap` instance +3. colormap from the normalized data to RGBA via a `~.colors.Colormap` instance + +Prior to Matplotlib 2.0 we did the normalization and colormapping +first and then resampled to fit the screen. However this can produce +artifacts in the visualization when the data is changing close to the +full range on the scale of a few pixels with most colormaps due to the +interpolation in RGB space producing colors that are not in the +colormap. + + +Floating point and you +~~~~~~~~~~~~~~~~~~~~~~ + +Floating point numbers, despite being ubiquitous, are not fully +understood by most practitioners. For a through review of how +floating point numbers work see `Goldberg, ACM Computing +Surveys (1991) 10.1145/103162.103163 +`__ or the `IEEE Standard for +Floating Point Arithmetic ( IEEE std 754) 10.1109/IEEESTD.2008.4610935 +`__ (both behind +paywalls). For the purposes of this discussion we need to know: + +1. There are only a finite number "floating point numbers" (that is + values that can be represented by a IEEE float in the computer) and + hence can not exactly represent all Real Numbers. Between those + two Real Numbers there is an infinite number of Real numbers, hence + the floating point numbers and computation expressed in a computer + are an approximation of the Real Numbers. +2. The absolute distance between adjacent floating point numbers + scales with the magnitude, while the relative distance remains + the same. This is a consequence of the implementation of IEEE + floats. +3. During computation results are rounded to the nearest + represent-able value. Working with numbers that are either almost + identical or vastly different orders of magnitude exasperates the + errors due to this rounding. + +This is relevant to images because, as an implementation detail, we +make use of the GAG library to do the resampling from the data space +to screen space and that code clips all input values to the range +:math:`[0, 1]`. In addition to the mapping the colors "in range" we also +map over, under, and bad values (see :ref:`norms_and_colormaps`) which need to be +preserved through the resampling process. Thus, we: + +1. scale the data to :math:`[.1, .9]` +2. pass the data to AGG to resample the pixels +3. scale back to the original data range + +and then resume going through the user supplied normalization and colormap. + +Naively, this could be expressed as :: + + data_min, data_max = data.min, data.max + # scale to [.1, .9] + rescaled = .1 + .8 * (data - data_min) / (data_max - data_min) + # get the correct number of pixels + resampled = resample(scaled) + # scale back to original data range + scaled = (resampled - .1) * (data_max - data_min) + data_min + +For "most" user data is OK, but can fail in interesting ways. First, +if range of the input data is large, but the range the user actually +cares about is small this will effectively map all of the interesting +data to the same value! To counter act this, we have a check min / +max of the data are drastically different than the vmin / vmax of the +norm we use a data range expanded from vmin/vmax in the rescaling. +This was addressed in and :ghissue:`10072`, :ghpull:`10133`, and +:ghpull:`11047`. + +The second is that due floating point math being an approximation of +the exact infinite precision computation not all values "round trip" +identically. This cause the rescaling to move values in the input +data that are very close to the values of vmin or vmax to the other +side. In the default case, when the over and under colors are equal +to the top and bottom colors of the colormap respectively this is not +visually apparent, however if the user sets a different color for +over/under this is extremely apparent. The solution is to also +rescale the vmin and vmax values. Despite accumulating errors, the +float operations will preserve the relative ordering of values under +:math:`\geq` and :math:`\leq`. This was reported in :ghissue:`16910` and +fixed in :ghpull:`17636`. + +The third issue we had was that due to rescaling the vmin and vmax, +under certain conditions the sign of the vmin may change. In the case +of a linear `~.colors.Normalize` this is not a problem, but in the case of a +`~.colors.LogNorm` we check that both vmin and vmax are greater than 0. This +was reported in :ghissue:`18415` and fixed in :ghpull:`18458` by +special casing `~.colors.LogNorm` and clipping vmin to be greater than 0. -Prior to Matplotlib 2.0 we re Helper functions -~~~~~~~~~~~~~~~~ +---------------- @@ -75,8 +159,8 @@ Image I/O functions This functions can be used to read, save, and generate thumbnails of files on disk. These are here for historical reasons, and while it is -unlikely we will remove them, please prefer to use a dedicated image -I/O library (such as `imageio `__, `pillow +unlikely we will remove them, please use a dedicated image I/O library +(such as `imageio `__, `pillow `__, or `tifffile `__) instead. From 0123002960d394ced9be9cca71136fd4d360ebc5 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 7 Oct 2020 00:11:32 -0400 Subject: [PATCH 05/15] DOC: fix link in newly built docstring --- lib/matplotlib/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 1b7b551b1905..31a3ee2f4be5 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1674,9 +1674,9 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, def pil_to_array(pilImage): """ - Load a `PIL image`_ and return it as a numpy int array. + Load a PIL_ image and return it as a numpy int array. - .. _PIL image: https://pillow.readthedocs.io/en/latest/reference/Image.html + .. _PIL: https://pillow.readthedocs.io/en/latest/reference/Image.html Returns ------- From 6af9106e18a246b5b87c342f31e125163cd6ade4 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 7 Oct 2020 15:06:21 -0400 Subject: [PATCH 06/15] DOC: make the colormap description more colloquial --- doc/api/colors_api.rst | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/doc/api/colors_api.rst b/doc/api/colors_api.rst index cb7cfb86da6e..d79f9232f8d0 100644 --- a/doc/api/colors_api.rst +++ b/doc/api/colors_api.rst @@ -40,17 +40,24 @@ Color Conversion tools Normalization and Colormapping ------------------------------ -Some Artists can map an array of input data to RGBA values, (ex -`~.axes.Axes.scatter` or `~.axes.Axes.imshow`). The machinery for -this is implemented via the `~.cm.ScalarMappable` base class in `~.cm` -and the `~.Normalize` and `~.Colormap` classes in this module. - -At the core, colormapping is going from a scalar value to a RGB(A) tuple -(formally :math:`f(x) : ℝ^1 \rightarrow ℝ^3`), that is we are tracing some -1D path through the 3D RGB space. This mapping can be separated into two -orthogonal parts: - -1. the path through color space +Some `~.artists.Artist` classes can map an array of input data to RGBA +values, (ex `~.axes.Axes.scatter` or `~.axes.Axes.imshow`). The +machinery for this is implemented via the `~.cm.ScalarMappable` base +class in `~.cm` and the `~.Normalize` and `~.Colormap` classes in +`~.colors` (this module). + +At the core, colormapping is going from a scalar value to a RGB tuple +(formally :math:`f(x) : ℝ^1 \rightarrow ℝ^3`). To effectively +communicate through the color we want pick a :ref:`colormap suited to +the data ` but in general +"good" colormaps smoothly and continuously change their RGB values as +a function of the input data. By looking at the RGB values as we go +through the full range of the user data (e.g. a colorbar) we can trace +out the 1-dimensional path the colormap takes through 3-dimensional +RGB space. This allows us to separate the mapping process into two +orthogonal parts : + +1. the parameterized path through color space 2. the mapping between the users data to distance along the curve. The first step is expressed in Matplotlib via the `.Colormap` family From d6867f6de2f3c2e264ba1bce61452546686423c5 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 7 Oct 2020 15:06:37 -0400 Subject: [PATCH 07/15] DOC: word-choice in floating point section --- doc/api/image_api.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/image_api.rst b/doc/api/image_api.rst index 034146afedc7..a0bfa68d7eff 100644 --- a/doc/api/image_api.rst +++ b/doc/api/image_api.rst @@ -57,8 +57,8 @@ interpolation in RGB space producing colors that are not in the colormap. -Floating point and you -~~~~~~~~~~~~~~~~~~~~~~ +What you need to know about Floating Point Arithmetic for Colormapping +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Floating point numbers, despite being ubiquitous, are not fully understood by most practitioners. For a through review of how @@ -81,7 +81,7 @@ paywalls). For the purposes of this discussion we need to know: floats. 3. During computation results are rounded to the nearest represent-able value. Working with numbers that are either almost - identical or vastly different orders of magnitude exasperates the + identical or vastly different orders of magnitude exaggerates the errors due to this rounding. This is relevant to images because, as an implementation detail, we From 2d721056ed47bf1cc89bfe0e4332e0f5b9a5eaa6 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 8 Oct 2020 22:37:16 -0400 Subject: [PATCH 08/15] DOC: copy-edits from review Co-authored-by: Elliott Sales de Andrade --- doc/api/colors_api.rst | 12 ++++++------ doc/api/image_api.rst | 26 +++++++++++++------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/doc/api/colors_api.rst b/doc/api/colors_api.rst index d79f9232f8d0..41727fd43ce4 100644 --- a/doc/api/colors_api.rst +++ b/doc/api/colors_api.rst @@ -40,7 +40,7 @@ Color Conversion tools Normalization and Colormapping ------------------------------ -Some `~.artists.Artist` classes can map an array of input data to RGBA +Some `~.artist.Artist` classes can map an array of input data to RGBA values, (ex `~.axes.Axes.scatter` or `~.axes.Axes.imshow`). The machinery for this is implemented via the `~.cm.ScalarMappable` base class in `~.cm` and the `~.Normalize` and `~.Colormap` classes in @@ -58,17 +58,17 @@ RGB space. This allows us to separate the mapping process into two orthogonal parts : 1. the parameterized path through color space -2. the mapping between the users data to distance along the curve. +2. the mapping between the user's data to distance along the curve. The first step is expressed in Matplotlib via the `.Colormap` family of classes and the second step is expressed through the `.Normalize` family of classes. This allows us to fully independently pick what colors to use (by selecting the colormap), what data range to show (via the ``vmin`` and ``vmax`` -attributes on `.Normalize` or the `.cm.ScalarMappable.set_clim` method), and -the functional transform (ex linear vs log) from data space to distance along the +attributes on `.Normalize`, or via the `.cm.ScalarMappable.set_clim` method), and +the functional transform (e.g., linear vs log) from data space to distance along the curve space. -In addition to the colors in the map `.Colormap` objects carry three additional colors +In addition to the colors in the map, `.Colormap` objects carry three additional colors: - over (`.Colormap.set_over` / `.Colormap.get_over`) - under (`.Colormap.set_under` / `.Colormap.get_under`) @@ -96,7 +96,7 @@ inf) and defaults to transparent. Both `.Colormap` and `.Normalize` are implemented as `callable classes `__ which -allows use to bind some (mutable) state to a function call. The `.Colormap.__call__` +allows us to bind some (mutable) state to a function call. The `.Colormap.__call__` signature when passed floats :: def map(X: NormedData, alpha:Optional[float] =None, bytes:Bool=False) -> RGBA: diff --git a/doc/api/image_api.rst b/doc/api/image_api.rst index a0bfa68d7eff..a1f4fed13ab7 100644 --- a/doc/api/image_api.rst +++ b/doc/api/image_api.rst @@ -30,14 +30,14 @@ Image Artists Resampling ~~~~~~~~~~ -When Matplotlib rasterizes an image when saving / displaying a Figure +When Matplotlib rasterizes an image to save / display a Figure, we need to, in general, resample the data (either up or down) in addition to normalizing and color mapping it. This is because the exact size of the input, in "data" pixels, will not match the size, in "screen" pixels, of the output. The details of how we do the resampling is controlled by the *interpolation* specified. This resampling process can introduce a variety of artifacts and the -default interpolation is chosen to avoid aliasis in common cases (see +default interpolation is chosen to avoid aliasing in common cases (see :doc:`/gallery/images_contours_and_fields/image_antialiasing`). Colormapping @@ -45,7 +45,7 @@ Colormapping The processing steps for rendering a pseudo color image are: -1. rasample to user input to the required dimensions +1. resample the user input to the required dimensions 2. normalize the user data via a `~.colors.Normalize` instance 3. colormap from the normalized data to RGBA via a `~.colors.Colormap` instance @@ -69,9 +69,9 @@ Floating Point Arithmetic ( IEEE std 754) 10.1109/IEEESTD.2008.4610935 `__ (both behind paywalls). For the purposes of this discussion we need to know: -1. There are only a finite number "floating point numbers" (that is +1. There are only a finite number of "floating point numbers" (that is, values that can be represented by a IEEE float in the computer) and - hence can not exactly represent all Real Numbers. Between those + hence they can not exactly represent all Real Numbers. Between two Real Numbers there is an infinite number of Real numbers, hence the floating point numbers and computation expressed in a computer are an approximation of the Real Numbers. @@ -80,19 +80,19 @@ paywalls). For the purposes of this discussion we need to know: the same. This is a consequence of the implementation of IEEE floats. 3. During computation results are rounded to the nearest - represent-able value. Working with numbers that are either almost + representable value. Working with numbers that are either almost identical or vastly different orders of magnitude exaggerates the errors due to this rounding. This is relevant to images because, as an implementation detail, we -make use of the GAG library to do the resampling from the data space +make use of the Agg library to do the resampling from the data space to screen space and that code clips all input values to the range -:math:`[0, 1]`. In addition to the mapping the colors "in range" we also +:math:`[0, 1]`. In addition to mapping the colors "in range" we also map over, under, and bad values (see :ref:`norms_and_colormaps`) which need to be preserved through the resampling process. Thus, we: 1. scale the data to :math:`[.1, .9]` -2. pass the data to AGG to resample the pixels +2. pass the data to Agg to resample the pixels 3. scale back to the original data range and then resume going through the user supplied normalization and colormap. @@ -110,13 +110,13 @@ Naively, this could be expressed as :: For "most" user data is OK, but can fail in interesting ways. First, if range of the input data is large, but the range the user actually cares about is small this will effectively map all of the interesting -data to the same value! To counter act this, we have a check min / +data to the same value! To counteract this, we have a check if min / max of the data are drastically different than the vmin / vmax of the norm we use a data range expanded from vmin/vmax in the rescaling. -This was addressed in and :ghissue:`10072`, :ghpull:`10133`, and +This was addressed in :ghissue:`10072`, :ghpull:`10133`, and :ghpull:`11047`. -The second is that due floating point math being an approximation of +The second is that due to floating point math being an approximation of the exact infinite precision computation not all values "round trip" identically. This cause the rescaling to move values in the input data that are very close to the values of vmin or vmax to the other @@ -157,7 +157,7 @@ Helper functions Image I/O functions ------------------- -This functions can be used to read, save, and generate thumbnails of +These functions can be used to read, save, and generate thumbnails of files on disk. These are here for historical reasons, and while it is unlikely we will remove them, please use a dedicated image I/O library (such as `imageio `__, `pillow From e5bf8707af37a778ca630ac90df7e8e0198ccd97 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 9 Oct 2020 16:08:40 -0400 Subject: [PATCH 09/15] ENH: add a repr to Normalize --- lib/matplotlib/colors.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index c5db6117f1bc..cde6070b3f20 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1101,6 +1101,10 @@ class Normalize: ``[0.0, 1.0]`` interval. """ + def __repr__(self): + cls_name = f'{self.__class__.__module__}.{self.__class__.__qualname__}' + return f'<{cls_name}, vmin={self.vmin}, vmax={self.vmax}>' + def __init__(self, vmin=None, vmax=None, clip=False): """ Parameters From c49b646e65597db700dec0200c070c6f79a79bf8 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 9 Oct 2020 16:08:54 -0400 Subject: [PATCH 10/15] DOC: more copy-editing --- doc/api/colors_api.rst | 149 +++++++++++++++++++++++++++++++++++++---- doc/api/image_api.rst | 67 +++++++++--------- 2 files changed, 171 insertions(+), 45 deletions(-) diff --git a/doc/api/colors_api.rst b/doc/api/colors_api.rst index 41727fd43ce4..044e2ac50392 100644 --- a/doc/api/colors_api.rst +++ b/doc/api/colors_api.rst @@ -52,10 +52,10 @@ communicate through the color we want pick a :ref:`colormap suited to the data ` but in general "good" colormaps smoothly and continuously change their RGB values as a function of the input data. By looking at the RGB values as we go -through the full range of the user data (e.g. a colorbar) we can trace -out the 1-dimensional path the colormap takes through 3-dimensional -RGB space. This allows us to separate the mapping process into two -orthogonal parts : +through the full range of the user data we can trace out the +1-dimensional path the colormap takes through 3-dimensional RGB space. +This allows us to separate the mapping process into two orthogonal +parts : 1. the parameterized path through color space 2. the mapping between the user's data to distance along the curve. @@ -68,7 +68,8 @@ attributes on `.Normalize`, or via the `.cm.ScalarMappable.set_clim` method), an the functional transform (e.g., linear vs log) from data space to distance along the curve space. -In addition to the colors in the map, `.Colormap` objects carry three additional colors: +In addition to the colors in the map, `.Colormap` objects carry three +additional colors: - over (`.Colormap.set_over` / `.Colormap.get_over`) - under (`.Colormap.set_under` / `.Colormap.get_under`) @@ -96,13 +97,24 @@ inf) and defaults to transparent. Both `.Colormap` and `.Normalize` are implemented as `callable classes `__ which -allows us to bind some (mutable) state to a function call. The `.Colormap.__call__` -signature when passed floats :: +allows use to bind some (mutable) state to a function call. + +The `.Normalize.__call__` signature is :: + + def normalize(value: RawData, clip:Optional[Bool] =None): + ... + +It takes in data in the user's data space and converts it to *NormedData*. The +*clip* parameter allows you to override the value set at class instantiation time +and controls if the input data is clipped to the range :math:`[vmin, vmax]` before +being transformed or not. + +The `.Colormap.__call__` signature when passed floats :: def map(X: NormedData, alpha:Optional[float] =None, bytes:Bool=False) -> RGBA: ... -Takes data in a "normalized" space and: +It takes data in a "normalized" space and: - maps values in the closed set ``[0, 1]`` to that fraction along the curve - maps any value greater than 1 to the "over" color @@ -110,14 +122,123 @@ Takes data in a "normalized" space and: - maps any non-finite or masked value to the "bad" color broadcasting to match the input shape (scalar to tuple, n-D array to -(n+1)-D array). This can be useful to get a set of colors drawn from -a color map :: +(n+1)-D array). + +.. note :: + + This can be useful to draw a set of colors from a colormap :: + + import matplotlib.cm as mcm + import numpy as np + + cmap = mcm.get_cmap('viridis') + array_of_colors = cmap(np.arange(0, 1, 5)) + + +In practice +~~~~~~~~~~~ + +To make the above section concrete, lets first consider the linear `.Normalize` + +.. ipython :: + + In [104]: import matplotlib.colors as mcolors + + In [105]: norm = mcolors.Normalize(vmin=100, vmax=300) + + In [106]: norm + Out[106]: + +If we now pass in values in the range of :math:`[vmin, vmax]` + +.. ipython :: + + + In [130]: norm([100, 200, 300]) + Out[130]: + masked_array(data=[ 0. , 0.5, 1.], + mask=False, + fill_value=1e+20) + +We see that they are scaled as expected. If we also pass in some over +/ under / bad values + +.. ipython :: + + In [131]: norm([0, 100, 200, 300, 400, np.nan]) + Out[131]: + masked_array(data=[-0.5, 0. , 0.5, 1. , 1.5, nan], + mask=False, + fill_value=1e+20) + +we see that they are also scaled and produce values outside of the +range :math:`[0, 1]`. If you need the values to be clipped, you can do +have the norm do that for you via the *clip* kwarg + +.. ipython :: + + In [130]: norm([0, 100, 200, 300, 400, np.nan], clip=True) + Out[130]: + masked_array(data=[0. , 0. , 0.5, 1. , 1. , nan], + mask=False, + fill_value=1e+20) + +The default value of *clip* can be set when instantiating the +`.Normalize` instance. + +We can also use a non-linear norm + +.. ipython :: + + In [136]: log_norm = mcolors.LogNorm(10, 1000) + + In [137]: log_norm([10, 100, 1000]) + Out[137]: + masked_array(data=[0.0, 0.5, 1.0], + mask=[False, False, False], + fill_value=1e+20) + +if the data has a large dynamic range. + +Once we have normalized our data we can pass it to the colormap + +.. ipython :: + + In [102]: import copy + + In [103]: import matplotlib.cm as mcm + + In [141]: viridis = copy.copy(mcm.get_cmap('viridis')) + +.. ipython :: + + In [143]: viridis([0, .5, 1]) + Out[143]: + array([[0.267004, 0.004874, 0.329415, 1. ], + [0.127568, 0.566949, 0.550556, 1. ], + [0.993248, 0.906157, 0.143936, 1. ]]) + +Which pulls out the bottom, middle, and top color of the *viridis* . +If we set the over/under/bad colors and pass out-of-range values we can also +see them pulled out: + +.. ipython :: + + In [144]: viridis.set_over('w') + + In [147]: viridis.set_under('k') + + In [148]: viridis.set_bad('r') + + In [149]: viridis([1.5, -0.5, np.nan]) + Out[149]: + array([[1., 1., 1., 1.], + [0., 0., 0., 1.], + [1., 0., 0., 1.]]) - import matplotlib.cm as mcm - import numpy as np - cmap = mcm.get_cmap('viridis') - list_of_colors = cmap(np.arange(0, 1, 5)) +API +~~~ diff --git a/doc/api/image_api.rst b/doc/api/image_api.rst index a1f4fed13ab7..d8ea87aad9e1 100644 --- a/doc/api/image_api.rst +++ b/doc/api/image_api.rst @@ -52,22 +52,26 @@ The processing steps for rendering a pseudo color image are: Prior to Matplotlib 2.0 we did the normalization and colormapping first and then resampled to fit the screen. However this can produce artifacts in the visualization when the data is changing close to the -full range on the scale of a few pixels with most colormaps due to the -interpolation in RGB space producing colors that are not in the -colormap. +full range on the scale of a few screen pixels. Because most +colormaps are not straight lines in RGB space the interpolated values +"cut the corner" and produce colors in the output image that are not +present in the colormap. To fix this problem we re-ordered the +processing, however this has lead to a number of subtle issues with +floating point discussed below. What you need to know about Floating Point Arithmetic for Colormapping ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Floating point numbers, despite being ubiquitous, are not fully -understood by most practitioners. For a through review of how -floating point numbers work see `Goldberg, ACM Computing -Surveys (1991) 10.1145/103162.103163 -`__ or the `IEEE Standard for -Floating Point Arithmetic ( IEEE std 754) 10.1109/IEEESTD.2008.4610935 -`__ (both behind -paywalls). For the purposes of this discussion we need to know: +understood by most practitioners. For a concise discussion of how +floating point numbers work see `https://floating-point-gui.de/`__, +for a through review see `Goldberg, ACM Computing Surveys (1991) +10.1145/103162.103163 `__ +(paywall), or to for all of the details see `IEEE Standard for +Floating Point Arithmetic (IEEE std 754) 10.1109/IEEESTD.2008.4610935 +`__ (paywall). For the +purposes of this discussion we need to know: 1. There are only a finite number of "floating point numbers" (that is, values that can be represented by a IEEE float in the computer) and @@ -107,8 +111,9 @@ Naively, this could be expressed as :: # scale back to original data range scaled = (resampled - .1) * (data_max - data_min) + data_min -For "most" user data is OK, but can fail in interesting ways. First, -if range of the input data is large, but the range the user actually +For "most" user data is OK, but can fail in interesting ways. + +If the range of the input data is large, but the range the user actually cares about is small this will effectively map all of the interesting data to the same value! To counteract this, we have a check if min / max of the data are drastically different than the vmin / vmax of the @@ -116,25 +121,25 @@ norm we use a data range expanded from vmin/vmax in the rescaling. This was addressed in :ghissue:`10072`, :ghpull:`10133`, and :ghpull:`11047`. -The second is that due to floating point math being an approximation of -the exact infinite precision computation not all values "round trip" -identically. This cause the rescaling to move values in the input -data that are very close to the values of vmin or vmax to the other -side. In the default case, when the over and under colors are equal -to the top and bottom colors of the colormap respectively this is not -visually apparent, however if the user sets a different color for -over/under this is extremely apparent. The solution is to also -rescale the vmin and vmax values. Despite accumulating errors, the -float operations will preserve the relative ordering of values under -:math:`\geq` and :math:`\leq`. This was reported in :ghissue:`16910` and -fixed in :ghpull:`17636`. - -The third issue we had was that due to rescaling the vmin and vmax, -under certain conditions the sign of the vmin may change. In the case -of a linear `~.colors.Normalize` this is not a problem, but in the case of a -`~.colors.LogNorm` we check that both vmin and vmax are greater than 0. This -was reported in :ghissue:`18415` and fixed in :ghpull:`18458` by -special casing `~.colors.LogNorm` and clipping vmin to be greater than 0. +Due floating point math being an approximation of the exact infinite +precision computation not all values "round trip" identically. This +cause the rescaling to move values in the input data that are very +close to the values of vmin or vmax to the other side. In the default +case, when the over and under colors are equal to the top and bottom +colors of the colormap respectively this is not visually apparent, +however if the user sets a different color for over/under this is +extremely apparent. The solution is to also rescale the vmin and vmax +values. Despite accumulating errors, the float operations will +preserve the relative ordering of values under :math:`\geq` and +:math:`\leq`. This was reported in :ghissue:`16910` and fixed in +:ghpull:`17636`. + +Due to rescaling the vmin and vmax, under certain conditions the sign +of the vmin may change. In the case of a linear `~.colors.Normalize` +this is not a problem, but in the case of a `~.colors.LogNorm` we +check that both vmin and vmax are greater than 0. This was reported +in :ghissue:`18415` and fixed in :ghpull:`18458` by special casing +`~.colors.LogNorm` and clipping vmin to be greater than 0. From 32a2a70dc846cee32c5c5994249241fd8093128d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 28 Oct 2020 16:23:51 -0400 Subject: [PATCH 11/15] DOC/WIP: reorganization --- doc/api/image_api.rst | 48 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/doc/api/image_api.rst b/doc/api/image_api.rst index d8ea87aad9e1..ef2126a1ec9e 100644 --- a/doc/api/image_api.rst +++ b/doc/api/image_api.rst @@ -27,37 +27,35 @@ Image Artists BboxImage -Resampling -~~~~~~~~~~ - -When Matplotlib rasterizes an image to save / display a Figure, -we need to, in general, resample the data (either up or down) in -addition to normalizing and color mapping it. This is because the -exact size of the input, in "data" pixels, will not match the size, in -"screen" pixels, of the output. The details of how we do the -resampling is controlled by the *interpolation* specified. This -resampling process can introduce a variety of artifacts and the -default interpolation is chosen to avoid aliasing in common cases (see -:doc:`/gallery/images_contours_and_fields/image_antialiasing`). - -Colormapping -~~~~~~~~~~~~ - -The processing steps for rendering a pseudo color image are: +Resampling and Colormapping +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When Matplotlib rasterizes an image to save / display a Figure, we +need to resample the user supplied data because the size of the input, +in "data" pixels, will in general not match the size, in "screen" +pixels, of the output. This resampling process can introduce a +variety of artifacts and the default interpolation is chosen to avoid +aliasing in common cases (see +:doc:`/gallery/images_contours_and_fields/image_antialiasing`). The +details of how we do the resampling is controlled by the +*interpolation* kwarg and, depending on the kernel, may also +implicitly smooth the user data. + +The processing steps for rendering a pseudo color image are currently: 1. resample the user input to the required dimensions 2. normalize the user data via a `~.colors.Normalize` instance 3. colormap from the normalized data to RGBA via a `~.colors.Colormap` instance Prior to Matplotlib 2.0 we did the normalization and colormapping -first and then resampled to fit the screen. However this can produce -artifacts in the visualization when the data is changing close to the -full range on the scale of a few screen pixels. Because most -colormaps are not straight lines in RGB space the interpolated values -"cut the corner" and produce colors in the output image that are not -present in the colormap. To fix this problem we re-ordered the -processing, however this has lead to a number of subtle issues with -floating point discussed below. +first and then resampled in RGBA space to fit the screen. However, +colormaps are not straight lines in RGB space the RGBA interpolated +values may "cut the corner" and produce colors in the output image +that are not present in the colormap when the data is changing close +to the full range on the scale of a few screen pixels. To fix this +problem we re-ordered the processing in 2.0. + + What you need to know about Floating Point Arithmetic for Colormapping From 67ed9717af20d8754f17512ab214fe9fb2db705a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 13 Aug 2021 20:45:53 -0400 Subject: [PATCH 12/15] DOC: fix a PIL reference --- lib/matplotlib/image.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 31a3ee2f4be5..5ad97bd9e795 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1674,9 +1674,7 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, def pil_to_array(pilImage): """ - Load a PIL_ image and return it as a numpy int array. - - .. _PIL: https://pillow.readthedocs.io/en/latest/reference/Image.html + Load a `PIL.Image.Image` image and return it as a numpy int array. Returns ------- From ced2552546d8581b9d6173f7343945e52c827ee5 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 13 Aug 2021 20:46:08 -0400 Subject: [PATCH 13/15] DOC: fix url --- doc/api/image_api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/image_api.rst b/doc/api/image_api.rst index ef2126a1ec9e..4b742b5bf3f6 100644 --- a/doc/api/image_api.rst +++ b/doc/api/image_api.rst @@ -63,7 +63,7 @@ What you need to know about Floating Point Arithmetic for Colormapping Floating point numbers, despite being ubiquitous, are not fully understood by most practitioners. For a concise discussion of how -floating point numbers work see `https://floating-point-gui.de/`__, +floating point numbers work see ``_, for a through review see `Goldberg, ACM Computing Surveys (1991) 10.1145/103162.103163 `__ (paywall), or to for all of the details see `IEEE Standard for From c8fa541a2e3122ae0c69fd448f93a3933ea5b664 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 13 Aug 2021 20:46:19 -0400 Subject: [PATCH 14/15] DOC: add attributes to Normalize --- lib/matplotlib/colors.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index cde6070b3f20..f8693e88b602 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1099,6 +1099,27 @@ class Normalize: """ A class which, when called, linearly normalizes data into the ``[0.0, 1.0]`` interval. + + Attributes + ---------- + vmin : float or None + The value mapped to 0. Any input data less than *vmin* is mapped to + "under" + + If `None`, will be inferred from the minimum of the first input data + passed to `.__call__`. + + vmax : float or None + The value mapped to 1. Any input data greater than *vmax* is mapped + to "over" + + If `None`, will be inferred from the maximum of the first input data + passed to `.__call__`. + + clip : bool + If `True`, then the input data is clipped to *vmin* and *vmax* before + normalizing. This means there will be no value mapped as over or under. + """ def __repr__(self): From 317a6af7697eda9dc28ab84775f2a55fe4cfe48a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 13 Aug 2021 20:46:35 -0400 Subject: [PATCH 15/15] DOC: re-write most of the new text --- doc/api/colors_api.rst | 198 +++++++++++++++++++++++------------------ 1 file changed, 113 insertions(+), 85 deletions(-) diff --git a/doc/api/colors_api.rst b/doc/api/colors_api.rst index 044e2ac50392..5d6dd3f70880 100644 --- a/doc/api/colors_api.rst +++ b/doc/api/colors_api.rst @@ -37,8 +37,8 @@ Color Conversion tools .. _norms_and_colormaps: -Normalization and Colormapping ------------------------------- +Normalization and Colormapping of Continuous Data +------------------------------------------------- Some `~.artist.Artist` classes can map an array of input data to RGBA values, (ex `~.axes.Axes.scatter` or `~.axes.Axes.imshow`). The @@ -46,94 +46,92 @@ machinery for this is implemented via the `~.cm.ScalarMappable` base class in `~.cm` and the `~.Normalize` and `~.Colormap` classes in `~.colors` (this module). -At the core, colormapping is going from a scalar value to a RGB tuple -(formally :math:`f(x) : ℝ^1 \rightarrow ℝ^3`). To effectively -communicate through the color we want pick a :ref:`colormap suited to -the data ` but in general -"good" colormaps smoothly and continuously change their RGB values as -a function of the input data. By looking at the RGB values as we go -through the full range of the user data we can trace out the -1-dimensional path the colormap takes through 3-dimensional RGB space. -This allows us to separate the mapping process into two orthogonal -parts : - -1. the parameterized path through color space -2. the mapping between the user's data to distance along the curve. - -The first step is expressed in Matplotlib via the `.Colormap` family -of classes and the second step is expressed through the `.Normalize` family -of classes. This allows us to fully independently pick what colors to use (by -selecting the colormap), what data range to show (via the ``vmin`` and ``vmax`` -attributes on `.Normalize`, or via the `.cm.ScalarMappable.set_clim` method), and -the functional transform (e.g., linear vs log) from data space to distance along the -curve space. - -In addition to the colors in the map, `.Colormap` objects carry three -additional colors: - -- over (`.Colormap.set_over` / `.Colormap.get_over`) -- under (`.Colormap.set_under` / `.Colormap.get_under`) -- bad (`.Colormap.set_bad` / `.Colormap.get_bad`) - -The first two (over / under) control what should be done for values -that are greater or less than the data range set by the user. By -default these are equal to the top and bottom colors of the color map. -The "bad" value is used for masked or non-finite values (e.g. nan and -inf) and defaults to transparent. - - -.. note:: +At the core, colormapping is going from a scalar value to a RGB tuple (formally +:math:`f(x) : ℝ^1 \rightarrow ℝ^3`). To effectively communicate through the +color we want pick a :ref:`colormap suited to the data +`. For continuous data types [#f1]_ a +"good" colormap smoothly and continuously change their RGB values as a function +of the input that trace out a 1-dimensional path through the 3-dimensional RGB +space [#f2]_. We can restrict the domain of :math:`f` to $[0, 1]$ which we +interpret as the normalized distance along the curve. This allows us to +cleanly separate the mapping process from the continuous input data to RGB into +two steps: + +1. the mapping between the user's data to distance along the curve +2. the parameterized path through color space. + +The first step is expressed through the `.Normalize` family of classes and the +second is expressed in Matplotlib via the `.Colormap` family of classes. This +allows us to fully independently pick the functional transform (e.g., linear vs +log) from data space to distance along the curve space, what (user) data range +to show (via the ``vmin`` and ``vmax`` attributes on `.Normalize`, or via the +`.cm.ScalarMappable.set_clim` method), and what colors to use (by selecting the +`.Colormap`). Both `.Colormap` and `.Normalize` are implemented as `callable +classes `__ +which allows use to bind some (mutable) state to a function call. The complete +functionality is exposed in the `.ScalarMappable` family of artists which have +a `.Colormap` and `.Normalize` instances and are responsible for invoking them +at draw time. + +The `.Normalize` family has 3 common attributes: *vmin*, *vmax*, and *clip* +which control the data limits. The `.Normalize.__call__` signature is :: + + def __call__(value: RawData, clip:Optional[Bool] =None) -> NormedData: + ... - Using `.cm.get_cmap` may return to you a reference to a globally - visible instance of the colormap (rather than a new instance). If - you plan to set the over/under/bad values we recommend you first - make a copy :: +It takes in data in the user's data space and converts it to *NormedData* with +the range: - from copy import copy - import matplotlib.cm as mcm +.. math:: - my_cmap = copy(mcm.get_cmap('viridis')) + \begin{cases} + \mathrm{under} & d < vmin \\ + [0, 1] & vmin \leq d \leq vmax \\ + \mathrm{over} & vmax < d \\ + \mathrm{bad} & !\mathrm{np.finite(d)} + \end{cases} -Both `.Colormap` and `.Normalize` are implemented as `callable classes -`__ which -allows use to bind some (mutable) state to a function call. -The `.Normalize.__call__` signature is :: +The `.Colormap.__call__` signature when passed *NormedData* (floats) [#f3]_ is +:: - def normalize(value: RawData, clip:Optional[Bool] =None): + def __call__(self, X: NormedData, + alpha:Optional[float] =None, bytes:Bool=False) -> RGBA: ... -It takes in data in the user's data space and converts it to *NormedData*. The -*clip* parameter allows you to override the value set at class instantiation time -and controls if the input data is clipped to the range :math:`[vmin, vmax]` before -being transformed or not. - -The `.Colormap.__call__` signature when passed floats :: - - def map(X: NormedData, alpha:Optional[float] =None, bytes:Bool=False) -> RGBA: - ... +In addition to parameterized path through RGB (which handles values in $[0, +1]$, `.Colormap` objects carry three additional colors: -It takes data in a "normalized" space and: +- *over* (`.Colormap.set_over` / `.Colormap.get_over`) +- *under* (`.Colormap.set_under` / `.Colormap.get_under`) +- *bad* (`.Colormap.set_bad` / `.Colormap.get_bad`) -- maps values in the closed set ``[0, 1]`` to that fraction along the curve -- maps any value greater than 1 to the "over" color -- maps any value less than 0 to the "under" color -- maps any non-finite or masked value to the "bad" color +which control the color for the corresponding values in *NormedData*. +By default the over and under colors are the top and bottom colors of +the colormap respectively and bad is transparent. -broadcasting to match the input shape (scalar to tuple, n-D array to -(n+1)-D array). +.. warning:: -.. note :: - - This can be useful to draw a set of colors from a colormap :: + Using `.cm.get_cmap` may return to you a reference to a globally + visible instance of the colormap (rather than a new instance). If + you plan to set the over/under/bad values we recommend you first + make a copy :: + from copy import copy import matplotlib.cm as mcm - import numpy as np - cmap = mcm.get_cmap('viridis') - array_of_colors = cmap(np.arange(0, 1, 5)) + my_cmap = copy(mcm.get_cmap('viridis')) +.. rubric:: Footnotes + +.. [#f1] Discrete data types, such as Categorical and Ordinal, have different + considerations. +.. [#f2] Notable, the cubehelix colormap is named because it traces a helix + through the RGB color cube from black to white. +.. [#f3] Discrete data, as handled by `.NoNorm` and `.BoundaryNorm` are passed + as integers and act as direct Look Up Table (LUT) indexes into the + colormap. In practice ~~~~~~~~~~~ @@ -237,10 +235,30 @@ see them pulled out: [1., 0., 0., 1.]]) + +Directly using a `.Colormap` outside of a `.ScalarMappable` can be useful +to generate a family of coherent colors for plotting + +.. plot:: + :include-source: + + import matplotlib.cm as mcm + import numpy as np + + cmap = mcm.get_cmap('viridis') + array_of_colors = cmap(np.linspace(0, 1, 5)) + + x = np.linspace(0, 1, 25) + fig, ax = plt.subplots(constrained_layout=True) + for j, color in enumerate(array_of_colors): + ax.plot(x, x**j, color=color, label=f'$x^{j}$') + ax.legend() + API ~~~ - +Colormap Classes +++++++++++++++++ .. autosummary:: :toctree: _as_gen/ @@ -251,6 +269,14 @@ API ListedColormap +.. inheritance-diagram:: matplotlib.colors.Colormap matplotlib.colors.LinearSegmentedColormap matplotlib.colors.ListedColormap + :parts: 1 + :private-bases: + + +Norm Classes +++++++++++++ + .. autosummary:: :toctree: _as_gen/ :template: autosummary.rst @@ -258,11 +284,22 @@ API Normalize LogNorm CenteredNorm - BoundaryNorm TwoSlopeNorm PowerNorm - NoNorm SymLogNorm + FuncNorm + BoundaryNorm + NoNorm + + +.. inheritance-diagram:: matplotlib.colors.Normalize matplotlib.colors.LogNorm matplotlib.colors.PowerNorm matplotlib.colors.NoNorm matplotlib.colors.TwoSlopeNorm matplotlib.colors.SymLogNorm matplotlib.colors.BoundaryNorm matplotlib.colors.FuncNorm matplotlib.colors.CenteredNorm + :parts: 1 + :private-bases: + + +Factory Functions & Decorators +++++++++++++++++++++++++++++++ + .. autosummary:: :toctree: _as_gen/ @@ -272,15 +309,6 @@ API make_norm_from_scale -.. inheritance-diagram:: matplotlib.colors.Colormap matplotlib.colors.LinearSegmentedColormap matplotlib.colors.ListedColormap - :parts: 1 - :private-bases: - - -.. inheritance-diagram:: matplotlib.colors.Normalize matplotlib.colors.LogNorm matplotlib.colors.PowerNorm matplotlib.colors.NoNorm matplotlib.colors.TwoSlopeNorm matplotlib.colors.SymLogNorm matplotlib.colors.BoundaryNorm - :parts: 1 - :private-bases: - Hill Shading ------------