21

I am trying to save a numpy array of dimensions 128x128 pixels into a grayscale image. I simply thought that the pyplot.imsave function would do the job but it's not, it somehow converts my array into an RGB image. I tried to force the colormap to Gray during conversion but eventhough the saved image appears in grayscale, it still has a 128x128x4 dimension. Here is a code sample I wrote to show the behaviour :

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mplimg
from matplotlib import cm

x_tot = 10e-3
nx = 128

x = np.arange(-x_tot/2, x_tot/2, x_tot/nx)

[X, Y] = np.meshgrid(x,x)
R = np.sqrt(X**2 + Y**2)

diam = 5e-3
I = np.exp(-2*(2*R/diam)**4)

plt.figure()
plt.imshow(I, extent = [-x_tot/2, x_tot/2, -x_tot/2, x_tot/2])

print I.shape

plt.imsave('image.png', I)
I2 = plt.imread('image.png')
print I2.shape

mplimg.imsave('image2.png',np.uint8(I), cmap = cm.gray)
testImg = plt.imread('image2.png')
print testImg.shape

In both cases the results of the "print" function are (128,128,4).

Can anyone explain why the imsave function is creating those dimensions eventhough my input array is of a luminance type? And of course, does anyone have a solution to save the array into a standard grayscale format?

Thanks!

Mathieu Paurisse
  • 450
  • 4
  • 8
  • 17

5 Answers5

24

With PIL it should work like this

from PIL import Image

I8 = (((I - I.min()) / (I.max() - I.min())) * 255.9).astype(np.uint8)

img = Image.fromarray(I8)
img.save("file.png")
lsbmsb
  • 39
  • 1
  • 9
eickenberg
  • 14,152
  • 1
  • 48
  • 52
  • The rescaling can evidently be done differently, but passing `uint8` values makes things unambiguous to PIL – eickenberg Nov 14 '14 at 11:52
3

There is also an alternative of using imageio. It provides an easy and convenient API and it is bundled with Anaconda. It can save grayscale images as a single color channel file.

Quoting the documentation

>>> import imageio
>>> im = imageio.imread('imageio:astronaut.png')
>>> im.shape  # im is a numpy array
(512, 512, 3)
>>> imageio.imwrite('astronaut-gray.jpg', im[:, :, 0])
tpl
  • 197
  • 1
  • 7
2

There is also a possibility to use scikit-image, then there is no need to convert numpy array into a PIL object.

from skimage import io
io.imsave('output.tiff', I.astype(np.uint16))
Bartłomiej
  • 1,068
  • 1
  • 14
  • 23
1

I didn't want to use PIL in my code and as noted in the question I ran into the same problem with pyplot, where even in grayscale, the file is saved in MxNx3 matrix.

Since the actual image on disk wasn't important to me, I ended up writing the matrix as is and reading it back "as-is" using numpy's save and load methods:

np.save("filename", image_matrix)

And:

np.load("filename.npy")
roy650
  • 633
  • 1
  • 10
  • 18
0

There are several methods that you can use, as stated in the other answers.

Answering your question, for matplotlib, my guess is that for .png files, they are converting the 2D grayscale image for an RGBA (still in grayscale) 3D array. See this line in matplotlib's GtiHub. That is why your read image is a 3D array instead of a 2D.

Check this minimal working example below that uses Matplotlib, Opencv and Pillow. Note, matplotlib also uses pillow as does recommend using it on plt.imread page.

import numpy as np
import matplotlib.pyplot as plt
import cv2
import os
from PIL import Image

im = np.random.randint(low=0, high=255, size=(100, 100), dtype=np.uint8)

#### Using matplotlib ####
print("Using matplotlib ------")
print(im.shape)

plt.imsave('test.png', im, cmap='gray', pil_kwargs={'compress_level':0})

print(os.path.getsize('test.png'), 'bytes')

im_r_plt = plt.imread('test.png')

print(im_r_plt.shape)

#### Using PIL ####
print("Using PIL ------")
img = Image.fromarray(im)
img.save('test.png', compress_level=0)

print(os.path.getsize('test.png'), 'bytes')

im_r_pil = Image.open('test.png') 

print(im_r_pil.size)

#### Using opencv ####
print("Using opencv ------")
cv2.imwrite('test.png', im, [cv2.IMWRITE_PNG_COMPRESSION, 0])

print(os.path.getsize('test.png'), 'bytes')

im_r_cv2 = cv2.imread('test.png', cv2.IMREAD_GRAYSCALE)

print(im_r_cv2.shape)

The code outputs:

Using matplotlib ------
(100, 100)
40263 bytes
(100, 100, 4)
Using PIL ------
10168 bytes
(100, 100)
Using opencv ------
10180 bytes
(100, 100)
rvimieiro
  • 1,043
  • 1
  • 10
  • 17