Home > Blockchain >  Can't recover the same numpy array from PIL-saved PNG image
Can't recover the same numpy array from PIL-saved PNG image

Time:02-26

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.

From the Pillow docs:

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)

From the Pillow PNG docs:

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

CodePudding user response:

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):

  • 1.) numpy array -> 2.) PIL image -> 3.) image file format

Your mode='I' covers 1 -> 2, but you need to pick the right format to preserve the data for 2 -> 3.

CodePudding user response:

Pillow may support 32 bit signed integer images, but png doesn't. The maximum color depth per pixel supported by the png format is 16 bits. I'm not sure in which sense they mean that saving I mode images is supported.

You can always save the numpy array directly with numpy.save instead.

  • Related