So i noticed for me and a few of my colleagues that when we display a binary array using matplotlib.pyplot's imshow function the edges of the displayed image seems altered. For a while i thought it was just a visual artifact, but ran into further trouble with it today.
By the way i am running with matplotlib: 3.2.2 and numpy: 1.19.1
If i create a small binary array and plot it it you can see a small "halo" to the binary box in the image. It is not very obvious but it is there:
import matplotlib.pyplot as plt
import numpy as np
img=np.zeros((100,100))
img[25:60,25:60]=50
plt.imshow(img)
It will become more apparent if i change the cmap for the plot.
my_cmap = plt.cm.get_cmap('prism')
my_cmap.set_under('black')
plt.imshow(img,cmap=my_cmap, vmin=1)
The displayed array should only have 0's as background and 1's in the box, but the box is displayed as a green box with a red/yellow border.
With previous versions of pyplot i have not had this issue and it does become a problem when i do object detection and i want to display them and my other wise binary objects end up like this:
I hope you can help me with this
imshow doesn't know about your data being discrete or even binary. Default it adds some interpolation. You can suppress the smooth interpolation using imshow(...., interpolation='none') (or interpolation='nearest').
Note that the default mode is 'antialiased' for which the effect is different depending on the number of screen pixels occupied by an image pixel.
See the official matplotlib documentation for more details.
Here is some test code comparing the default and the 'none' interpolation mode for different image sizes:
from matplotlib import pyplot as plt
import numpy as np
x = np.round(10 + np.random.uniform(-.1, .1, (100, 100)).cumsum(axis=0).cumsum(axis=1))
x[x % 2 == 0] = x.max() + 1
fig, axes = plt.subplots(2, 6, figsize=(14, 5))
for i, axrow in enumerate(axes):
for j, ax in enumerate(axrow):
k = 10 * (j + 5)
ax.imshow(x[-k:, -k:], cmap='Dark2', interpolation=None if i == 0 else 'none')
ax.set_title("size={}\ninterpolation={}".format(k, 'None' if i == 0 else "'none'"))
plt.tight_layout()
plt.show()
Here is another example, using the 'seismic' colormap and only two data values. This colormap has dark blue and red at the extremes and white near the center, which shows the interpolation much more pronunciated:
Related
An advantage of plt.pcolormesh over plt.imshow is the possibility to have unequal axis spacing.
On the other hand, plt.imshow's advantage over plt.pcolormesh is that it can display RGB-triplets.
Now, the predicament I am in is that I need to plot RGB-triplets with uneven axis spacing....
Below is a MWE:
import numpy as np
import matplotlib.pyplot as plt
from colorsys import hsv_to_rgb
square_x_axis = np.linspace(0,1,100)**2
cube_y_axis = np.linspace(0,1,200)**3
X,Y = np.meshgrid(cube_y_axis,square_x_axis); print(f'meshgrid has shape: {X.shape}')
rgb_array = np.zeros((square_x_axis.size, cube_y_axis.size,3)); print(f'rgb_array has shape: {rgb_array.shape}')
""" Now we populate the rgb array (initially in hsv color space for clarity)"""
for i,row in enumerate(rgb_array):
for j,col in enumerate(row):
rgb_array[i,j,:] = np.array(hsv_to_rgb(0,square_x_axis[i],cube_y_axis[j]))
fig = plt.figure(figsize=(15,10))
imshow_ax = plt.subplot(1,2,1)
imshow_ax.imshow(rgb_array, aspect='auto', extent=[0,1,0,1])
pcolor_R_ax = plt.subplot(3,2,2)
pcolor_R_ax.pcolormesh(X,Y,rgb_array[:,:,0], cmap='Reds')
pcolor_G_ax = plt.subplot(3,2,4)
pcolor_G_ax.pcolormesh(X,Y,rgb_array[:,:,1], cmap='Greens')
pcolor_B_ax = plt.subplot(3,2,6)
pcolor_B_ax.pcolormesh(X,Y,rgb_array[:,:,2], cmap='Blues')
Which produces the following figure:
The problem becomes immediately obvious: imshow (on the left) is capable of representing the 3D array, but its axis are scaled wrong, leading to a distorted representation. pcolormesh (on the right), on the other hand, can not represent the 3D array (hence why I plot all three channels separately), but is capable of applying the axis correctly, leading to no distortion.
How can I combine these properties?
I found another answer here that seems to work on your example, with a small tweak for some new pcolorbesh behaviour (the shading='auto' bit). Try this plot on your data:
fig = plt.figure(figsize=(15,10))
placeholder = rgb_array[..., 0]
colors = rgb_array.reshape(-1, 3)
mesh = plt.pcolormesh(X, Y, placeholder, facecolors=colors, shading='auto')
mesh.set_array(None)
It produces:
#kwinkunks answer is the method that solved my problem:
The original data, using imshow, looked like this, where both the x- and y-axis of the data plot and the colorbar are wrong. Of all 4 axes, only the data y-axis is linear, the 3 other axes are non-linear, and so using imshows's extent option is no good:
Now... taking #kwinkunks answer directly produced the following plot:
...where the axes tickmarks are now as they should be! Amazing!
I am trying to figure out the differences between the coordinates I give points in a plot vs the coordinates those points have when I save the image and open it in a NumPy array.
In order to understand the transform needed, I first tackle the question:
How can I actually know the amount pixel mark of matplotlib isn't a few pixels wide when I work with the image?
Therefore I'm trying to create 2 pixel sized markers in a simple plot and see if I can receive 2 values when inquiring it from NumPy.
So far this is the code I tried:
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
#creating figure and subplot
fig=plt.figure(figsize=(4,2))
ax = fig.add_subplot()
#These lines are for 'cleaning' the lines that might interfer with the black pixel count
ax.set_position([0, 0, 1, 1])
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(False)
#creating of the two mentioned pixels
ax.plot([1, 2],[0.4,0.4], marker='o',ms=72./fig.dpi, mew=0,
color='black', linestyle="", lw=0)
ax.set_xlim(0,8)
ax.set_ylim(0,1)
plt.show()
fig.savefig('plot.png', dpi=fig.dpi)
pix=np.array(Image.open('plot.png'))
black_points=np.all(pix==(0,0,0,0),axis=-1)
print(pix.shape)
sought = [0,0,0,255]
black = np.count_nonzero(np.all(pix==sought,axis=-1))
print(black)
Currently, the count returns 0 black pixels and I fail to see which part of the code malfunctions.
I want to save on disk a colormapped image (that is, a grayscale image with a colormap applied to it). Most of its values are np.nans, which I would like to keep with a special colour (e.g. red).
It works if I plot the image with plt.imshow, but not if I apply the colormap separately.
This is an example code that shows the behaviour:
import numpy as np
import matplotlib.pyplot as plt
import PIL.Image as Image
a = np.zeros((20, 20))
a[10, 10] = 10
a[a == 0] = np.nan
current_cmap = plt.cm.get_cmap('viridis')
current_cmap.set_bad(color='red')
plt.imshow(a, vmin=0, vmax=20)
That's what I would like to save on disk (as a png, not as a matplotlib figure). I would normally use PIL for saving arrays.
norm = plt.Normalize(0, 20)
b = current_cmap(norm(a))
Image.fromarray(np.uint8(b * 255)).show()
(Shown with my OS image viewer which applies some interpolation when zooming). I can see the center is colormapped just fine, but I lose the red colours for the nans.
Overall, I am doing this because I want to save the the output of imshow to disk with the right colormap and the right nan's colours, so any solution to this problem would work for me.
As documented here: https://github.com/matplotlib/matplotlib/issues/9892
the solution is
norm = plt.Normalize(0, 20)
b = current_cmap(np.ma.masked_invalid(norm(a)))
Image.fromarray(np.uint8(b * 255)).show()
I am attempting to use matplotlib to plot some figures for a paper I am working on. I have two sets of data in 2D numpy arrays: An ascii hillshade raster which I can happily plot and tweak using:
import matplotlib.pyplot as pp
import numpy as np
hillshade = np.genfromtxt('hs.asc', delimiter=' ', skip_header=6)[:,:-1]
pp.imshow(hillshade, vmin=0, vmax=255)
pp.gray()
pp.show()
Which gives:
And a second ascii raster which delineates properties of a river flowing across the landscape. This data can be plotted in the same manner as above, however values in the array which do not correspond to the river network are assigned a no data value of -9999. The aim is to have the no data values set to be transparent so the river values overlie the hillshade.
This is the river data, ideally every pixel represented here as 0 would be completely transparent.
Having done some research on this it seems I may be able to convert my data into an RGBA array and set the alpha values to only make the unwanted cells transparent. However, the values in the river array are floats and cannot be transformed (as the original values are the whole point of the figure) and I believe the imshow function can only take unsigned integers if using the RGBA format.
Is there any way around this limitation? I had hoped I could simply create a tuple with the pixel value and the alpha value and plot them like that, but this does not seem possible.
I have also had a play with PIL to attempt to create a PNG file of the river data with the no data value transparent, however this seems to automatically scale the pixel values to 0-255, thereby losing the values I need to preserve.
I would welcome any insight anyone has on this problem.
Just mask your "river" array.
e.g.
rivers = np.ma.masked_where(rivers == 0, rivers)
As a quick example of overlaying two plots in this manner:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
# Generate some data...
gray_data = np.arange(10000).reshape(100, 100)
masked_data = np.random.random((100,100))
masked_data = np.ma.masked_where(masked_data < 0.9, masked_data)
# Overlay the two images
fig, ax = plt.subplots()
ax.imshow(gray_data, cmap=cm.gray)
ax.imshow(masked_data, cmap=cm.jet, interpolation='none')
plt.show()
Also, on a side note, imshow will happily accept floats for its RGBA format. It just expects everything to be in a range between 0 and 1.
An alternate way to do this with out using masked arrays is to set how the color map deals with clipping values below the minimum of clim (shamelessly using Joe Kington's example):
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
# Generate some data...
gray_data = np.arange(10000).reshape(100, 100)
masked_data = np.random.random((100,100))
my_cmap = cm.jet
my_cmap.set_under('k', alpha=0)
# Overlay the two images
fig, ax = plt.subplots()
ax.imshow(gray_data, cmap=cm.gray)
im = ax.imshow(masked_data, cmap=my_cmap,
interpolation='none',
clim=[0.9, 1])
plt.show()
There as also a set_over for clipping off the top and a set_bad for setting how the color map handles 'bad' values in the data.
An advantage of doing it this way is you can change your threshold by just adjusting clim with im.set_clim([bot, top])
Another option is to set all cells which shall remain transparent to np.nan (not sure what's more efficient here, I guess tacaswell's answer based on clim will be the fastet). Example adapting Joe Kington's answer:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
# Generate some data...
gray_data = np.arange(10000).reshape(100, 100)
masked_data = np.random.random((100,100))
masked_data[np.where(masked_data < 0.9)] = np.nan
# Overlay the two images
fig, ax = plt.subplots()
ax.imshow(gray_data, cmap=cm.gray)
ax.imshow(masked_data, cmap=cm.jet, interpolation='none')
plt.show()
Note that for arrays of dtype=bool you should not follow your IDE's advice to compare masked_data is True for the sake of PEP 8 (E712) but stick with masked_data == True for element-wise comparison, otherwise the masking will fail:
I have a set of coordinates, say [(2,3),(45,4),(3,65)]
I need to plot them as a matrix is there anyway I can do this in matplotlib so I want it to have this sort of look http://imgur.com/Q6LLhmk
Edit: My original answer used ax.scatter. There is a problem with this: If two points are side-by-side, ax.scatter may draw them with a bit of space in between, depending on the scale:
For example, with
data = np.array([(2,3),(3,3)])
Here is a zoomed-in detail:
So here is a alternative solution that fixes this problem:
import matplotlib.pyplot as plt
import numpy as np
data = np.array([(2,3),(3,3),(45,4),(3,65)])
N = data.max() + 5
# color the background white (1 is white)
arr = np.ones((N,N), dtype = 'bool')
# color the dots black (0)
arr[data[:,1], data[:,0]] = 0
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.imshow(arr, interpolation='nearest', cmap = 'gray')
ax.invert_yaxis()
# ax.axis('off')
plt.show()
No matter how much you zoom in, the adjacent squares at (2,3) and (3,3) will remain side-by-side.
Unfortunately, unlike ax.scatter, using ax.imshow requires building an N x N array, so it could be more memory-intensive than using ax.scatter. That should not be a problem unless data contains very large numbers, however.