Ofer Sadan
Ofer Sadan

Reputation: 11922

PIL.Image.fromarray produces distorted image after np.reshape

I have an array (numpy) of shape (10000,1296) that I want to save as 10,000 images of 36x36 size. All values in the array are in the range (0,255). So I have used this code to do that (which worked well):

for i, line in enumerate(myarray):
    img = Image.new('L',(36,36))
    img.putdata(line)
    img.save(str(i)+'.png')

I wanted to replace this code using the Image.fromarray method, but the resulting pictures are distorted beyond recognition compared to the original method. First I tried this:

myarray = myarray.reshape(10000,36,36)
for i, line in enumerate(myarray):
    img = Image.fromarray(line, 'L')
    img.save(str(i)+'.png')

Which didn't work. So to debug I thought I would try just one item and did this:

Image.fromarray(myarray[0].reshape(36,36), 'L').save('test.png')

And again - garbled distorted image.

So I figured either fromarray isn't working like I thought it should, or my reshape is too naive and messes up the data, but I am not able to fix this. Any ideas are welcome.

Upvotes: 3

Views: 3215

Answers (1)

unutbu
unutbu

Reputation: 879103

PIL's L mode is a grayscale mode for data that represents luminance (brightness). The data is expected to be ints from 0 to 255. If you create a PIL Image by passing a NumPy array to Image.fromarray with mode='L', the dtype of the array should be uint8. Therefore use

myarray = myarray.astype('uint8')

to ensure that the arrays passed to Image.fromarray have dtype uint8.


uint8s are unsigned 8-bit ints. float32s are 32-bit floats. They are 4 times as wide as a uint8. Image.fromarray views the underlying data in the NumPy array as uint8s, reads enough bytes to fill the image, and ignores the rest. So each 32-bit float becomes four 8-bit ints and each of those 8-bit ints colors a different pixel.

In other words, the following assert passes:

import numpy as np
from PIL import Image

line = np.arange(256).reshape(16, 16).astype('float32')
img = Image.fromarray(line, 'L')
line2 = np.asarray(img)
assert (line.view('uint8').ravel()[:256].reshape(16, 16) == line2).all()

This is why using myarray without converting to uint8s creates a garbled image.


Alternatively, instead of converting myarray to uint8s you could read the data in mode='F' (float mode):

import numpy as np
from PIL import Image

line = np.arange(256).reshape(16, 16).astype('float32')
img = Image.fromarray(line, 'F').convert('L')
img.save('/tmp/out.png')

which yields

enter image description here

See this page for a list of all the possible PIL modes.

Upvotes: 6

Related Questions