How to convert a NumPy array to PIL image applying matplotlib colormap

375,247

Solution 1

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

Solution 2

  • 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')
    

Solution 3

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.

Share:
375,247

Related videos on Youtube

heltonbiker
Author by

heltonbiker

I am an ex-physician, have studied mechanical engineering for a while, and have a master degree in product design. Now I work designing diagnostic equipment (surface EMG, posturography, pedobarography), dealing with system requirements, data visualization, and GUI design, and the like. I am also a die-hard cyclist, be it trails (not much nowadays), off-road, commuting, touring or randonneuring. Besides, I have deep interests in bike design and mechanics.

Updated on June 07, 2020

Comments

  • heltonbiker
    heltonbiker over 3 years

    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)
    
  • heltonbiker
    heltonbiker over 11 years
    The "Apply the colormap directly to myarray" part cut straight to the heart! I didn't knew it was possible, thank you!
  • heltonbiker
    heltonbiker over 11 years
    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))
  • Ciprian Tomoiagă
    Ciprian Tomoiagă almost 7 years
    @heltonbiker what shape should myarray have ? I'm trying to get a wider image
  • heltonbiker
    heltonbiker almost 7 years
    @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.
  • Ciprian Tomoiagă
    Ciprian Tomoiagă almost 7 years
    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(leg‌​end_w).reshape(legen‌​d_h,legend_w), bytes=True). Calling frombytes on legend_arry gives a PIL image of size (legend_h, legend_w). Thanks for the reference !
  • rnso
    rnso about 5 years
    I am getting error NameError: name 'cm' is not defined
  • Quantum7
    Quantum7 about 5 years
    @mso from matplotlib import cm
  • Aaron
    Aaron over 4 years
    Also for a different colour map use im = Image.fromarray(np.uint8(cm.get_cmap('inferno')(myarray)*255‌​))
  • Jaimil Patel
    Jaimil Patel over 3 years
    Hope It will solve issue but please add explanation of your code with it so user will get perfect understanding which he/she really wants.
  • Catalina Chircu
    Catalina Chircu over 3 years
    Good, updated answer. The previous ones are from several years ago.
  • Hyelin
    Hyelin almost 3 years
    Is there a reason for applying cm.gist_earth to myarray? I just thought I'd multiply 255 right away.
  • SaladHead
    SaladHead over 2 years
    I like how this post has 256 up-votes. Tempted but can't do an up-vote.
  • El Bachir
    El Bachir almost 2 years
    Great solution. Used this to convert a numpy array to a PIL image inorder to display it in a Tkinter Frame. new_image = Image.fromarray(np.uint8(image_array)) tkinter_image = ImageTk.PhotoImage(new_image)
  • bart-khalid
    bart-khalid over 1 year
    works well for me without 'RGB' arg, in order to keep the same image channel

Related