File: normalization.rst

package info (click to toggle)
astropy 7.0.1-3
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 35,328 kB
  • sloc: python: 233,437; ansic: 55,264; javascript: 17,680; lex: 8,621; sh: 3,317; xml: 2,287; makefile: 191
file content (339 lines) | stat: -rw-r--r-- 11,757 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339

.. _astropy-visualization-stretchnorm:

**********************************
Image stretching and normalization
**********************************

The `astropy.visualization` module provides a framework for
transforming values in images (and more generally any arrays),
typically for the purpose of visualization. Two main types of
transformations are provided:

* Normalization to the [0:1] range using lower and upper limits where
  :math:`x` represents the values in the original image:

.. math::

    y = \frac{x - v_{\rm min}}{v_{\rm max} - v_{\rm min}}

* *Stretching* of values in the [0:1] range to the [0:1] range using a
  linear or non-linear function:

.. math::

    z = f(y)

In addition, classes are provided in order to identify lower and upper
limits for a dataset based on specific algorithms (such as using
percentiles).

Identifying lower and upper limits, as well as re-normalizing, is
described in the `Intervals and Normalization`_ section, while
stretching is described in the `Stretching`_ section.

Intervals and Normalization
===========================

The Quick Way
-------------

``astropy`` provides a convenience
:func:`~astropy.visualization.mpl_normalize.simple_norm` function and
a :class:`~astropy.visualization.mpl_normalize.SimpleNorm` class that
can be useful for quick interactive analysis. These convenience tools
create a :class:`~astropy.visualization.mpl_normalize.ImageNormalize`
normalization object that can be used with Matplotlib's
:meth:`~matplotlib.axes.Axes.imshow` method

Here's an example using the
:func:`~astropy.visualization.mpl_normalize.simple_norm` function:

.. plot::
    :include-source:
    :align: center

    import numpy as np
    import matplotlib.pyplot as plt
    from astropy.visualization import simple_norm

    # Generate a test image
    image = np.arange(65536).reshape((256, 256))

    # Create an ImageNormalize object
    norm = simple_norm(image, 'sqrt')

    # Display the image
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    im = ax.imshow(image, origin='lower', norm=norm)
    fig.colorbar(im)

Here's an example using the
:class:`~astropy.visualization.mpl_normalize.SimpleNorm` class with its
:meth:`~astropy.visualization.mpl_normalize.SimpleNorm.imshow`
method:

.. plot::
    :include-source:
    :align: center

    import numpy as np
    import matplotlib.pyplot as plt
    from astropy.visualization import SimpleNorm

    # Generate a test image
    image = np.arange(65536).reshape((256, 256))

    # Create an ImageNormalize object
    snorm = SimpleNorm('sqrt', percent=98)

    # Display the image
    fig, ax = plt.subplots()
    axim = snorm.imshow(image, ax=ax, origin='lower')
    fig.colorbar(axim)


The detailed way
----------------

Several classes are provided for determining intervals and for
normalizing values in this interval to the [0:1] range. One of the
simplest examples is the
:class:`~astropy.visualization.MinMaxInterval` which determines the
limits of the values based on the minimum and maximum values in the
array. The class is instantiated with no arguments::

    >>> from astropy.visualization import MinMaxInterval
    >>> interval = MinMaxInterval()

and the limits can be determined by calling the
:meth:`~astropy.visualization.MinMaxInterval.get_limits` method, which
takes the array of values::

    >>> interval.get_limits([1, 3, 4, 5, 6])
    (np.int64(1), np.int64(6))

The ``interval`` instance can also be called like a function to
actually normalize values to the range::

    >>> interval([1, 3, 4, 5, 6])  # doctest: +FLOAT_CMP
    array([0. , 0.4, 0.6, 0.8, 1. ])

