Description
Bug summary
Under certain conditions pyplot's imsave() function will fail, with the underlying PIL library throwing an "array is not C-contiguous" error (while the array provided to imsave is C-contiguous).
Code for reproduction
import numpy as np
import matplotlib.pyplot as plt
result = np.zeros((100, 100, 4), dtype='uint8')
print(result.flags) # the ndarray is actually C-contiguous
plt.imsave(fname="test_upper.png", arr=result, format="png", origin="upper")# no problem
plt.imsave(fname="test_lower.png", arr=result, format="png", origin="lower")# error
Actual outcome
File [/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/matplotlib/pyplot.py:2200](http://localhost:8888/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/matplotlib/pyplot.py#line=2199), in imsave(fname, arr, **kwargs)
2198 @_copy_docstring_and_deprecators(matplotlib.image.imsave)
2199 def imsave(fname, arr, **kwargs):
-> 2200 return matplotlib.image.imsave(fname, arr, **kwargs)
File [/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/matplotlib/image.py:1659](http://localhost:8888/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/matplotlib/image.py#line=1658), in imsave(fname, arr, vmin, vmax, cmap, format, origin, dpi, metadata, pil_kwargs)
1657 pil_kwargs = pil_kwargs.copy()
1658 pil_shape = (rgba.shape[1], rgba.shape[0])
-> 1659 image = PIL.Image.frombuffer(
1660 "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1)
1661 if format == "png":
1662 # Only use the metadata kwarg if pnginfo is not set, because the
1663 # semantics of duplicate keys in pnginfo is unclear.
1664 if "pnginfo" in pil_kwargs:
File [/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/PIL/Image.py:3020](http://localhost:8888/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/PIL/Image.py#line=3019), in frombuffer(mode, size, data, decoder_name, *args)
3018 if args[0] in _MAPMODES:
3019 im = new(mode, (1, 1))
-> 3020 im = im._new(core.map_buffer(data, size, decoder_name, 0, args))
3021 if mode == "P":
3022 from . import ImagePalette
ValueError: ndarray is not C-contiguous
Expected outcome
saved image
Additional information
- The input must be an input of size MxNx4. RGBA fails but RGB works.
- The dtype must be uint8.
- origin must be set to "lower"
- Image file type can be set to png, jpg, gif, or tiff, and all trigger the same issue. It's likely not a codec-specific problem.
- The data in the image does not matter.
Suggestion from stackoverflow user Nick ODell:
[https://github.com/matplotlib/matplotlib/blob/v3.8.3/lib/matplotlib/image.py#L1605](link to code)
If origin == "lower", then the array is reversed in a zero-copy fashion. If this happens, then arr is no longer C contiguous. It then uses ScalarMappable to convert to rgba. However, if the input is already in rgba, it does not copy it. Because of this, using RGB masks the bug, because the copy would be C contiguous.
It then calls PIL.Image.frombuffer, which appears to assume that its input is C contiguous. (Pillow doesn't appear to document this assumption, so this may actually be a Pillow bug?)
Operating system
No response
Matplotlib Version
3.8.3
Matplotlib Backend
No response
Python version
No response
Jupyter version
No response
Installation
None