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

Saving grayscale 16 bit PNG fails #2970

Closed
tazr opened this issue Jan 23, 2018 · 7 comments
Closed

Saving grayscale 16 bit PNG fails #2970

tazr opened this issue Jan 23, 2018 · 7 comments

Comments

@tazr
Copy link

tazr commented Jan 23, 2018

What did you do?

Trying to save a grayscale 16 bit PNG.

What did you expect to happen?

Obtain a 16 bit PNG file.

What actually happened?

An error.

What versions of Pillow and Python are you using?

Pillow 5.00
Python 3.5.4

Code:

import numpy as np
from PIL import Image

ar = np.ones((32, 32), dtype=np.uint16)
im = Image.fromarray(ar)
im.save('foo.png')

Output:

OSError: cannot write mode I;16 as PNG

@wiredfool
Copy link
Member

wiredfool commented Jan 24, 2018

There's a mapping from I mode, but not I;16. The current full list is here:

_OUTMODES = {
# supported PIL modes, and corresponding rawmodes/bits/color combinations
"1": ("1", b'\x01\x00'),
"L;1": ("L;1", b'\x01\x00'),
"L;2": ("L;2", b'\x02\x00'),
"L;4": ("L;4", b'\x04\x00'),
"L": ("L", b'\x08\x00'),
"LA": ("LA", b'\x08\x04'),
"I": ("I;16B", b'\x10\x00'),
"P;1": ("P;1", b'\x01\x03'),
"P;2": ("P;2", b'\x02\x03'),
"P;4": ("P;4", b'\x04\x03'),
"P": ("P", b'\x08\x03'),
"RGB": ("RGB", b'\x08\x02'),
"RGBA": ("RGBA", b'\x08\x06'),
}

It may be possible to add in a set of I;16 mappings.

@tazr
Copy link
Author

tazr commented Jan 24, 2018

Nice to hear. Also, casting to I could be a workaround until then. Thanks.

@aclark4life aclark4life added this to the Future milestone Apr 1, 2018
@grant-zietsman
Copy link

grant-zietsman commented Apr 4, 2018

@tazr This seems to work. Is that what you mean by casting to I?

import numpy as np
from PIL import Image

ar = np.ones((32, 32) , dtype=np.uint16)
ar32 = ar.astype(np.uint32)
im = Image.fromarray(ar32) # or more verbose as Image.fromarray(ar32, 'I')
im.save('foo.png')

@aclark4life So when/if the enhancement is made tarz's code shall be supported?

import numpy as np
from PIL import Image

ar = np.ones((32, 32), dtype=np.uint16)
im = Image.fromarray(ar) # or more verbose as Image.fromarray(ar, 'I;16')
im.save('foo.png')

@ahundt
Copy link

ahundt commented May 4, 2018

Is there any update on this? I tried with the latest pillow version and got an error

@LandingEllipse
Copy link

Same issue here. Curiously, the workaround for me involves using imageio (which in turn uses Pillow as the backend for png). I haven't yet had time to check why this works, but I include it here in case someone encounters this in the future.

Working Example

import numpy as np
import imageio

arr = np.full((32, 32), 256, dtype=np.uint16)
imageio.imwrite("~/16-bit_test.png", arr)

img = imageio.imread("~/16-bit_test.png")
print(img.dtype)  # => "uint16"

and yields:

$ file ~/16-bit_test.png
16-bit_test.png: PNG image data, 32 x 32, 16-bit grayscale, non-interlaced

Apparent Bug

When preparing the example above I noticed that using e.g. np.ones seems to produce an 8-bit output image. And indeed it appears that if the range of values in the source array will fit within uint8 the library takes the liberty to drop the top byte:

import numpy as np
import imageio

arr = np.full((32, 32), 255, dtype=np.uint16)
imageio.imwrite("~/16-bit_test.png", arr)

img = imageio.imread("~/16-bit_test.png")
print(img.dtype)  # => "uint8"

and:

$ file ~/16-bit_test.png
16-bit_test.png: PNG image data, 32 x 32, 8-bit grayscale, non-interlaced

This is dangerous as e.g. low saturation input images will be falsely represented. I'll do some digging and raise an issue over at the imageio project so that this can hopefully be fixed.

@LandingEllipse
Copy link

Futher to my comment above I have extracted the approach that imageio takes which produces a working example using only Pillow:

import numpy as np
from PIL import Image

arr = np.random.randint(0, 2**16-1, (32, 64), dtype=np.uint16)  # or np.ones etc.

array_buffer = arr.tobytes()
img = Image.new("I", arr.T.shape)
img.frombytes(array_buffer, 'raw', "I;16")
img.save("16-bit_test_pillow.png")

resulting in:

$ file 16-bit_test_pillow.png 
16-bit_test_pillow.png: PNG image data, 64 x 32, 16-bit grayscale, non-interlaced

Pillow: 5.1.0
Python: 3.6.5

@radarhere
Copy link
Member

I have created PR #3566 to address this.

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

No branches or pull requests

7 participants