Bizzare matplotlib behavior when displaying images cast as a float

When a regular RGB image in the range (0.255) is displayed as a float and then matplotlib is displayed, the image is displayed as negative. If it is selected as uint8, it displays correctly (of course). This caused me some problem to understand what was going on, because I accidentally selected one of the images as floating.

I am well aware that when used as a float, the image should be in the range (0,1), and, of course, when divided by 255, the displayed image is correct. But why is the image in the range (0.255), which is displayed as a float, displayed as negative? Would I expect either saturation (all white) or automatically output the range from the input (and therefore display correctly)? If one of these expected events happened, I would be able to debug my code faster. I have included the required code to reproduce the behavior. Does anyone know why this is happening?

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 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 
+6
source share
2 answers

In the image.py sources in the AxesImage class (which imshow returns), the _get_unsampled_image method is called at some point in the drawing process. The corresponding code starts at 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 

Thus, the type and size of input A are checked:

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

and in this case, pre-processing is not performed. Otherwise, without checking the input range, you end up having multiplication by 255 and listing on 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 the light becomes dark. That this inverts the image is probably not the intended behavior, as I think you are assuming.

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

+4
source

I think you are on the wrong track here, as you are claiming that np.random.randint() should return a floating point array. This is not true! ( docs )

This means :

Your first plot calls imshow with a numpy array dtype = int64 . This is not allowed, as shown here !

The only valid types for your measurements are described as: ( see documents ):

 MxNx3 – RGB (float or uint8 array) # there are others # -> this one applies to your dims! 

The only valid imshow calls in your example are numbers 3 and 4 (while 1 and 2 are not valid)!

Listing:

  • plt.imshow(img) not normal since dtype = int64
  • plt.imshow(255-img) not normal since dtype = int64
  • plt.imshow(np.uint8(img)) ok as dtype = uint8 (and compatible dims)
  • plt.imshow(img/255.0) ok as dtype = float (and compatible dims)

Note:

If this bothers you, you can check out scikit-image , which is a little more cautious in design with regard to the internal representation of images (during custom modifications and resulting types). Internal data structures are still numpy arrays!

+1
source

All Articles