183

I have a simple problem, but I cannot find a good solution to it.

I want to take a NumPy 2D array which represents a grayscale image, and convert it to an RGB PIL image while applying some of the matplotlib colormaps.

I can get a reasonable PNG output by using the pyplot.figure.figimage command:

dpi = 100.0
w, h = myarray.shape[1]/dpi, myarray.shape[0]/dpi
fig = plt.figure(figsize=(w,h), dpi=dpi)
fig.figimage(sub, cmap=cm.gist_earth)
plt.savefig('out.png')

Although I could adapt this to get what I want (probably using StringIO do get the PIL image), I wonder if there is not a simpler way to do that, since it seems to be a very natural problem of image visualization. Let's say, something like this:

colored_PIL_image = magic_function(array, cmap)
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
heltonbiker
  • 23,225
  • 20
  • 121
  • 212
  • For fully working code, you may ref: [Is there any good color map to convert gray-scale image to colorful ones using python's PIL?](http://stackoverflow.com/questions/43457308/is-there-any-good-color-map-to-convert-gray-scale-image-to-colorful-ones-using-p) – Adam Apr 23 '17 at 04:49

3 Answers3

278

Quite a busy one-liner, but here it is:

  1. First ensure your NumPy array, myarray, is normalised with the max value at 1.0.
  2. Apply the colormap directly to myarray.
  3. Rescale to the 0-255 range.
  4. Convert to integers, using np.uint8().
  5. Use Image.fromarray().

And you're done:

from PIL import Image
from matplotlib import cm
im = Image.fromarray(np.uint8(cm.gist_earth(myarray)*255))

with plt.savefig():

Enter image description here

with im.save():

Enter image description here

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
fraxel
  • 31,038
  • 11
  • 87
  • 96
  • 8
    The "Apply the colormap directly to `myarray`" part cut straight to the heart! I didn't knew it was possible, thank you! – heltonbiker Jun 11 '12 at 02:26
  • 39
    Studying the docs about LinearSegmentedColormap (from which cm.gist_earth is an instance), I discovered that it's possible to call it with a "bytes" argument which already converts it to uint8. Then, the one-liner gets a lot quieter: `im = Image.fromarray(cm.gist_earth(myarray, bytes=True))` – heltonbiker Jun 13 '12 at 04:20
  • @heltonbiker what shape should `myarray` have ? I'm trying to get a wider image – Ciprian Tomoiagă Jan 31 '17 at 07:20
  • 1
    @CiprianTomoiaga, the shape of the array should be the image dimensions you want. For example, a VGA image would be generated from an array with shape (1024,768). You should notice this applies for monochrome images. This is important because usually when you convert an RGB image to an array, its shape is, for example, (1024,768,3), since it has three channels. – heltonbiker Jan 31 '17 at 11:40
  • Awesome! To generate a legend from a colormap I chained `repeat` and `reshape` like this: `legegend_arry = plt.get_cmap(cmap_name)(np.linspace(0,1,legend_h).repeat(legend_w).reshape(legend_h,legend_w), bytes=True)`. Calling `frombytes` on `legend_arry` gives a PIL image of size `(legend_h, legend_w)`. Thanks for the reference ! – Ciprian Tomoiagă Jan 31 '17 at 22:57
  • 7
    I am getting error `NameError: name 'cm' is not defined` – rnso Aug 31 '18 at 03:10
  • 11
    @mso `from matplotlib import cm` – Quantum7 Oct 02 '18 at 12:53
  • Also for a different colour map use `im = Image.fromarray(np.uint8(cm.get_cmap('inferno')(myarray)*255))` – Aaron May 01 '19 at 12:34
  • 1
    Is there a reason for applying cm.gist_earth to myarray? I just thought I'd multiply 255 right away. – Hyelin Jan 10 '21 at 07:43
  • 1
    I like how this post has 256 up-votes. Tempted but can't do an up-vote. – SaladHead Feb 10 '21 at 06:34
35
  • input = numpy_image
  • np.unit8 -> converts to integers
  • convert('RGB') -> converts to RGB
  • Image.fromarray -> returns an image object

    from PIL import Image
    import numpy as np
    
    PIL_image = Image.fromarray(np.uint8(numpy_image)).convert('RGB')
    
    PIL_image = Image.fromarray(numpy_image.astype('uint8'), 'RGB')
    
Aravinda_gn
  • 523
  • 1
  • 4
  • 10
10

The method described in the accepted answer didn't work for me even after applying changes mentioned in its comments. But the below simple code worked:

import matplotlib.pyplot as plt
plt.imsave(filename, np_array, cmap='Greys')

np_array could be either a 2D array with values from 0..1 floats o2 0..255 uint8, and in that case it needs cmap. For 3D arrays, cmap will be ignored.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Shital Shah
  • 47,549
  • 10
  • 193
  • 157