0

I'd like to understand why, when I convert the PIL image imageRGB to a float array arrayRGB_f and use matplotlib's imshow() without a cmap it looks either black, or strange and unreadable, even though PIL's imageRGB.show() looks fine, and each of the individual r, g, b channels shown with cmap='gray' look okay as well.

I have workarounds, but I just don't understand why this happens.

matplotlib.__version__ returns '2.0.2' and I'm using MacOS with an Anaconda installation.

See this answer for more on the conversion of a ttf rendering to a 1bit.

fyi the output of the print statements are:

float64 (41, 101, 3)
int64 (41, 101, 3)
int64 (41, 101)
int64 (41, 101)

fontname   = 'default' 

imageRGB.show()

enter image description here

plt.imshow()

enter image description here

fontname   = 'Arial Unicode.ttf' 

imageRGB.show()

enter image description here

plt.imshow()

enter image description here

font   = ImageFont.truetype(fontname, 20)

imageRGB.show()

enter image description here

plt.imshow()

enter image description here

from PIL import Image, ImageDraw, ImageFont
import numpy as np
import matplotlib.pyplot as plt

# fontname   = 'Arial Unicode.ttf' 
fontname   = 'default' 

if fontname == 'default':
    font   = ImageFont.load_default()
else:
    font   = ImageFont.truetype(fontname, 12)

string     = "Hello " + fontname[:6]
ww, hh     = 101, 41
threshold  = 80   # https://stackoverflow.com/a/47546095/3904031

imageRGB   = Image.new('RGB', (ww, hh))
draw       = ImageDraw.Draw(imageRGB)
image8bit  = draw.text((10, 12), string, font=font,
                       fill=(255, 255, 255, 255))  # R, G, B alpha

image8bit  = imageRGB.convert("L")
image1bit  = image8bit.point(lambda x: 0 if x < threshold else 1, mode='1')  # https://stackoverflow.com/a/47546095/3904031

arrayRGB   = np.array(list(imageRGB.getdata())).reshape(hh, ww, 3)
arrayRGB_f = arrayRGB.astype(float)

array8bit  = np.array(list(image8bit.getdata())).reshape(hh, ww)
array1bit  = np.array(list(image1bit.getdata())).reshape(hh, ww)

for a in (arrayRGB_f, arrayRGB, array8bit, array1bit):
    print a.dtype, a.shape

imageRGB.show()

if True:
    plt.figure()

    a = arrayRGB_f
    plt.subplot(2, 2, 1)
    plt.imshow(a)  # , interpolation='nearest',  cmap='gray',

    for i in range(3):
        plt.subplot(2, 2, 2+i)
        plt.imshow(a[:, :, i], cmap='gray') 

    plt.suptitle('arrayRGB_f, fontname = ' + fontname)
    plt.show()
uhoh
  • 3,713
  • 6
  • 42
  • 95
  • 1
    Use `a = arrayRGB_f/255.` because float arrays should be in the range between 0. and 1. – ImportanceOfBeingErnest Aug 08 '18 at 21:06
  • 1
    [This is the output I get](https://i.stack.imgur.com/YzrcV.png) using the proposed solution. Do you get the exact same output when trying a = arrayRGB_f/255.? (Note that I'm talking about images here, which are 3D rgb arrays, unnormalized 2D data arrays work fine of course) – ImportanceOfBeingErnest Aug 08 '18 at 21:36
  • @ImportanceOfBeingErnest Oh geez, that's right! When it's an `n x m x 3` Matplotlib does *expect* normalized values. I'm usually looking at rainbow plots of `n x m`. Consider posting an answer? I will later if nobody does. Looks great here! – uhoh Aug 08 '18 at 21:36
  • Nah, this question has been asked a hundred times already. You may find the appropriate duplicate and close it as such if you want. – ImportanceOfBeingErnest Aug 08 '18 at 21:37
  • @ImportanceOfBeingErnest It's `05:40 AM` here, I may just delete and go to sleep. – uhoh Aug 08 '18 at 21:39
  • I wouldn't delete it. Apparently the wording you used made not find you any answer, so keeping this question linking to a duplicate for a proper solution might be helpful for other people in the future. – ImportanceOfBeingErnest Aug 08 '18 at 21:48

1 Answers1

0

I can't find an ideal duplicate so I'll post an answer.

As @ImportanceOfBeingErnest mentions when .imshow() is given an n x m x 3 or n x m x 4 array, it is expecting a normalized array between 0.0 and 1.0.

Best way to do this is:

arrayRGB_f = arrayRGB.astype(float)/255.

though this seems to work as well:

arrayRGB_f = arrayRGB.astype(float)
arrayRGB_f = arrayRGB_f / arrayRGB_f.max()

For longer discussions, see this and this.

uhoh
  • 3,713
  • 6
  • 42
  • 95
  • @ImportanceOfBeingErnest how does this look (I couldn't find a good dupe) – uhoh Aug 08 '18 at 21:50
  • 1
    What about [this one](https://stackoverflow.com/questions/24739769/matplotlib-imshow-plots-different-if-using-colormap-or-rgb-array)? – ImportanceOfBeingErnest Aug 08 '18 at 21:51
  • 1
    Or [this one](https://stackoverflow.com/questions/42044259/getting-black-plots-with-plt-imshow-after-multiplying-image-array-by-a-scalar)? – ImportanceOfBeingErnest Aug 08 '18 at 21:51
  • @ImportanceOfBeingErnest in this case, this answer is actually the best, it literally "solved my problem" so I'm going to leave it here. Both of those have answers that do contain the solution, but it takes a while to find it as they contain a lot more information to read through. This is exactly what I needed to see, and your comments now direct people to deeper explanations. I'll add those back into the answer as well. Thank you very much for your time and help! – uhoh Aug 08 '18 at 21:57