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

Conversion of greyscale image to NumPy causes color inversion #7045

Closed
Abe404 opened this issue Mar 30, 2023 · 7 comments
Closed

Conversion of greyscale image to NumPy causes color inversion #7045

Abe404 opened this issue Mar 30, 2023 · 7 comments
Labels

Comments

@Abe404
Copy link

Abe404 commented Mar 30, 2023

When I convert an image to a numpy array, the colors are inverted i.e light becomes dark and dark becomes light. Please see example images attached.

The following code is a minimal example that reproduces the problem.

from PIL import Image
import matplotlib.pyplot as plt
fpath = 'CHNCXR_0024_0.png'
im = Image.open(fpath)
plt.imshow(im) # this is OK
plt.show()
im = np.array(im) # image is now inverted
plt.imshow(im, cmap='gray') 
plt.show()
im = Image.fromarray(im)
im.save('inverted.png')  # the problem is not with matplotlib - see for yourself in a saved PNG

I was able to find a workaround by converting the image data to RGB (im.convert("RGB")) before converting to numpy but it seems there is a bug in Pillow here when converting some greyscale images to numpy.

Thanks!
CHNCXR_0024_0
inverted

@radarhere radarhere changed the title Conversion of greyscale image to numpy causes color inversion Conversion of greyscale image to NumPy causes color inversion Mar 30, 2023
@radarhere
Copy link
Member

The image that you are opening is in P mode.

from PIL import Image
fpath = 'CHNCXR_0024_0.png'
im = Image.open(fpath)
print(im.mode)  # P

This means that the value of each pixel is an index, referring to a palette of colors.

When you convert the image to NumPy, it passes through the value of each pixel... but not the palette. This is just part of the nature of a P mode image - an 2D representation of its pixel values is not the end of the story.

You've correctly figured out that converting the image to RGB works around this, by abandoning the idea of a palette.

@Abe404
Copy link
Author

Abe404 commented Mar 30, 2023

Thanks for the swift response and clarification.

I had no idea idea about P mode images.

OK so if I understand correctly:

I was not actually working with greyscale images but RGB P-mode images? (potentially used to save space).
Converting to numpy inverted the image because the index was interpreted as a grey value, i.e converting from P-mode images to numpy converts their index and not the actual color/grey value.

When you convert the image to NumPy, it passes through the value of each pixel... but not the palette.

My gut feeling is that it would make more sense for Pillow to convert the image to NumPy in a way that provides the indexed pixel value (i.e looks up the palette value). But I think you know what is best here based on other peoples use cases and maintenance etc. Perhaps doing what I suggest could also cause problems for people looking to access the P-mode indices.

Thanks again for your swift response and clarification.

@Abe404 Abe404 closed this as completed Mar 30, 2023
@Abe404
Copy link
Author

Abe404 commented Mar 30, 2023

BTW I recorded myself trying to figure out the issue: https://www.twitch.tv/videos/1779761479 it took around 45 minutes or so.

Perhaps a warning from Pillow could could help during the NumPy conversion.

@wiredfool
Copy link
Member

The numpy conversion really needs to be as low level as possible -- many people use it as the reference to what's actually happening to images in Pillow. Ideally it would be a shared buffer, potentially rw, but more safely ro, so that there could be a zero cost sharing of data.

The numpy interface really is "get image bytes" but with a different client api.

@Yay295
Copy link
Contributor

Yay295 commented Mar 30, 2023

You can do this to convert your image from P/PA:

im = Image.open(fpath)
if im.mode in ('P','PA'):
    im = im.convert('L')

This will remove any transparency and color, but it doesn't seem like you need either in your case. You could convert to RGBA instead if you want those.

@Abe404
Copy link
Author

Abe404 commented Mar 30, 2023

Thanks both. I like the im.convert('L') option (#7045 (comment)) as it is more explicit, but for now will just leave a comment in my code linking to this issue and explaining why I convert to RGB with PIL.

This PIL to numpy conversion is more low level than I had assumed as when converting to numpy I expected to have PIL just handle this palette lookup stuff without me having to worry about it (I was not previously aware of P mode images). This is a case where things did not work how I expected (I thought converting a PIL image to numpy would give me the image in terms of how it normally looks when you open the image in an image viewer).

That said, I appreciate there's different use-cases and it's not that easy to make an intuitive API for everyone as different users will have different expectations about how something should work. I also appreciate sometimes you need to expose this low level stuff to the users. This problem was just very unexpected for me as the code had been working for a while (I guess I just hadn't encountered any of these P mode images before since I had converted to PIL for image reading).

Please let me know if you think there's anything else I need to be aware of when converting PIL images to numpy (in terms of image appearance changing in potentially unexpected ways). My hope is that conversion to RGB will handle it now. I need to work with any type of PNG, TIFF or JPEG image in my application. For now I don't need alpha channel and convert all images to RGB to make things consistent in the rest of the code.

Thanks again for you super swift and informative responses. It's interesting to learn about these aspects of PNG and PIL.

@radarhere
Copy link
Member

I've created PR #7049, documenting that the palette will be lost when converting to NumPy.

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

No branches or pull requests

4 participants