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
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))
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With