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

Skip to content

Commit 2d1c785

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

File tree

7 files changed

+143
-62
lines changed

7 files changed

+143
-62
lines changed
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+
single dots 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 speciful 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.

lib/matplotlib/axes/_axes.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5482,17 +5482,25 @@ 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 three
5499+
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`
54985506
for an overview of the supported interpolation methods.

lib/matplotlib/image.py

Lines changed: 22 additions & 5 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': None, # this will use nearest or Hanning if downsampled
3637
'none': _image.NEAREST, # fall back to nearest when not supported
3738
'nearest': _image.NEAREST,
3839
'bilinear': _image.BILINEAR,
@@ -168,11 +169,28 @@ 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+
# do antialiasing....
181+
# compare the number of displayed pixels of the image to the number of
182+
# the data pixels.
183+
disppixels = int(transform.transform([data.shape[1], 0])[0])
184+
if (disppixels < 3 * data.shape[1] and disppixels != data.shape[1]):
185+
interpolation = 'hanning'
186+
else:
187+
interpolation = 'nearest'
188+
171189
out = np.zeros(out_shape + data.shape[2:], data.dtype) # 2D->2D, 3D->3D.
172190
if resample is None:
173191
resample = image_obj.get_resample()
174192
_image.resample(data, out, transform,
175-
_interpd_[image_obj.get_interpolation()],
193+
_interpd_[interpolation],
176194
resample,
177195
alpha,
178196
image_obj.get_filternorm(),
@@ -432,7 +450,6 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
432450
A_scaled += 0.1
433451
# resample the input data to the correct resolution and shape
434452
A_resampled = _resample(self, A_scaled, out_shape, t)
435-
436453
# done with A_scaled now, remove from namespace to be sure!
437454
del A_scaled
438455
# un-scale the resampled data to approximately the
@@ -708,9 +725,9 @@ def set_interpolation(self, s):
708725
709726
Parameters
710727
----------
711-
s : {'nearest', 'bilinear', 'bicubic', 'spline16', 'spline36', \
712-
'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', 'gaussian', \
713-
'bessel', 'mitchell', 'sinc', 'lanczos', 'none'}
728+
s : {'antialiased', 'nearest', 'bilinear', 'bicubic', 'spline16',
729+
'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', \
730+
'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', 'none'}
714731
715732
"""
716733
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: 78 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3764,21 +3764,24 @@ def test_specgram_freqs():
37643764
ax23 = fig2.add_subplot(3, 1, 3)
37653765

37663766
ax11.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3767-
pad_to=pad_to, sides='default')
3767+
pad_to=pad_to, sides='default', interpolation='nearest')
37683768
ax12.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3769-
pad_to=pad_to, sides='onesided')
3769+
pad_to=pad_to, sides='onesided', interpolation='nearest')
37703770
ax13.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3771-
pad_to=pad_to, sides='twosided')
3771+
pad_to=pad_to, sides='twosided', interpolation='nearest')
37723772

37733773
ax21.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
37743774
pad_to=pad_to, sides='default',
3775-
scale='linear', norm=matplotlib.colors.LogNorm())
3775+
scale='linear', norm=matplotlib.colors.LogNorm(),
3776+
interpolation='nearest')
37763777
ax22.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
37773778
pad_to=pad_to, sides='onesided',
3778-
scale='linear', norm=matplotlib.colors.LogNorm())
3779+
scale='linear', norm=matplotlib.colors.LogNorm(),
3780+
interpolation='nearest')
37793781
ax23.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
37803782
pad_to=pad_to, sides='twosided',
3781-
scale='linear', norm=matplotlib.colors.LogNorm())
3783+
scale='linear', norm=matplotlib.colors.LogNorm(),
3784+
interpolation='nearest')
37823785

37833786

37843787
@image_comparison(['specgram_noise.png', 'specgram_noise_linear.png'],
@@ -3810,21 +3813,24 @@ def test_specgram_noise():
38103813
ax23 = fig2.add_subplot(3, 1, 3)
38113814

38123815
ax11.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3813-
pad_to=pad_to, sides='default')
3816+
pad_to=pad_to, sides='default', interpolation='nearest')
38143817
ax12.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3815-
pad_to=pad_to, sides='onesided')
3818+
pad_to=pad_to, sides='onesided', interpolation='nearest')
38163819
ax13.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3817-
pad_to=pad_to, sides='twosided')
3820+
pad_to=pad_to, sides='twosided', interpolation='nearest')
38183821

38193822
ax21.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
38203823
pad_to=pad_to, sides='default',
3821-
scale='linear', norm=matplotlib.colors.LogNorm())
3824+
scale='linear', norm=matplotlib.colors.LogNorm(),
3825+
interpolation='nearest')
38223826
ax22.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
38233827
pad_to=pad_to, sides='onesided',
3824-
scale='linear', norm=matplotlib.colors.LogNorm())
3828+
scale='linear', norm=matplotlib.colors.LogNorm(),
3829+
interpolation='nearest')
38253830
ax23.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
38263831
pad_to=pad_to, sides='twosided',
3827-
scale='linear', norm=matplotlib.colors.LogNorm())
3832+
scale='linear', norm=matplotlib.colors.LogNorm(),
3833+
interpolation='nearest')
38283834

38293835

38303836
@image_comparison(['specgram_magnitude_freqs.png',
@@ -3865,21 +3871,27 @@ def test_specgram_magnitude_freqs():
38653871
ax23 = fig2.add_subplot(3, 1, 3)
38663872

38673873
ax11.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3868-
pad_to=pad_to, sides='default', mode='magnitude')
3874+
pad_to=pad_to, sides='default', mode='magnitude',
3875+
interpolation='nearest')
38693876
ax12.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3870-
pad_to=pad_to, sides='onesided', mode='magnitude')
3877+
pad_to=pad_to, sides='onesided', mode='magnitude',
3878+
interpolation='nearest')
38713879
ax13.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3872-
pad_to=pad_to, sides='twosided', mode='magnitude')
3880+
pad_to=pad_to, sides='twosided', mode='magnitude',
3881+
interpolation='nearest')
38733882

38743883
ax21.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
38753884
pad_to=pad_to, sides='default', mode='magnitude',
3876-
scale='linear', norm=matplotlib.colors.LogNorm())
3885+
scale='linear', norm=matplotlib.colors.LogNorm(),
3886+
interpolation='nearest')
38773887
ax22.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
38783888
pad_to=pad_to, sides='onesided', mode='magnitude',
3879-
scale='linear', norm=matplotlib.colors.LogNorm())
3889+
scale='linear', norm=matplotlib.colors.LogNorm(),
3890+
interpolation='nearest')
38803891
ax23.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
38813892
pad_to=pad_to, sides='twosided', mode='magnitude',
3882-
scale='linear', norm=matplotlib.colors.LogNorm())
3893+
scale='linear', norm=matplotlib.colors.LogNorm(),
3894+
interpolation='nearest')
38833895

38843896

38853897
@image_comparison(['specgram_magnitude_noise.png',
@@ -3912,21 +3924,27 @@ def test_specgram_magnitude_noise():
39123924
ax23 = fig2.add_subplot(3, 1, 3)
39133925

39143926
ax11.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3915-
pad_to=pad_to, sides='default', mode='magnitude')
3927+
pad_to=pad_to, sides='default', mode='magnitude',
3928+
interpolation='nearest')
39163929
ax12.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3917-
pad_to=pad_to, sides='onesided', mode='magnitude')
3930+
pad_to=pad_to, sides='onesided', mode='magnitude',
3931+
interpolation='nearest')
39183932
ax13.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3919-
pad_to=pad_to, sides='twosided', mode='magnitude')
3933+
pad_to=pad_to, sides='twosided', mode='magnitude',
3934+
interpolation='nearest')
39203935

39213936
ax21.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
39223937
pad_to=pad_to, sides='default', mode='magnitude',
3923-
scale='linear', norm=matplotlib.colors.LogNorm())
3938+
scale='linear', norm=matplotlib.colors.LogNorm(),
3939+
interpolation='nearest')
39243940
ax22.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
39253941
pad_to=pad_to, sides='onesided', mode='magnitude',
3926-
scale='linear', norm=matplotlib.colors.LogNorm())
3942+
scale='linear', norm=matplotlib.colors.LogNorm(),
3943+
interpolation='nearest')
39273944
ax23.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
39283945
pad_to=pad_to, sides='twosided', mode='magnitude',
3929-
scale='linear', norm=matplotlib.colors.LogNorm())
3946+
scale='linear', norm=matplotlib.colors.LogNorm(),
3947+
interpolation='nearest')
39303948

39313949

39323950
@image_comparison(['specgram_angle_freqs.png'],
@@ -3961,26 +3979,29 @@ def test_specgram_angle_freqs():
39613979
ax13 = fig1.add_subplot(3, 1, 3)
39623980

39633981
ax11.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3964-
pad_to=pad_to, sides='default', mode='angle')
3982+
pad_to=pad_to, sides='default', mode='angle',
3983+
interpolation='nearest')
39653984
ax12.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3966-
pad_to=pad_to, sides='onesided', mode='angle')
3985+
pad_to=pad_to, sides='onesided', mode='angle',
3986+
interpolation='nearest')
39673987
ax13.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
3968-
pad_to=pad_to, sides='twosided', mode='angle')
3988+
pad_to=pad_to, sides='twosided', mode='angle',
3989+
interpolation='nearest')
39693990

39703991
with pytest.raises(ValueError):
39713992
ax11.specgram(y, NFFT=NFFT, Fs=Fs,
39723993
noverlap=noverlap, pad_to=pad_to, sides='default',
3973-
mode='phase', scale='dB')
3994+
mode='phase', scale='dB', interpolation='nearest')
39743995

39753996
with pytest.raises(ValueError):
39763997
ax12.specgram(y, NFFT=NFFT, Fs=Fs,
39773998
noverlap=noverlap, pad_to=pad_to, sides='onesided',
3978-
mode='phase', scale='dB')
3999+
mode='phase', scale='dB', interpolation='nearest')
39794000

39804001
with pytest.raises(ValueError):
39814002
ax13.specgram(y, NFFT=NFFT, Fs=Fs,
39824003
noverlap=noverlap, pad_to=pad_to, sides='twosided',
3983-
mode='phase', scale='dB')
4004+
mode='phase', scale='dB', interpolation='nearest')
39844005

39854006

39864007
@image_comparison(['specgram_angle_noise.png'],
@@ -4007,26 +4028,29 @@ def test_specgram_noise_angle():
40074028
ax13 = fig1.add_subplot(3, 1, 3)
40084029

40094030
ax11.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
4010-
pad_to=pad_to, sides='default', mode='angle')
4031+
pad_to=pad_to, sides='default', mode='angle',
4032+
interpolation='nearest')
40114033
ax12.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
4012-
pad_to=pad_to, sides='onesided', mode='angle')
4034+
pad_to=pad_to, sides='onesided', mode='angle',
4035+
interpolation='nearest')
40134036
ax13.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
4014-
pad_to=pad_to, sides='twosided', mode='angle')
4037+
pad_to=pad_to, sides='twosided', mode='angle',
4038+
interpolation='nearest')
40154039

40164040
with pytest.raises(ValueError):
40174041
ax11.specgram(y, NFFT=NFFT, Fs=Fs,
40184042
noverlap=noverlap, pad_to=pad_to, sides='default',
4019-
mode='phase', scale='dB')
4043+
mode='phase', scale='dB', interpolation='nearest')
40204044

40214045
with pytest.raises(ValueError):
40224046
ax12.specgram(y, NFFT=NFFT, Fs=Fs,
40234047
noverlap=noverlap, pad_to=pad_to, sides='onesided',
4024-
mode='phase', scale='dB')
4048+
mode='phase', scale='dB', interpolation='nearest')
40254049

40264050
with pytest.raises(ValueError):
40274051
ax13.specgram(y, NFFT=NFFT, Fs=Fs,
40284052
noverlap=noverlap, pad_to=pad_to, sides='twosided',
4029-
mode='phase', scale='dB')
4053+
mode='phase', scale='dB', interpolation='nearest')
40304054

40314055

40324056
@image_comparison(['specgram_phase_freqs.png'],
@@ -4061,26 +4085,29 @@ def test_specgram_freqs_phase():
40614085
ax13 = fig1.add_subplot(3, 1, 3)
40624086

40634087
ax11.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
4064-
pad_to=pad_to, sides='default', mode='phase')
4088+
pad_to=pad_to, sides='default', mode='phase',
4089+
interpolation='nearest')
40654090
ax12.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
4066-
pad_to=pad_to, sides='onesided', mode='phase')
4091+
pad_to=pad_to, sides='onesided', mode='phase',
4092+
interpolation='nearest')
40674093
ax13.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
4068-
pad_to=pad_to, sides='twosided', mode='phase')
4094+
pad_to=pad_to, sides='twosided', mode='phase',
4095+
interpolation='nearest')
40694096

40704097
with pytest.raises(ValueError):
40714098
ax11.specgram(y, NFFT=NFFT, Fs=Fs,
40724099
noverlap=noverlap, pad_to=pad_to, sides='default',
4073-
mode='phase', scale='dB')
4100+
mode='phase', scale='dB', interpolation='nearest')
40744101

40754102
with pytest.raises(ValueError):
40764103
ax12.specgram(y, NFFT=NFFT, Fs=Fs,
40774104
noverlap=noverlap, pad_to=pad_to, sides='onesided',
4078-
mode='phase', scale='dB')
4105+
mode='phase', scale='dB', interpolation='nearest')
40794106

40804107
with pytest.raises(ValueError):
40814108
ax13.specgram(y, NFFT=NFFT, Fs=Fs,
40824109
noverlap=noverlap, pad_to=pad_to, sides='twosided',
4083-
mode='phase', scale='dB')
4110+
mode='phase', scale='dB', interpolation='nearest')
40844111

40854112

40864113
@image_comparison(['specgram_phase_noise.png'],
@@ -4107,26 +4134,29 @@ def test_specgram_noise_phase():
41074134
ax13 = fig1.add_subplot(3, 1, 3)
41084135

41094136
ax11.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
4110-
pad_to=pad_to, sides='default', mode='phase')
4137+
pad_to=pad_to, sides='default', mode='phase',
4138+
interpolation='nearest')
41114139
ax12.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
4112-
pad_to=pad_to, sides='onesided', mode='phase')
4140+
pad_to=pad_to, sides='onesided', mode='phase',
4141+
interpolation='nearest')
41134142
ax13.specgram(y, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
4114-
pad_to=pad_to, sides='twosided', mode='phase')
4143+
pad_to=pad_to, sides='twosided', mode='phase',
4144+
interpolation='nearest')
41154145

41164146
with pytest.raises(ValueError):
41174147
ax11.specgram(y, NFFT=NFFT, Fs=Fs,
41184148
noverlap=noverlap, pad_to=pad_to, sides='default',
4119-
mode='phase', scale='dB')
4149+
mode='phase', scale='dB', interpolation='nearest')
41204150

41214151
with pytest.raises(ValueError):
41224152
ax12.specgram(y, NFFT=NFFT, Fs=Fs,
41234153
noverlap=noverlap, pad_to=pad_to, sides='onesided',
4124-
mode='phase', scale='dB')
4154+
mode='phase', scale='dB', interpolation='nearest')
41254155

41264156
with pytest.raises(ValueError):
41274157
ax13.specgram(y, NFFT=NFFT, Fs=Fs,
41284158
noverlap=noverlap, pad_to=pad_to, sides='twosided',
4129-
mode='phase', scale='dB')
4159+
mode='phase', scale='dB', interpolation='nearest')
41304160

41314161

41324162
@image_comparison(['psd_freqs.png'], remove_text=True)

0 commit comments

Comments
 (0)