0

I have 2D numpy array that I want to mask and plot. I have tried this.

import numpy as np
import matplotlib.pyplot as plt

a = np.random.random((101,99))

data1 = a.copy()
bound = np.percentile(data1, 80)
data1[data1<bound] = np.nan
plt.figure()
plt.imshow(data1)

Output:

enter image description here

data2 = a.copy()
data2[data2 < bound] = 0
plt.figure()
plt.imshow(data2)

Output:

enter image description here

I am expecting the first image to look like the second image, where there are the same number of white pixels as dark-blue pixels, and the white pixels are in the same position as the dark-blue pixels. Clearly, there are more white pixels than dark-blue pixels. I feel like I'm missing something simple. Is something wrong with my matplotlib configuration?

EDIT:

To show the first image actually has more white pixels than the second image -- and there are no anti-aliasing effects -- I have rerun the code block using plt.gca().set_facecolor('black'):

a = np.random.random((101,99))

data1 = a.copy()

bound = np.percentile(data1, 80)
data1[data1<bound] = np.nan
plt.figure()
plt.imshow(data1)
plt.gca().set_facecolor('black')

Output:

enter image description here

data2 = a.copy()
data2[data2 < bound] = 0
plt.figure()
plt.imshow(data2)

Output:

enter image description here

T Walker
  • 330
  • 1
  • 3
  • 12
  • 2
    NaN values are usually shown fully transparent. That enables e.g. plotting multiple images with different color maps. You could try to use `set_bad(..)` on the colormap to change the color for NaNs. – JohanC Jan 28 '22 at 18:51
  • Ah I must not have been clear. I don't want to change the color of the NaN pixels; I want transparent NaN pixels everywhere there are zeros in the second image. I thought the first code block would do this automatically, but clearly it thinks there are more transparent pixels than there should be (there should be the same number of transparent pixels in the first image as there are zero pixels in the second image). – T Walker Jan 28 '22 at 19:01
  • I have edited the post with your suggestion, and have to disagree. The differences in the images are not due to lack of contrast. – T Walker Jan 28 '22 at 19:20
  • I ran your code and the pattern of non NaN points is just the same in both cases. – Christian K. Jan 28 '22 at 19:25
  • Increase the size of your plot (e.g. to (15, 15)) and you'll immediate see the 'missing' pixels. Unless I misunderstood... – Matt Hall Jan 28 '22 at 19:25
  • If I understand correctly, your real question is to get your second image, but with white instead of black. In that case, setting the "under" color to white and drawing using `cmap1 = plt.get_cmap('viridis').copy(); cmap1.set_under('white'); plt.imshow(a, vmin=bound, cmap=cmap1)` could do the trick. – JohanC Jan 29 '22 at 11:50

2 Answers2

1

As others pointed out this seems to be due to interpolation. On my machine, running your code, there was no difference between the two plots apart from the background. Try to explicitely turn off interpolation:

plt.imshow(data1, interpolation='nearest')
Christian K.
  • 2,785
  • 18
  • 40
1

The effect is due to antialiasing. For each pixel on the screen, matplotlib averages out the corresponding pixels of the data. If one of the data pixels is NaN, the complete screen pixel is considered transparent. With zeros instead of NaNs, the standard averaging is used.

The following code example illustrates what's happening.

import numpy as np
import matplotlib.pyplot as plt

a = np.random.random((101, 99))

data1 = a.copy()
bound = np.percentile(data1, 80)
data1[data1 < bound] = np.nan
fig, (ax1, ax2, ax3) = plt.subplots(ncols=3, figsize=(15, 6))
ax1.imshow(data1)
ax2.imshow(data1)
ax2.set_facecolor('black')

data2 = a.copy()
data2[data2 < bound] = 0
ax3.imshow(data2)
plt.tight_layout()
plt.show()

figsize=(15,6)

Now, the same, but with figsize=(10,4)

figsize=(10,4)

JohanC
  • 71,591
  • 8
  • 33
  • 66
  • Thank you! I clearly don't have a very good grasp of matplotlib functionality. I appreciate your demonstration. @Christian K. had responded with a solution as well, but I have marked yours as the answer as it is more complete. – T Walker Jan 28 '22 at 19:33
  • Your original question was *"why the difference"*, so that's what my answer tried to show. Turning off the antialiasing via `interpolation='none')` or `interpolation='nearest')` is also a good way to visualize what's going on. It is a workaround to show more pixels, but also looses smoothness. If you want to save the result as an image, it helps to increase the dpi. – JohanC Jan 28 '22 at 19:43
  • 1
    Please also see https://matplotlib.org/stable/gallery/images_contours_and_fields/image_antialiasing.html. Fundamentally if you downsample (your output has fewer pixels than your input) then you need to remove info. If this is happening you should either increase dpi or use an antialiasing filter – Jody Klymak Jan 29 '22 at 08:43