I'm saving a Numpy array as a PNG image using PIL, but when I read it back, I don't get the same array numbers.
I save a PNG image with one pixel with the value 2^16 but when I read it back to a Numpy array, such pixel has a value of 2^16 - 1.
Seems like the bit depth range is clipped to 2^16 -1 bits, but the docs say that saving as PNG, I can use the 'I' mode, which is for 32 bit signed integers.
The mode of an image is a string which defines the type and depth of a pixel in the image. Each pixel uses the full range of the bit depth. So a 1-bit pixel has a range of 0-1, an 8-bit pixel has a range of 0-255 and so on. The current release supports the following standard modes:
I
(32-bit signed integer pixels)
Pillow identifies, reads, and writes PNG files containing 1, L, LA, I, P, RGB or RGBA data.
Reproducible example:
import tempfile
from PIL import Image
import numpy as np
im_np = np.array([[1, 1], [1, 2**16]], dtype=np.int32)
im_pil = Image.fromarray(im_np, mode='I')
with tempfile.TemporaryFile() as f:
im_pil.save(f, 'PNG')
with Image.open(f) as im:
recovered_im_np = np.array(im)
print(f"Numpy array:\n{im_np}")
print(f"Numpy array receovered from PNG:\n {recovered_im_np}")
I get this:
Numpy array:
[[ 1 1]
[ 1 65536]]
Numpy array receovered from PNG:
[[ 1 1]
[ 1 65535]]
Python version: 3.9.10 (main, Feb 17 2022, 18:15:00) \n[GCC 9.3.0]
PIL version: 9.0.1
Numpy version: 1.22.2
As @mateusreis correctly points out, the .png
format only supports 16 bits per pixel in grayscale, so either you have to transform the value into a 3x8 24 bits per pixel RGB value, or you should save in an image format that support 32 bits per pixel, like TIFF:
import tempfile
from PIL import Image
import numpy as np
im_np = np.array([[1, 1], [1, 2 ** 16]], dtype=np.int32)
im_pil = Image.fromarray(im_np, mode='I')
with tempfile.TemporaryFile() as f:
im_pil.save(f, 'TIFF')
with Image.open(f) as im:
recovered_im_np = np.array(im)
print(f"Numpy array:\n{im_np}")
print(f"Numpy array receovered from PNG:\n {recovered_im_np}")
Result:
Numpy array:
[[ 1 1]
[ 1 65536]]
Numpy array receovered from PNG:
[[ 1 1]
[ 1 65536]]
The key thing here is to realise you make 2 conversions (in both directions):
numpy
array -> 2.) PIL
image -> 3.) image file formatYour mode='I'
covers 1 -> 2, but you need to pick the right format to preserve the data for 2 -> 3.