Skip to content

Commit 0922396

Browse files
committed
DOC: draft text on floating point and images resampling
1 parent 87723fb commit 0922396

File tree

2 files changed

+95
-9
lines changed

2 files changed

+95
-9
lines changed

doc/api/colors_api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Color Conversion tools
3232
get_named_colors_mapping
3333

3434

35+
.. _norms_and_colormaps:
36+
3537
Normalization and Colormapping
3638
------------------------------
3739

doc/api/image_api.rst

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
``matplotlib.image``
33
********************
44

5-
.. currentmodule:: matplotlib.image
6-
75
.. automodule:: matplotlib.image
86
:no-members:
97
:no-inherited-members:
@@ -42,21 +40,107 @@ resampling process can introduce a variety of artifacts and the
4240
default interpolation is chosen to avoid aliasis in common cases (see
4341
:doc:`/gallery/images_contours_and_fields/image_antialiasing`).
4442

45-
Floating point and you
46-
----------------------
43+
Colormapping
44+
~~~~~~~~~~~~
4745

4846
The processing steps for rendering a pseudo color image are:
4947

5048
1. rasample to user input to the required dimensions
5149
2. normalize the user data via a `~.colors.Normalize` instance
52-
3. color map from the normalized data to RGBA via a `~.colors.ColorMap` instance
50+
3. colormap from the normalized data to RGBA via a `~.colors.Colormap` instance
51+
52+
Prior to Matplotlib 2.0 we did the normalization and colormapping
53+
first and then resampled to fit the screen. However this can produce
54+
artifacts in the visualization when the data is changing close to the
55+
full range on the scale of a few pixels with most colormaps due to the
56+
interpolation in RGB space producing colors that are not in the
57+
colormap.
58+
59+
60+
Floating point and you
61+
~~~~~~~~~~~~~~~~~~~~~~
62+
63+
Floating point numbers, despite being ubiquitous, are not fully
64+
understood by most practitioners. For a through review of how
65+
floating point numbers work see `Goldberg, ACM Computing
66+
Surveys (1991) 10.1145/103162.103163
67+
<https://doi.org/10.1145/103162.103163>`__ or the `IEEE Standard for
68+
Floating Point Arithmetic ( IEEE std 754) 10.1109/IEEESTD.2008.4610935
69+
<https://doi.org/10.1109/IEEESTD.2008.4610935>`__ (both behind
70+
paywalls). For the purposes of this discussion we need to know:
71+
72+
1. There are only a finite number "floating point numbers" (that is
73+
values that can be represented by a IEEE float in the computer) and
74+
hence can not exactly represent all Real Numbers. Between those
75+
two Real Numbers there is an infinite number of Real numbers, hence
76+
the floating point numbers and computation expressed in a computer
77+
are an approximation of the Real Numbers.
78+
2. The absolute distance between adjacent floating point numbers
79+
scales with the magnitude, while the relative distance remains
80+
the same. This is a consequence of the implementation of IEEE
81+
floats.
82+
3. During computation results are rounded to the nearest
83+
represent-able value. Working with numbers that are either almost
84+
identical or vastly different orders of magnitude exasperates the
85+
errors due to this rounding.
86+
87+
This is relevant to images because, as an implementation detail, we
88+
make use of the GAG library to do the resampling from the data space
89+
to screen space and that code clips all input values to the range
90+
:math:`[0, 1]`. In addition to the mapping the colors "in range" we also
91+
map over, under, and bad values (see :ref:`norms_and_colormaps`) which need to be
92+
preserved through the resampling process. Thus, we:
93+
94+
1. scale the data to :math:`[.1, .9]`
95+
2. pass the data to AGG to resample the pixels
96+
3. scale back to the original data range
97+
98+
and then resume going through the user supplied normalization and colormap.
99+
100+
Naively, this could be expressed as ::
101+
102+
data_min, data_max = data.min, data.max
103+
# scale to [.1, .9]
104+
rescaled = .1 + .8 * (data - data_min) / (data_max - data_min)
105+
# get the correct number of pixels
106+
resampled = resample(scaled)
107+
# scale back to original data range
108+
scaled = (resampled - .1) * (data_max - data_min) + data_min
109+
110+
For "most" user data is OK, but can fail in interesting ways. First,
111+
if range of the input data is large, but the range the user actually
112+
cares about is small this will effectively map all of the interesting
113+
data to the same value! To counter act this, we have a check min /
114+
max of the data are drastically different than the vmin / vmax of the
115+
norm we use a data range expanded from vmin/vmax in the rescaling.
116+
This was addressed in and :ghissue:`10072`, :ghpull:`10133`, and
117+
:ghpull:`11047`.
118+
119+
The second is that due floating point math being an approximation of
120+
the exact infinite precision computation not all values "round trip"
121+
identically. This cause the rescaling to move values in the input
122+
data that are very close to the values of vmin or vmax to the other
123+
side. In the default case, when the over and under colors are equal
124+
to the top and bottom colors of the colormap respectively this is not
125+
visually apparent, however if the user sets a different color for
126+
over/under this is extremely apparent. The solution is to also
127+
rescale the vmin and vmax values. Despite accumulating errors, the
128+
float operations will preserve the relative ordering of values under
129+
:math:`\geq` and :math:`\leq`. This was reported in :ghissue:`16910` and
130+
fixed in :ghpull:`17636`.
131+
132+
The third issue we had was that due to rescaling the vmin and vmax,
133+
under certain conditions the sign of the vmin may change. In the case
134+
of a linear `~.colors.Normalize` this is not a problem, but in the case of a
135+
`~.colors.LogNorm` we check that both vmin and vmax are greater than 0. This
136+
was reported in :ghissue:`18415` and fixed in :ghpull:`18458` by
137+
special casing `~.colors.LogNorm` and clipping vmin to be greater than 0.
53138

54-
Prior to Matplotlib 2.0 we re
55139

56140

57141

58142
Helper functions
59-
~~~~~~~~~~~~~~~~
143+
----------------
60144

61145

62146

@@ -75,8 +159,8 @@ Image I/O functions
75159

76160
This functions can be used to read, save, and generate thumbnails of
77161
files on disk. These are here for historical reasons, and while it is
78-
unlikely we will remove them, please prefer to use a dedicated image
79-
I/O library (such as `imageio <https://imageio.github.io/>`__, `pillow
162+
unlikely we will remove them, please use a dedicated image I/O library
163+
(such as `imageio <https://imageio.github.io/>`__, `pillow
80164
<https://pillow.readthedocs.io/en/stable/>`__, or `tifffile
81165
<https://pypi.org/project/tifffile/>`__) instead.
82166

0 commit comments

Comments
 (0)