Skip to content

Raise warning and downsample if data given to _image.resample is too large #19368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/api/next_api_changes/behavior/19368-DS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Large ``imshow`` images are now downsampled
-------------------------------------------
When showing an image using `~matplotlib.axes.Axes.imshow` that has more than
:math:`2^{24}` columns or :math:`2^{23}` rows, the image will now be downsampled
to below this resolution before being resampled for display by the AGG renderer.
Previously such a large image would be shown incorrectly. To prevent this
downsampling and the warning it raises, manually downsample your data before
handing it to `~matplotlib.axes.Axes.imshow`
18 changes: 17 additions & 1 deletion lib/matplotlib/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
import logging
from pathlib import Path
import warnings

import numpy as np
import PIL.PngImagePlugin
Expand Down Expand Up @@ -166,7 +167,22 @@ def _resample(
allocating the output array and fetching the relevant properties from the
Image object *image_obj*.
"""

# AGG can only handle coordinates smaller than 24-bit signed integers,
# so raise errors if the input data is larger than _image.resample can
# handle.
msg = ('Data with more than {n} cannot be accurately displayed. '
'Downsampling to less than {n} before displaying. '
'To remove this warning, manually downsample your data.')
if data.shape[1] > 2**23:
warnings.warn(msg.format(n='2**23 columns'))
step = int(np.ceil(data.shape[1] / 2**23))
data = data[:, ::step]
transform = Affine2D().scale(step, 1) + transform
if data.shape[0] > 2**24:
warnings.warn(msg.format(n='2**24 rows'))
step = int(np.ceil(data.shape[0] / 2**24))
data = data[::step, :]
transform = Affine2D().scale(1, step) + transform
# decide if we need to apply anti-aliasing if the data is upsampled:
# compare the number of displayed pixels to the number of
# the data pixels.
Expand Down
40 changes: 40 additions & 0 deletions lib/matplotlib/tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1376,3 +1376,43 @@ def test_rgba_antialias():
# alternating red and blue stripes become purple
axs[3].imshow(aa, interpolation='antialiased', interpolation_stage='rgba',
cmap=cmap, vmin=-1.2, vmax=1.2)


# We check for the warning with a draw() in the test, but we also need to
# filter the warning as it is emitted by the figure test decorator
@pytest.mark.filterwarnings(r'ignore:Data with more than .* '
'cannot be accurately displayed')
@pytest.mark.parametrize('origin', ['upper', 'lower'])
@pytest.mark.parametrize(
'dim, size, msg', [['row', 2**23, r'2\*\*23 columns'],
['col', 2**24, r'2\*\*24 rows']])
@check_figures_equal(extensions=('png', ))
def test_large_image(fig_test, fig_ref, dim, size, msg, origin):
# Check that Matplotlib downsamples images that are too big for AGG
# See issue #19276. Currently the fix only works for png output but not
# pdf or svg output.
ax_test = fig_test.subplots()
ax_ref = fig_ref.subplots()

array = np.zeros((1, size + 2))
array[:, array.size // 2:] = 1
if dim == 'col':
array = array.T
im = ax_test.imshow(array, vmin=0, vmax=1,
aspect='auto', extent=(0, 1, 0, 1),
interpolation='none',
origin=origin)

with pytest.warns(UserWarning,
match=f'Data with more than {msg} cannot be '
'accurately displayed.'):
fig_test.canvas.draw()

array = np.zeros((1, 2))
array[:, 1] = 1
if dim == 'col':
array = array.T
im = ax_ref.imshow(array, vmin=0, vmax=1, aspect='auto',
extent=(0, 1, 0, 1),
interpolation='none',
origin=origin)