Other interval classes include
:class:`~astropy.visualization.ManualInterval`,
:class:`~astropy.visualization.PercentileInterval`,
:class:`~astropy.visualization.AsymmetricPercentileInterval`, and
:class:`~astropy.visualization.ZScaleInterval`. For these, values in
the array can fall outside of the limits given by the interval.  A
``clip`` argument is provided to control the behavior of the
normalization when values fall outside the limits::

    >>> from astropy.visualization import PercentileInterval
    >>> interval = PercentileInterval(50.)
    >>> interval.get_limits([1, 3, 4, 5, 6])
    (np.float64(3.0), np.float64(5.0))
    >>> interval([1, 3, 4, 5, 6])  # default is clip=True  # doctest: +FLOAT_CMP
    array([0. , 0. , 0.5, 1. , 1. ])
    >>> interval([1, 3, 4, 5, 6], clip=False)  # doctest: +FLOAT_CMP
    array([-1. ,  0. ,  0.5,  1. ,  1.5])


Stretching
==========

In addition to classes that can scale values to the [0:1] range, a
number of classes are provided to 'stretch' the values using different
functions. These map a [0:1] range onto a transformed [0:1] range. A
simple example is the :class:`~astropy.visualization.SqrtStretch`
class::

    >>> from astropy.visualization import SqrtStretch
    >>> stretch = SqrtStretch()
    >>> stretch([0., 0.25, 0.5, 0.75, 1.])  # doctest: +FLOAT_CMP
    array([0.        , 0.5       , 0.70710678, 0.8660254 , 1.        ])

As for the intervals, values outside the [0:1] range can be treated
differently depending on the ``clip`` argument. By default, output
values are clipped to the [0:1] range::

    >>> stretch([-1., 0., 0.5, 1., 1.5])  # doctest: +FLOAT_CMP
    array([0.       , 0.        , 0.70710678, 1.        , 1.        ])

but this can be disabled::

    >>> stretch([-1., 0., 0.5, 1., 1.5], clip=False)  # doctest: +FLOAT_CMP
    array([       nan, 0.        , 0.70710678, 1.        , 1.22474487])

.. note::
    The stretch functions are similar but not always strictly
    identical to those used in e.g. `DS9
    <http://ds9.si.edu/site/Home.html>`_ (although they should have
    the same behavior). The equations for the DS9 stretches can be
    found `here <http://ds9.si.edu/doc/ref/how.html>`_ and can be
    compared to the equations for our stretches provided in the
    `astropy.visualization` API section. The main difference between
    our stretches and DS9 is that we have adjusted them so that the
    [0:1] range always maps exactly to the [0:1] range.


Combining transformations
=========================

Any intervals and stretches can be chained by using the ``+``
operator, which returns a new transformation. When combining intervals
and stretches, the stretch object must come before the interval
object. For example, to apply normalization based on a percentile
value, followed by a square root stretch, you can do::

    >>> transform = SqrtStretch() + PercentileInterval(90.)
    >>> transform([1, 3, 4, 5, 6])  # doctest: +FLOAT_CMP
    array([0.        , 0.60302269, 0.76870611, 0.90453403, 1.        ])

As before, the combined transformation can also accept a ``clip``
argument (which is `True` by default).

Matplotlib normalization
========================

Matplotlib allows a custom normalization and stretch to be used when
displaying data by passing a :class:`matplotlib.colors.Normalize`
object, e.g. to :meth:`~matplotlib.axes.Axes.imshow`. The
`astropy.visualization` module provides an
:class:`~astropy.visualization.mpl_normalize.ImageNormalize` class
that wraps the interval (see `Intervals and Normalization`_) and
stretch (see `Stretching`_) objects into an object Matplotlib
understands.

The inputs to the
:class:`~astropy.visualization.mpl_normalize.ImageNormalize` class are
the data and the interval and stretch objects:

