Skip to content
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

Image.putdata() NumPy usability suggestion #5858

Closed
rlevine opened this issue Nov 29, 2021 · 6 comments · Fixed by #5910
Closed

Image.putdata() NumPy usability suggestion #5858

rlevine opened this issue Nov 29, 2021 · 6 comments · Fixed by #5910
Labels

Comments

@rlevine
Copy link

rlevine commented Nov 29, 2021

This could be a feature request, an ask for a doc change or for better error reporting.

When I grab an image, suck it into a numpy array, process it, and then spit it back into an image, I hit what appears to be a silent fail when the data given to Image.putdata() is a 2D numpy array instead of 1D (nominally a sequence), or is a float array instead of uint8.

My naive expectation was to do this:

image_array = np.array(source_image)
# ...do something to the data via numpy that might inadvertently 
# change it to float64, still with int-equivalent values...
new_image = Image.new('P', (source_image.size))
new_image.putdata(image_array)

If I don't coerce the array to uint8 and 1D, I still get an image, but with all values set to 0, and Pillow is silent on what (again, naively) appears to be a failure.

(I now know new_image.putdata(image_array.ravel().astype(int)) works, but it wan't obvious. :)

While making the docs more precise or adding better error reporting are both easier responses, it would seem that this isn't an uncommon use case, and it might be a nice ease-of-use change to have putdata accept a 2D array if it's the same size as the image, and also to coerce the data to ints, as a better silent option.

I'm running Pillow 8.4.0, python 3.10 on osx Mojave 10.14.6.

Thanks!


Test code:

from PIL import Image
import numpy as np


palette_data = [
		0, 0, 192,
		0, 33, 76,
		0, 192, 0,
		0, 192, 192,
		9, 9, 9,
		19, 19, 19,
		29, 29, 29,
		50, 0, 106,
		192, 0, 0,
		192, 0, 192,
		192, 192, 0,
		192, 192, 192,
		255, 255, 255
		]

image_data = np.array([
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [11., 11., 11., 11., 10., 10., 10., 10., 3., 3., 3., 3., 2., 2., 2., 2., 9., 9., 9., 9., 8., 8., 8., 8., 0., 0., 0., 0.],
                      [ 0., 0., 0., 0., 5., 5., 5., 5., 9., 9., 9., 9., 5., 5., 5., 5., 3., 3., 3., 3., 5., 5., 5., 5., 11., 11., 11., 11.],
                      [ 0., 0., 0., 0., 5., 5., 5., 5., 9., 9., 9., 9., 5., 5., 5., 5., 3., 3., 3., 3., 5., 5., 5., 5., 11., 11., 11., 11.],
                      [ 0., 0., 0., 0., 5., 5., 5., 5., 9., 9., 9., 9., 5., 5., 5., 5., 3., 3., 3., 3., 5., 5., 5., 5., 11., 11., 11., 11.],
                      [ 1., 1., 1., 1., 1., 12., 12., 12., 12., 12., 7., 7., 7., 7., 7., 5., 5., 5., 5., 5., 4., 5., 5., 6., 5., 5., 5., 5.],
                      [ 1., 1., 1., 1., 1., 12., 12., 12., 12., 12., 7., 7., 7., 7., 7., 5., 5., 5., 5., 5., 4., 5., 5., 6., 5., 5., 5., 5.],
                      [ 1., 1., 1., 1., 1., 12., 12., 12., 12., 12., 7., 7., 7., 7., 7., 5., 5., 5., 5., 5., 4., 5., 5., 6., 5., 5., 5., 5.],
                      [ 1., 1., 1., 1., 1., 12., 12., 12., 12., 12., 7., 7., 7., 7., 7., 5., 5., 5., 5., 5., 4., 5., 5., 6., 5., 5., 5., 5.],
                      [ 1., 1., 1., 1., 1., 12., 12., 12., 12., 12., 7., 7., 7., 7., 7., 5., 5., 5., 5., 5., 4., 5., 5., 6., 5., 5., 5., 5.],
                      [ 1., 1., 1., 1., 1., 12., 12., 12., 12., 12., 7., 7., 7., 7., 7., 5., 5., 5., 5., 5., 4., 5., 5., 6., 5., 5., 5., 5.],
                      [ 1., 1., 1., 1., 1., 12., 12., 12., 12., 12., 7., 7., 7., 7., 7., 5., 5., 5., 5., 5., 4., 5., 5., 6., 5., 5., 5., 5.]
                      ], dtype=np.float64)

new_image = Image.new('P', (28, 28), color=None)
new_image.putpalette(palette_data)

new_image.putdata(image_data.astype(int))
# Resulting image data is all zeroes.
new_image.show()
new_image.save('int_data_array.png')

new_image.putdata(image_data.ravel())
# Resulting image data is all zeroes.
new_image.show()
new_image.save('float_data_sequence.png')

new_image.putdata(image_data.ravel().astype(int))
# Resulting image data is correct, as expected.
new_image.show()
new_image.save('int_data_sequence.png')

Correct image:

int_data_sequence

Bad image cases:

int_data_array

float_data_sequence

@radarhere radarhere changed the title Image.putdata() numpy usability suggestion Image.putdata() NumPy usability suggestion Nov 29, 2021
@radarhere
Copy link
Member

This isn't your main point - but when I run your code on my machine, float_data_sequence.png appears correctly.

@rlevine
Copy link
Author

rlevine commented Nov 29, 2021

Interesting. The attached files are literally the result of running the test here. I'm running numpy 1.21.4.

I took a chance on stuffing data back into the same image for each variation.

Separating them out make any difference?

new_image_1 = Image.new('P', (28, 28), color=None)
new_image_2 = Image.new('P', (28, 28), color=None)
new_image_3 = Image.new('P', (28, 28), color=None)
new_image_1.putpalette(palette_data)
new_image_2.putpalette(palette_data)
new_image_3.putpalette(palette_data)

# Resulting image data is all zeroes.
new_image_1.putdata(image_data.astype(int))
new_image_1.show()
new_image_1.save('int_data_array.png')

# Resulting image data is all zeroes.
new_image_2.putdata(image_data.ravel())
new_image_2.show()
new_image_2.save('float_data_sequence.png')

# Resulting image data is correct, as expected.
new_image_3.putdata(image_data.ravel().astype(int))
new_image_3.show()
new_image_3.save('int_data_sequence.png')

@radarhere
Copy link
Member

radarhere commented Dec 27, 2021

float_data_sequence.png still works correctly on my machine with Python 3.8 using the code from your second post. I've found that the problem there starts with Python 3.10. PR #5910 fixes that, so that float_data_sequence.png should work in Python 3.10.

@radarhere
Copy link
Member

radarhere commented Dec 27, 2021

I think the ideal method for this scenario is actually use fromarray

new_image = Image.fromarray(image_data).convert("L")
new_image.putpalette(palette_data)
new_image.save('out.png')

put_data, as you have noted, is intended for 1 dimensional sequences. So PR #5910 also updates the documentation, clarifying that the array you've passed in should be flattened.

Let us know what you think.

@radarhere
Copy link
Member

I've added an error for this situation to #5910 as well.

@rlevine
Copy link
Author

rlevine commented Dec 28, 2021

Nice. This hits all my issues. Thanks for the suggestion of using Image.fromarray() - it's a good simplification. I realized I've been using putdata because fromarray was added after I started using PIL, and I'm just now refactoring this code. *sigh*

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants