Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 37410a6

Browse files
committed
ENH: anti-alias down-sampled images
1 parent 9e7a235 commit 37410a6

File tree

13 files changed

+277
-23
lines changed

13 files changed

+277
-23
lines changed

.flake8

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ per-file-ignores =
9494
examples/images_contours_and_fields/contourf_hatching.py: E402
9595
examples/images_contours_and_fields/contourf_log.py: E402
9696
examples/images_contours_and_fields/demo_bboximage.py: E402
97+
examples/images_contours_and_fields/image_antialiasing.py: E402
9798
examples/images_contours_and_fields/image_clip_path.py: E402
9899
examples/images_contours_and_fields/image_demo.py: E402
99100
examples/images_contours_and_fields/image_masked.py: E402
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Default interpolation for `image` is new "antialiased" option
2+
-------------------------------------------------------------
3+
4+
Images displayed in Matplotlib previously used nearest-neighbor
5+
interpolation, leading to aliasing effects for non-integer upscaling, and
6+
isolated data pixels being lost.
7+
8+
New default for :rc:`image.interpolation` is the new option "antialiased".
9+
`imshow(A, interpolation='antialiased')` will apply a Hanning filter when
10+
resampling the data in A for display (or saving to file) *if* the upsample
11+
rate is less than a factor of three, and not an integer; downsampled data is
12+
always smoothed at resampling.
13+
14+
To get the old behavior, set :rc:`interpolation` to the old default "nearest"
15+
(or specify the ``interpolation`` kwarg of `.Axes.imshow`)
16+
17+
To always get the anti-aliasing behavior, no matter what the up/down sample
18+
rate, set :rc:`interpolation` to "hanning" (or one of the other filters
19+
available.
20+
21+
Note that the "hanning" filter was chosen because it has only a modest
22+
performance penalty. Anti-aliasing can be improved with other filters.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""
2+
==================
3+
Image Antialiasing
4+
==================
5+
6+
Images are represented by discrete pixels, either on the screen or in an
7+
image file. When data that makes up the image has a different resolution
8+
than its representation on the screen we will see aliasing effects.
9+
10+
By default Matplotlib has a mild anti-aliasing filter turned on for images
11+
that are rendered to screen with either down sampling (i.e. the screen
12+
representation has fewer pixels than the data) or mild up-sampling, to a
13+
limit of three-times upsampling.
14+
15+
If a different anti-aliasing filter is desired (or no filter), it can be
16+
specified in `.Axes.imshow` using the *interpolation* kwarg.
17+
"""
18+
19+
import numpy as np
20+
import matplotlib.pyplot as plt
21+
22+
###############################################################################
23+
# First we generate an image with varying frequency content:
24+
x = np.arange(500) / 500 - 0.5
25+
y = np.arange(500) / 500 - 0.5
26+
27+
X, Y = np.meshgrid(x, y)
28+
R = np.sqrt(X**2 + Y**2)
29+
f0 = 10
30+
k = 250
31+
a = np.sin(np.pi * 2 * (f0 * R + k * R**2 / 2))
32+
33+
34+
###############################################################################
35+
# Now plot. Note that these images are subsampled, with the images
36+
# representing 1000 data pixels in (approx) 604 image pixels.
37+
# The Moire patterns in the "nearest" interpolation are caused by the
38+
# high-frequency data being subsampled. The "antialiased" image
39+
# still has some Moire patterns as well, but they are greatly reduced.
40+
# We can reduce them more, as in the examples below, but better filters
41+
# are more computationally expensive, so we have erred on the side of
42+
# efficiency.
43+
fig, axs = plt.subplots(1, 2, figsize=(7, 4), constrained_layout=True)
44+
for n, interp in enumerate(['nearest', 'antialiased']):
45+
im = axs[n].imshow(a, interpolation=interp, cmap='gray')
46+
axs[n].set_title(interp)
47+
plt.show()
48+
49+
###############################################################################
50+
# Note that even up-sampling an image will lead to Moire patterns unless
51+
# the upsample is an integer number of pixels.
52+
fig, ax = plt.subplots(1, 1, figsize=(5.3, 5.3))
53+
ax.set_position([0, 0, 1, 1])
54+
im = ax.imshow(a, interpolation='nearest', cmap='gray')
55+
plt.show()
56+
57+
###############################################################################
58+
# The patterns aren't as bad, but still benefit from anti-aliasing
59+
fig, ax = plt.subplots(1, 1, figsize=(5.3, 5.3))
60+
ax.set_position([0, 0, 1, 1])
61+
im = ax.imshow(a, interpolation='antialiased', cmap='gray')
62+
plt.show()
63+
64+
###############################################################################
65+
# If the small Moire patterns in the default "hanning" antialiasing are
66+
# still undesireable, then we can use other filters.
67+
fig, axs = plt.subplots(1, 2, figsize=(7, 4), constrained_layout=True)
68+
for n, interp in enumerate(['hanning', 'lanczos']):
69+
im = axs[n].imshow(a, interpolation=interp, cmap='gray')
70+
axs[n].set_title(interp)
71+
plt.show()
72+
73+
74+
#############################################################################
75+
#
76+
# ------------
77+
#
78+
# References
79+
# """"""""""
80+
#
81+
# The use of the following functions and methods is shown
82+
# in this example:
83+
84+
import matplotlib
85+
matplotlib.axes.Axes.imshow

examples/images_contours_and_fields/interpolation_methods.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
If `interpolation` is None, it defaults to the :rc:`image.interpolation`
1010
(default: ``'nearest'``). If the interpolation is ``'none'``, then no
1111
interpolation is performed for the Agg, ps and pdf backends. Other backends
12-
will default to ``'nearest'``.
12+
will default to ``'antialiased'``.
1313
1414
For the Agg, ps and pdf backends, ``interpolation = 'none'`` works well when a
1515
big image is scaled down, while ``interpolation = 'nearest'`` works well when
1616
a small image is scaled up.
17+
18+
See :doc:`/gallery/images_contours_and_fields/image_antialiasing` for a
19+
discussion on the default `interpolation="antialiased"` option.
1720
"""
1821

1922
import matplotlib.pyplot as plt

lib/matplotlib/axes/_axes.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5482,20 +5482,30 @@ def imshow(self, X, cmap=None, norm=None, aspect=None,
54825482
The interpolation method used. If *None*
54835483
:rc:`image.interpolation` is used, which defaults to 'nearest'.
54845484
5485-
Supported values are 'none', 'nearest', 'bilinear', 'bicubic',
5486-
'spline16', 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser',
5487-
'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc',
5488-
'lanczos'.
5485+
Supported values are 'none', 'antialiased', 'nearest', 'bilinear',
5486+
'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite',
5487+
'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell',
5488+
'sinc', 'lanczos'.
54895489
54905490
If *interpolation* is 'none', then no interpolation is performed
54915491
on the Agg, ps, pdf and svg backends. Other backends will fall back
54925492
to 'nearest'. Note that most SVG renders perform interpolation at
54935493
rendering and that the default interpolation method they implement
54945494
may differ.
54955495
5496+
If *interpolation* is the default 'antialiased', then 'nearest'
5497+
interpolation is used if the image is upsampled by more than a
5498+
factor of three (i.e. the number of display pixels is at least
5499+
three times the size of the data array). If the upsampling rate is
5500+
smaller than 3, or the image is downsampled, then 'hanning'
5501+
interpolation is used to act as an anti-aliasing filter, unless the
5502+
image happens to be upsampled by exactly a factor of two or one.
5503+
54965504
See
54975505
:doc:`/gallery/images_contours_and_fields/interpolation_methods`
5498-
for an overview of the supported interpolation methods.
5506+
for an overview of the supported interpolation methods, and
5507+
:doc:`/gallery/images_contours_and_fields/image_antialiasing` for
5508+
a discussion of image antialiasing.
54995509
55005510
Some interpolation methods require an additional radius parameter,
55015511
which can be set by *filterrad*. Additionally, the antigrain image

lib/matplotlib/image.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
# map interpolation strings to module constants
3535
_interpd_ = {
36+
'antialiased': _image.NEAREST, # this will use nearest or Hanning...
3637
'none': _image.NEAREST, # fall back to nearest when not supported
3738
'nearest': _image.NEAREST,
3839
'bilinear': _image.BILINEAR,
@@ -168,11 +169,34 @@ def _resample(
168169
allocating the output array and fetching the relevant properties from the
169170
Image object *image_obj*.
170171
"""
172+
173+
# decide if we need to apply anti-aliasing if the data is upsampled:
174+
# compare the number of displayed pixels to the number of
175+
# the data pixels.
176+
interpolation = image_obj.get_interpolation()
177+
if interpolation == 'antialiased':
178+
# don't antialias if upsampling by an integer number or
179+
# if zooming in more than a factor of 3
180+
shape = list(data.shape)
181+
if image_obj.origin == 'upper':
182+
shape[0] = 0
183+
dispx, dispy = transform.transform([shape[1], shape[0]])
184+
185+
if ((dispx > 3 * data.shape[1] or
186+
dispx == data.shape[1] or
187+
dispx == 2 * data.shape[1]) and
188+
(dispy > 3 * data.shape[0] or
189+
dispy == data.shape[0] or
190+
dispy == 2 * data.shape[0])):
191+
interpolation = 'nearest'
192+
else:
193+
interpolation = 'hanning'
194+
171195
out = np.zeros(out_shape + data.shape[2:], data.dtype) # 2D->2D, 3D->3D.
172196
if resample is None:
173197
resample = image_obj.get_resample()
174198
_image.resample(data, out, transform,
175-
_interpd_[image_obj.get_interpolation()],
199+
_interpd_[interpolation],
176200
resample,
177201
alpha,
178202
image_obj.get_filternorm(),
@@ -432,7 +456,6 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
432456
A_scaled += 0.1
433457
# resample the input data to the correct resolution and shape
434458
A_resampled = _resample(self, A_scaled, out_shape, t)
435-
436459
# done with A_scaled now, remove from namespace to be sure!
437460
del A_scaled
438461
# un-scale the resampled data to approximately the
@@ -690,9 +713,10 @@ def get_interpolation(self):
690713
"""
691714
Return the interpolation method the image uses when resizing.
692715
693-
One of 'nearest', 'bilinear', 'bicubic', 'spline16', 'spline36',
694-
'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom',
695-
'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', or 'none'.
716+
One of 'antialiased', 'nearest', 'bilinear', 'bicubic', 'spline16',
717+
'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric',
718+
'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos',
719+
or 'none'.
696720
697721
"""
698722
return self._interpolation
@@ -708,9 +732,9 @@ def set_interpolation(self, s):
708732
709733
Parameters
710734
----------
711-
s : {'nearest', 'bilinear', 'bicubic', 'spline16', 'spline36', \
712-
'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', 'gaussian', \
713-
'bessel', 'mitchell', 'sinc', 'lanczos', 'none'}
735+
s : {'antialiased', 'nearest', 'bilinear', 'bicubic', 'spline16',
736+
'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', \
737+
'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', 'none'}
714738
715739
"""
716740
if s is None:

lib/matplotlib/rcsetup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1100,7 +1100,7 @@ def _validate_linestyle(ls):
11001100
'mathtext.fallback_to_cm': [True, validate_bool],
11011101

11021102
'image.aspect': ['equal', validate_aspect], # equal, auto, a number
1103-
'image.interpolation': ['nearest', validate_string],
1103+
'image.interpolation': ['antialiased', validate_string],
11041104
'image.cmap': ['viridis', validate_string], # gray, jet, etc.
11051105
'image.lut': [256, validate_int], # lookup table
11061106
'image.origin': ['upper',

lib/matplotlib/tests/test_axes.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,7 @@ def test_nonfinite_limits():
915915

916916
@image_comparison(['imshow', 'imshow'], remove_text=True, style='mpl20')
917917
def test_imshow():
918+
matplotlib.rcParams['image.interpolation'] = 'nearest'
918919
# Create a NxN image
919920
N = 100
920921
(x, y) = np.indices((N, N))
@@ -936,6 +937,7 @@ def test_imshow():
936937
@image_comparison(['imshow_clip'], style='mpl20')
937938
def test_imshow_clip():
938939
# As originally reported by Gellule Xg <[email protected]>
940+
matplotlib.rcParams['image.interpolation'] = 'nearest'
939941

940942
# Create a NxN image
941943
N = 100
@@ -3733,6 +3735,8 @@ def test_subplot_key_hash():
37333735
remove_text=True, tol=0.07, style='default')
37343736
def test_specgram_freqs():
37353737
'''test axes.specgram in default (psd) mode with sinusoidal stimuli'''
3738+
matplotlib.rcParams['image.interpolation'] = 'nearest'
3739+
37363740
n = 1000
37373741
Fs = 10.
37383742

@@ -3785,6 +3789,9 @@ def test_specgram_freqs():
37853789
remove_text=True, tol=0.01, style='default')
37863790
def test_specgram_noise():
37873791
'''test axes.specgram in default (psd) mode with noise stimuli'''
3792+
3793+
matplotlib.rcParams['image.interpolation'] = 'nearest'
3794+
37883795
np.random.seed(0)
37893796

37903797
n = 1000
@@ -3832,6 +3839,9 @@ def test_specgram_noise():
38323839
remove_text=True, tol=0.07, style='default')
38333840
def test_specgram_magnitude_freqs():
38343841
'''test axes.specgram in magnitude mode with sinusoidal stimuli'''
3842+
3843+
matplotlib.rcParams['image.interpolation'] = 'nearest'
3844+
38353845
n = 1000
38363846
Fs = 10.
38373847

@@ -3887,6 +3897,8 @@ def test_specgram_magnitude_freqs():
38873897
remove_text=True, style='default')
38883898
def test_specgram_magnitude_noise():
38893899
'''test axes.specgram in magnitude mode with noise stimuli'''
3900+
matplotlib.rcParams['image.interpolation'] = 'nearest'
3901+
38903902
np.random.seed(0)
38913903

38923904
n = 1000
@@ -3933,6 +3945,8 @@ def test_specgram_magnitude_noise():
39333945
remove_text=True, tol=0.007, style='default')
39343946
def test_specgram_angle_freqs():
39353947
'''test axes.specgram in angle mode with sinusoidal stimuli'''
3948+
matplotlib.rcParams['image.interpolation'] = 'nearest'
3949+
39363950
n = 1000
39373951
Fs = 10.
39383952

@@ -3987,6 +4001,8 @@ def test_specgram_angle_freqs():
39874001
remove_text=True, style='default')
39884002
def test_specgram_noise_angle():
39894003
'''test axes.specgram in angle mode with noise stimuli'''
4004+
matplotlib.rcParams['image.interpolation'] = 'nearest'
4005+
39904006
np.random.seed(0)
39914007

39924008
n = 1000
@@ -4033,6 +4049,7 @@ def test_specgram_noise_angle():
40334049
remove_text=True, style='default')
40344050
def test_specgram_freqs_phase():
40354051
'''test axes.specgram in phase mode with sinusoidal stimuli'''
4052+
matplotlib.rcParams['image.interpolation'] = 'nearest'
40364053
n = 1000
40374054
Fs = 10.
40384055

@@ -4087,6 +4104,7 @@ def test_specgram_freqs_phase():
40874104
remove_text=True, style='default')
40884105
def test_specgram_noise_phase():
40894106
'''test axes.specgram in phase mode with noise stimuli'''
4107+
matplotlib.rcParams['image.interpolation'] = 'nearest'
40904108
np.random.seed(0)
40914109

40924110
n = 1000

0 commit comments

Comments
 (0)