.. plot::
    :include-source:
    :align: center

    import numpy as np
    import matplotlib.pyplot as plt

    from astropy.visualization import (MinMaxInterval, SqrtStretch,
                                       ImageNormalize)

    # Generate a test image
    image = np.arange(65536).reshape((256, 256))

    # Create an ImageNormalize object
    norm = ImageNormalize(image, interval=MinMaxInterval(),
                          stretch=SqrtStretch())

    # or equivalently using positional arguments
    # norm = ImageNormalize(image, MinMaxInterval(), SqrtStretch())

    # Display the image
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    im = ax.imshow(image, origin='lower', norm=norm)
    fig.colorbar(im)

As shown above, the colorbar ticks are automatically adjusted.

Please note that one should not use ``ax.imshow(norm(image))`` because
the colorbar ticks marks will represent normalized image values (on a
linear scale), not the actual image values.  Also, the image displayed
by ``ax.imshow(norm(image))`` is not exactly equivalent to
``ax.imshow(image, norm=norm)`` if the image contains ``NaN`` or
``inf`` values.  The exact equivalent is
``ax.imshow(norm(np.ma.masked_invalid(image))``.

The input image to
:class:`~astropy.visualization.mpl_normalize.ImageNormalize` is
typically the one to be displayed, so there is a convenience function
:func:`~astropy.visualization.mpl_normalize.imshow_norm` to ease this
use case:


.. plot::
    :include-source:
    :align: center

    import numpy as np
    import matplotlib.pyplot as plt

    from astropy.visualization import imshow_norm, MinMaxInterval, SqrtStretch

    # Generate a test image
    image = np.arange(65536).reshape((256, 256))

    # Display the exact same thing as the above plot
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    im, norm = imshow_norm(image, ax, origin='lower',
                           interval=MinMaxInterval(), stretch=SqrtStretch())
    fig.colorbar(im)

While this is the simplest case, it is also possible for a completely different
image to be used to establish the normalization (e.g. if one wants to display
several images with exactly the same normalization and stretch).

The inputs to the
:class:`~astropy.visualization.mpl_normalize.ImageNormalize` class can
also be the vmin and vmax limits, which you can determine from the
`Intervals and Normalization`_ classes, and the stretch object:

.. plot::
    :include-source:
    :align: center

    import numpy as np
    import matplotlib.pyplot as plt

    from astropy.visualization import (MinMaxInterval, SqrtStretch,
                                       ImageNormalize)

    # Generate a test image
    image = np.arange(65536).reshape((256, 256))

    # Create interval object
    interval = MinMaxInterval()
    vmin, vmax = interval.get_limits(image)

    # Create an ImageNormalize object using a SqrtStretch object
    norm = ImageNormalize(vmin=vmin, vmax=vmax, stretch=SqrtStretch())

    # Display the image
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    im = ax.imshow(image, origin='lower', norm=norm)
    fig.colorbar(im)


Combining stretches and Matplotlib normalization
================================================

Stretches can also be combined with other stretches, just like transformations.
The resulting :class:`~astropy.visualization.stretch.CompositeStretch` can be
used to normalize Matplotlib images like any other stretch. For example, a
composite stretch can stretch residual images with negative values:

.. plot::
    :include-source:
    :align: center

    import numpy as np
    import matplotlib.pyplot as plt
    from astropy.visualization.stretch import SinhStretch, LinearStretch
    from astropy.visualization import ImageNormalize

    # Transforms normalized values [0,1] to [-1,1] before stretch and then back
    stretch = LinearStretch(slope=0.5, intercept=0.5) + SinhStretch() + \
        LinearStretch(slope=2, intercept=-1)

    # Image of random Gaussian noise
    rng = np.random.default_rng()
    image = rng.normal(size=(64, 64))
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    # ImageNormalize normalizes values to [0,1] before applying the stretch
    norm = ImageNormalize(stretch=stretch, vmin=-5, vmax=5)
    im = ax.imshow(image, origin='lower', norm=norm, cmap='gray')
    fig.colorbar(im)