Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bizzare matplotlib behaviour in displaying images cast as floats

When a regular RGB image in range (0,255) is cast as float, then displayed by matplotlib, the image is displayed as negative. If it is cast as uint8, it displays correctly (of course). It caused me some trouble to figure out what was going on, because I accidentally cast one of images as float.

I am well aware that when cast as float, the image is expected to be in range (0,1), and sure enough, when divided by 255 the image displayed is correct. But, why would an image in range (0,255) that is cast as float displayed as negative? I would have expected either saturation (all white) or automatically inferred the range from the input (and thus correctly displayed)? If either of those expected things happened, I would have been able to debug my code quicker. I have included the required code to reproduce the behaviour. Does anyone have insight on why this happens?

    import numpy as np
    import matplotlib.pyplot as plt
    a = np.random.randint(0,127,(200,400,3))
    b = np.random.randint(128,255,(200,400,3))
    img=np.concatenate((a,b)) # Top should be dark ; Bottom should be light
    plt.imshow(img) # Inverted
    plt.figure()
    plt.imshow(np.float64(img)) # Still Bad. Added to address sascha's comment
    plt.figure()
    plt.imshow(255-img) # Displayed Correctly
    plt.figure()
    plt.imshow(np.uint8(img)) # Displayed Correctly
    plt.figure()
    plt.imshow(img/255.0) # Displays correctly
like image 843
Prophecies Avatar asked Oct 07 '16 20:10

Prophecies


1 Answers

In the sources, in image.py, in the AxesImage class (what imshow returns) a method _get_unsampled_image is called at some point in the drawing process. The relevant code starts on line 226 for me (matplotlib-1.5.3):

if A.dtype == np.uint8 and A.ndim == 3:
    im = _image.frombyte(A[yslice, xslice, :], 0)
    im.is_grayscale = False
else:
    if self._rgbacache is None:
    x = self.to_rgba(A, bytes=False)
    # Avoid side effects: to_rgba can return its argument                        
    # unchanged.                                                                 
    if np.may_share_memory(x, A):
       x = x.copy()
    # premultiply the colors                                                     
    x[..., 0:3] *= x[..., 3:4]
    x = (x * 255).astype(np.uint8)
    self._rgbacache = x

So the type and size of the input A get checked:

if A.dtype == np.uint8 and A.ndim == 3:

in which case there is no preprocessing. Otherwise, without checking the range of the input, you ultimately have a multiplication by 255 and a cast to uint8:

x = (x * 255).astype(np.uint8)

And we know what to expect if x is from 0 to 255 instead of 0 to 1:

In [1]: np.uint8(np.array([1,2,128,254,255])*255)
Out[1]: array([255, 254, 128,   2,   1], dtype=uint8)

So light becomes dark. That this inverts the image is probably not a planned behavior as I think you assume.

You can compare the values of _rgbacache in the object returned from imshow for each of your input cases to observe the result, e.g. im._rbacache where im = plt.imshow(np.float64(img)).

like image 125
tsj Avatar answered Oct 03 '22 01:10

tsj