pythonmatplotlibpython-imaging-libraryflir

Image fromarray is showing a greyed out image


I am taking an array from a camera, thresholding the values and want to save the new image. I am using PIL.Image.fromarry() to turn an array into an image but I am only seeing greyed out images. The image is there because when I import into another program I can see what I want. When I show the image with plt.show(data) that is how I would like to see the image. How can I change the color values?

from flirpy.camera.lepton import Lepton
import matplotlib.pyplot as plt
import numpy as np
import cv2
from PIL import Image as im

camera = Lepton()

for i in range(5):
    image = camera.grab()
    cimage = np.around(image/100 - 273.15, 2)
    cimage = np.where(cimage < 20, 20, cimage)
    cimage = np.where(cimage > 30, 30, cimage)
    
    data = im.fromarray(cimage.astype(np.uint8),'L')
    data.save(f'test_{i:04}.png')
    plt.imshow(data)
    plt.show()

camera.close()

Matplotlib:

Matplotlib

PIL:

PIL


Solution

  • Matplotlib applies a colormap to your image and, under certain circumstances also scales the values to the full colour range. PIL doesn't do that, so if you give it a greyscale image (single channel) with all the values in the range 20..30, it will come out black, because it really expects values in the range 0..255 and it doesn't colorise the grey for you either.

    Here's how you can convert a Matplotlib cmap into a palette and push it into a PIL Image:

    #!/usr/bin/env python3
    
    import matplotlib.pyplot as plt
    from PIL import Image, ImageMath
    
    def cmap2palette(cmapName='viridis'):
        """Convert a Matplotlib colormap to a PIL Palette"""
        cmap = plt.get_cmap(cmapName)
        palette = [int(x*255) for entry in cmap.colors for x in entry]
        return palette
    
    # Generate a linear ramp
    im = Image.linear_gradient('L')
    im.save('DEBUG-ramp.png')            # just debug, not required
    
    # Generate the viridis Matplotlib colourmap push into our PIL Image
    pal = cmap2palette('viridis')
    im.putpalette(pal)
    im.save('DEBUG-viridis.png')
    
    # Just for fun...
    
    # Generate the inferno Matplotlib colourmap push into our PIL Image
    pal = cmap2palette('inferno')
    im.putpalette(pal)
    im.save('DEBUG-inferno.png')
    
    
    # Now with your image
    im = Image.open('w9jUR.png')
    
    # Contrast-stretch to full range
    gMin, gMax = im.getextrema()
    print(F'Greyscale min: {gMin}, greyscale max: {gMax}')
    im = ImageMath.eval('int(255*(g-gMin)/(gMax-gMin))', g=im, gMin=gMin, gMax=gMax).convert('L')
    
    # Generate the viridis Matplotlib colourmap push into our PIL Image
    pal = cmap2palette('viridis')
    im.putpalette(pal)
    im.save('DEBUG-result.png')
    

    That gives this as the input greyscale image:

    enter image description here

    and this as the result with the viridis and inferno colormaps:

    enter image description here enter image description here

    Note also that you don't need to make 2 passes over your image to clip it to the range 20..30 like this:

    cimage = np.where(cimage < 20, 20, cimage)
    cimage = np.where(cimage > 30, 30, cimage)
    

    You should be able to use np.clip():

    np.clip(cimage, 20, 30, out=cimage)
    

    If we apply the code above to your image:

    enter image description here