From 07226ab39ed981cfd16911933da538057a06d53a Mon Sep 17 00:00:00 2001 From: Yuepeng Gu Date: Thu, 28 Nov 2024 16:18:04 -0500 Subject: [PATCH 1/6] Fixed issue #29183 such that imsave() handled list-of-list-of-list and saved the correct image. --- build.out | 0 lib/matplotlib/axes/_axes.py | 4 ++++ lib/matplotlib/image.py | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 build.out diff --git a/build.out b/build.out new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index c55f77811859..49e76c304320 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5869,6 +5869,8 @@ def imshow(self, X, cmap=None, norm=None, *, aspect=None, `~matplotlib.pyplot.imshow` expects RGB images adopting the straight (unassociated) alpha representation. """ + print("Entering imshow") + im = mimage.AxesImage(self, cmap=cmap, norm=norm, colorizer=colorizer, interpolation=interpolation, origin=origin, extent=extent, filternorm=filternorm, @@ -5896,6 +5898,8 @@ def imshow(self, X, cmap=None, norm=None, *, aspect=None, # to tightly fit the image, regardless of dataLim. im.set_extent(im.get_extent()) + ##### TODO + self.add_image(im) return im diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 53ea452f4c84..b0bfd1f3950b 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -28,6 +28,8 @@ Affine2D, BboxBase, Bbox, BboxTransform, BboxTransformTo, IdentityTransform, TransformedBbox) +import matplotlib.image as mimage + _log = logging.getLogger(__name__) # map interpolation strings to module constants @@ -1093,6 +1095,7 @@ def set_data(self, x, y, A): """ Set the grid for the pixel centers, and the pixel values. + Parameters ---------- x, y : 1D array-like @@ -1582,6 +1585,20 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, _api.check_in_list(('upper', 'lower'), origin=origin) if origin == "lower": arr = arr[::-1] + + # This specifically handled list-of-list-of-list + if (isinstance(arr, list)): + fig = Figure() + ax = fig.add_axes([0, 0, 1, 1], + aspect='auto', + frameon=False, + xticks=[], + yticks=[]) + im = mimage.AxesImage(ax, cmap=cmap) + im.set_data(arr) + im._scale_norm(None, vmin, vmax) + arr = im.get_array() + if (isinstance(arr, memoryview) and arr.format == "B" and arr.ndim == 3 and arr.shape[-1] == 4): # Such an ``arr`` would also be handled fine by sm.to_rgba below @@ -1630,6 +1647,7 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, background = PIL.Image.new("RGB", pil_shape, color) background.paste(image, image) image = background + pil_kwargs.setdefault("format", format) pil_kwargs.setdefault("dpi", (dpi, dpi)) image.save(fname, **pil_kwargs) From e491c52b38c7415ed46edc5526462551d0ceee09 Mon Sep 17 00:00:00 2001 From: Yuepeng Gu Date: Thu, 28 Nov 2024 16:20:23 -0500 Subject: [PATCH 2/6] Removed debugging lines and prints. --- lib/matplotlib/axes/_axes.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 49e76c304320..c7b814ab024a 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5869,7 +5869,6 @@ def imshow(self, X, cmap=None, norm=None, *, aspect=None, `~matplotlib.pyplot.imshow` expects RGB images adopting the straight (unassociated) alpha representation. """ - print("Entering imshow") im = mimage.AxesImage(self, cmap=cmap, norm=norm, colorizer=colorizer, interpolation=interpolation, origin=origin, @@ -5898,8 +5897,6 @@ def imshow(self, X, cmap=None, norm=None, *, aspect=None, # to tightly fit the image, regardless of dataLim. im.set_extent(im.get_extent()) - ##### TODO - self.add_image(im) return im From e4bdd84204ee1502b8c197950290b09b9066da69 Mon Sep 17 00:00:00 2001 From: Yuepeng Gu Date: Thu, 28 Nov 2024 18:10:06 -0500 Subject: [PATCH 3/6] Added one roundtrip unit test case for imsave() together with imread(). --- lib/matplotlib/tests/test_image.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 24a0ab929bbf..9f924d6ede94 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -196,6 +196,22 @@ def test_imsave_rgba_origin(origin): def test_imsave_fspath(fmt): plt.imsave(Path(os.devnull), np.array([[0, 1]]), format=fmt) +def test_imsave_roundtrip(): + # Initializing RGBA data + original_img = np.array( + [[[255, 0, 0, 1], [0, 255, 0, 1], [0, 0, 255, 1]], + [[0, 255, 0, 1], [0, 255, 0, 1], [0, 0, 0, 1]], + [[0, 0, 255, 1], [0, 0, 255, 1], [0, 0, 0, 1]]], dtype=np.uint8) + + buff = io.BytesIO() + plt.imsave(buff, original_img, format="png") + buff.seek(0) + read_img = plt.imread(buff) + + # Need to multiplied by 255 to adjust for normalized process imread() + read_img = (255*read_img).astype('uint8') + + assert_array_equal(original_img, read_img) def test_imsave_color_alpha(): # Test that imsave accept arrays with ndim=3 where the third dimension is From c16b4b0ced88ce048461b1da991253fda62f5960 Mon Sep 17 00:00:00 2001 From: Yuepeng Gu Date: Thu, 28 Nov 2024 22:01:32 -0500 Subject: [PATCH 4/6] Changed functions names and added comment, also ran flake8 linter locally to ensure it passes checks on github. --- lib/matplotlib/image.py | 1 + lib/matplotlib/tests/test_image.py | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index b0bfd1f3950b..894ec8bc07cf 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1587,6 +1587,7 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, arr = arr[::-1] # This specifically handled list-of-list-of-list + # Produced an image instance using the data from arr and do scaling if (isinstance(arr, list)): fig = Figure() ax = fig.add_axes([0, 0, 1, 1], diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 9f924d6ede94..b02d005bb841 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -196,22 +196,25 @@ def test_imsave_rgba_origin(origin): def test_imsave_fspath(fmt): plt.imsave(Path(os.devnull), np.array([[0, 1]]), format=fmt) -def test_imsave_roundtrip(): - # Initializing RGBA data - original_img = np.array( - [[[255, 0, 0, 1], [0, 255, 0, 1], [0, 0, 255, 1]], - [[0, 255, 0, 1], [0, 255, 0, 1], [0, 0, 0, 1]], - [[0, 0, 255, 1], [0, 0, 255, 1], [0, 0, 0, 1]]], dtype=np.uint8) +def test_imsave_python_vanilla_list(): + # Initializing RGBA data + # Instead of testing numpy array, use python list + input_img = [ + [[255, 0, 0, 1], [0, 255, 0, 1], [0, 0, 255, 1]], + [[0, 255, 0, 1], [0, 255, 0, 1], [0, 0, 0, 1]], + [[0, 0, 255, 1], [0, 0, 255, 1], [0, 0, 0, 1]] + ] buff = io.BytesIO() - plt.imsave(buff, original_img, format="png") + plt.imsave(buff, input_img, format="png") buff.seek(0) read_img = plt.imread(buff) - # Need to multiplied by 255 to adjust for normalized process imread() + # Need to multiply by 255 to adjust for normalization in imread() read_img = (255*read_img).astype('uint8') - assert_array_equal(original_img, read_img) + assert_array_equal(np.array(input_img), read_img) + def test_imsave_color_alpha(): # Test that imsave accept arrays with ndim=3 where the third dimension is From 3faf4e2fadee28b4ebe6d705b790f591c9368f18 Mon Sep 17 00:00:00 2001 From: Yuepeng Gu Date: Fri, 6 Dec 2024 13:05:53 -0500 Subject: [PATCH 5/6] Removed confusing import mimage. --- lib/matplotlib/image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 894ec8bc07cf..4afa75ded250 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -28,7 +28,6 @@ Affine2D, BboxBase, Bbox, BboxTransform, BboxTransformTo, IdentityTransform, TransformedBbox) -import matplotlib.image as mimage _log = logging.getLogger(__name__) @@ -1595,7 +1594,7 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, frameon=False, xticks=[], yticks=[]) - im = mimage.AxesImage(ax, cmap=cmap) + im = AxesImage(ax, cmap=cmap) im.set_data(arr) im._scale_norm(None, vmin, vmax) arr = im.get_array() From 0df1f510a11575420952847d36a98c540547ba52 Mon Sep 17 00:00:00 2001 From: Yuepeng Gu Date: Fri, 13 Dec 2024 22:45:59 -0500 Subject: [PATCH 6/6] Cleaned commit history, and modified code according to reviewer's suggestions. --- lib/matplotlib/image.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 4afa75ded250..2466ae9eaa0f 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1585,19 +1585,8 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, if origin == "lower": arr = arr[::-1] - # This specifically handled list-of-list-of-list - # Produced an image instance using the data from arr and do scaling if (isinstance(arr, list)): - fig = Figure() - ax = fig.add_axes([0, 0, 1, 1], - aspect='auto', - frameon=False, - xticks=[], - yticks=[]) - im = AxesImage(ax, cmap=cmap) - im.set_data(arr) - im._scale_norm(None, vmin, vmax) - arr = im.get_array() + arr = np.asarray(arr, dtype=np.uint8) if (isinstance(arr, memoryview) and arr.format == "B" and arr.ndim == 3 and arr.shape[-1] == 4):