I'm working on some image processing algorithms using python and matplotlib. I'd like to display the original image and the output image in a figure using a subplot (e.g. the original image next to the output image). The output image(s) are of different size than the original image. I'd like to have the subplot display the images in their actual size (or uniformly scaled) so that I can compare "apples to apples". I currently use:
plt.figure()
plt.subplot(2,1,1)
plt.imshow(originalImage)
plt.subplot(2,1,2)
plt.imshow(outputImage)
plt.show()
The result is that I get the subplot, but both images are scaled so that they are the same size (despite the fact that the axes on the output image are different than the axes of the input image). Just to be explicit: if the input image is 512x512 and the output image is 1024x1024 then both images are displayed as though they are the same size.
Is there a way to force matplotlib to either display the images at their respective actual sizes (preferable solution so that matplotlib's dynamic rescaling doesn't effect the displayed image) or to scale the images such that they are displayed with sizes proportional to their actual sizes?
This is the answer that you are looking for:
def display_image_in_actual_size(im_path):
dpi = 80
im_data = plt.imread(im_path)
height, width, depth = im_data.shape
# What size does the figure need to be in inches to fit the image?
figsize = width / float(dpi), height / float(dpi)
# Create a figure of the right size with one axes that takes up the full figure
fig = plt.figure(figsize=figsize)
ax = fig.add_axes([0, 0, 1, 1])
# Hide spines, ticks, etc.
ax.axis('off')
# Display the image.
ax.imshow(im_data, cmap='gray')
plt.show()
display_image_in_actual_size("./your_image.jpg")
Adapted from here.
Adapting Joseph's answer here: Apparently the default dpi changed to 100, so to be safe in the future you can directly access the dpi from the rcParams as
import matplotlib as mpl
import matplotlib.pyplot as plt
def display_image_in_actual_size(im_path):
dpi = mpl.rcParams['figure.dpi']
im_data = plt.imread(im_path)
height, width, depth = im_data.shape
# What size does the figure need to be in inches to fit the image?
figsize = width / float(dpi), height / float(dpi)
# Create a figure of the right size with one axes that takes up the full figure
fig = plt.figure(figsize=figsize)
ax = fig.add_axes([0, 0, 1, 1])
# Hide spines, ticks, etc.
ax.axis('off')
# Display the image.
ax.imshow(im_data, cmap='gray')
plt.show()
display_image_in_actual_size("./your_image.jpg")
If you are looking to show images at their actual size, so the actual pixel size is the same for both images in subplots, you probably just want to use the options sharex and sharey in the subplot definition
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(15, 7), dpi=80, sharex=True, sharey=True)
ax[1].imshow(image1, cmap='gray')
ax[0].imshow(image2, cmap='gray')
results in:
Where the second image is 1/2 size of the first one.
Related
I am trying to add an image to the "whitespace" behind the various subplots of a matplotlib figure.
Most discussions similar to this topic are to add images to the plots themselves, however I have not yet come across a means to change the background of the overall "canvas".
The most similar function I have found is set_facecolor(), however this only allows a single color to be set as the background.
fig, ax = plt.subplots(2,2)
fig.patch.set_facecolor('xkcd:mint green')
plt.show()
However, I am seeking a solution to import an image behind the plots, similar to this (manually made):
I have googled, searched SO, and looked through the matplotlib docs but I only get results for either plt.imshow(image) or set_facecolor() or similar.
You can use a dummy subplot, with the same size as the figure, and plot the background onto that subplot.
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
image = plt.imread('test.jpg')
# make ticks white, for readability on colored background
mpl.rcParams.update({'xtick.color': "white",
'ytick.color': "white",
'axes.labelcolor': "white"})
# create a figure with 4 subplots, with the same aspect ratio as the image
width = 8
fig, axs = plt.subplots(nrows=2, ncols=2, figsize=(width, width * image.shape[0] / image.shape[1]))
for ax in np.ravel(axs):
ax.patch.set_alpha(0.7) # make subplots semi-transparent
background_ax = plt.axes([0, 0, 1, 1]) # create a dummy subplot for the background
background_ax.set_zorder(-1) # set the background subplot behind the others
background_ax.imshow(image, aspect='auto') # show the backgroud image
# plot something onto the subplots
t = np.linspace(0, 8 * np.pi, 2000)
for i in range(2):
for j in range(2):
axs[i, j].plot(np.sin(t * (i + 2)), np.sin(t * (j + 4)))
# plt.tight_layout() gives a warning, as the background ax won't be taken into account,
# but normally the other subplots will be rearranged to nicely fill the figure
plt.tight_layout()
plt.show()
I have an image which already has semilogarithmic scale in the y axis:
I would like to imshow this in matplotlib such that I can plot stuff over it. Here's the approach I took:
image = Image.open("cropped.jpg")
fig = plt.figure()
ax = plt.gca()
ax.imshow(image, extent=(0, 6, 1, 400), aspect="auto")
ax.set_yscale('log')
Unfortunately, this stretches my data and I end up with the following:
How can I preserve the underlying data but still readjust the scale to semilog?
I am displaying images from a 2D array with pyplot and have removed axis markings and padding. However, between rows of images, there is still whitespace which I would like to remove. The images themselves have no whitespace.
fig = plt.figure(figsize=(10, 10))
for x in range(quads_x):
for y in range(quads_y):
# ADD IMAGES
fig.add_subplot(quads_y, quads_x, (quads_x * x) + y + 1)
plt.imshow(cv2.imread("./map/" + winning_maps[x][y], 0))
# PYPLOT FORMATTING
plt.subplots_adjust(wspace=0, hspace=0)
ax = plt.gca()
ax.axis("off")
ax.xaxis.set_major_locator(matplotlib.ticker.NullLocator())
ax.yaxis.set_major_locator(matplotlib.ticker.NullLocator())
The code yields something like
Any ideas on how I should deal with the issue?
Normally using plt.subplots_adjust(wspace=0, hspace=0) would collapse all of the axes onto each other. The issue you are running into is that using imshow fixes the aspect ratio of the axes in the plot.
To compensate, you need to adjust the size of your canvas so that the frame has the same ratio as the images you are showing. The next issue is that the border padding around the axes is a ratio of the size of the image. If you are ok with it, you can remove the border, drop in the images, then adjust the height of the canvas to be the figure height times the ratio of the images times the number of rows of images divided by the number of columns of images.
Here is an example:
from matplotlib import pyplot as plt
from PIL import Image
img = Image.open('fox.jpg').resize(80,50)
fig, axes = plt.subplots(rows, columns, figsize=(7,7))
for ax in axes.ravel():
ax.imshow(img)
ax.set_autoscale_on(False)
ax.axis('off')
plt.subplots_adjust(hspace=0, wspace=0, left=0, bottom=0, right=1, top=1)
r, c = axes.shape
fig.set_figheight(fig.get_figwidth() * ax.get_data_ratio() * r / c )
plt.show()
Here is the image before using set_figheight:
And here it is with the adjustment:
I am trying to add an image behind each subplot of my scatter plot figure. I want my image to take up all the subplot space. But I do not want to map my scatter points onto the image: That is, I want the axes of my scatter points to be independent to that of the image.
When I simply use imread() and implot() while making a subplot to insert the image, like so:
im = plt.imread("/Users/mac/Desktop/image.jpeg")
two = plt.subplot(222)
implot = plt.imshow(im)
plt.title('4-8 Hz')
plt.scatter(X,Y, s=100, marker ='o', c=AveragedHursts4to8, cmap = cm.plasma)
plt.colorbar()
two.axis('off')
I get the right-most image down below, where, clearly, the image axes and scatter points axes are shared.
I tried to use the twiny() function to make a new set of axes for the image, with the image set as the first axes and the second axes set to the scatter points, like so:
onetwin = plt.subplot(221)
plt.title('1-4 Hz')
implot = plt.imshow(im, zorder=1)
onetwin.axis('off')
one = onetwin.twiny()
plt.scatter(X,Y, s=100, marker ='o', c=AveragedHursts1to4, cmap = cm.plasma, zorder = 2)
plt.colorbar()
one.axis('off')
There I get the leftmost image, where the scatter points are squished on the y axis and the image, for some reason, has been shrunk.
And when I switch the ordering of the creation of the axes for twiny, the image takes up the whole subplot and the scatter points do not show at all.
Suggestions?
My suggestion would be to leave the points' positions untouched and scale the background image accordingly. One can use the extent keyword to imshow for that purpose.
In the example below I plot some random points on four different scales. Each time the image is scaled to the scatterplot's dimensions using the extent keyword.
import matplotlib.pyplot as plt
import numpy as np
x = np.random.rand(8*8).reshape((8,8))
image = plt.imread("https://upload.wikimedia.org/wikipedia/en/2/27/EU_flag_square.PNG")
fig, ax = plt.subplots(ncols=4, figsize=(11,3.8))
for i in range(len(ax)):
ax[i].scatter(x[2*i,:]*10**(i-1), x[2*i+1,:]*10**(i-1), c="#ffcc00", marker="*", s=280, edgecolors='none')
xlim = ax[i].get_xlim()
ylim = ax[i].get_ylim()
mini = min(xlim[0],ylim[0])
maxi = max(xlim[1],ylim[1])
ax[i].imshow(image, extent=[mini, maxi, mini, maxi])
plt.tight_layout()
plt.show()
The simplest, fastest solution I came up with is to solve for x and y in:
largest_x_coodinate_value(x) = x_dimension of image_in_pixels
largest_y_coordinate_value(y) = y_dimension_of_image_in_pixels
And then do vectorized multiplication over the numpy arrays containing the X and Y coordinates with those calculated x,y values, effectively scaling the coordinates to the size of the image.
I am trying to plot a scatterplot over an image without having any white space around it.
If I plot just the image as follows, then there is no white space:
fig = plt.imshow(im,alpha=alpha,extent=(0,1,1,0))
plt.axis('off')
fig.axes.axis('tight')
fig.axes.get_xaxis().set_visible(False)
fig.axes.get_yaxis().set_visible(False)
but as I add a scatter plot over the image as follows:
fig = plt.scatter(sx, sy,c="gray",s=4,linewidths=.2,alpha=.5)
fig.axes.axis('tight')
fig.axes.get_xaxis().set_visible(False)
fig.axes.get_yaxis().set_visible(False)
At this point, by using the following savefig command, the white space is added around the image:
plt.savefig(im_filename,format="png",bbox_inches='tight',pad_inches=0)
Any idea on how to remove the white space definitely?
By switching to the mpl object-oriented style, you can plot both the image and the scatter plot on the same axes, and hence only have to set the whitespace once, by using ax.imshow and ax.scatter.
In the example below, I've used subplots_adjust to remove the whitespace around the axes, and ax.axis('tight') to set the axis limits to the data range.
import matplotlib.pyplot as plt
import numpy as np
# Load an image
im = plt.imread('stinkbug.png')
# Set the alpha
alpha = 0.5
# Some random scatterpoint data
sx = np.random.rand(100)
sy = np.random.rand(100)
# Creare your figure and axes
fig,ax = plt.subplots(1)
# Set whitespace to 0
fig.subplots_adjust(left=0,right=1,bottom=0,top=1)
# Display the image
ax.imshow(im,alpha=alpha,extent=(0,1,1,0))
# Turn off axes and set axes limits
ax.axis('tight')
ax.axis('off')
# Plot the scatter points
ax.scatter(sx, sy,c="gray",s=4,linewidths=.2,alpha=.5)
plt.show()
This worked for expanding images to full screen in both show and savefig with no frames, spines or ticks, Note everything is done in the plt instance with no need to create the subplot, axis instance or bbox:
from matplotlib import pyplot as plt
# create the full plot image with no axes
plt.subplots_adjust(left=0, right=1, bottom=0, top=1)
plt.imshow(im, alpha=.8)
plt.axis('off')
# add scatter points
plt.scatter(sx, sy, c="red", s=10, linewidths=.2, alpha=.8)
# display the plot full screen (backend dependent)
mng = plt.get_current_fig_manager()
mng.window.state('zoomed')
# save and show the plot
plt.savefig('im_filename_300.png', format="png", dpi=300)
plt.show()
plt.close() # if you are going on to do other things
This worked for at least 600 dpi which was well beyond the original image resolution at normal display widths.
This is very convenient for displaying OpenCV images without distortion using
import numpy as np
im = img[:, :, ::-1]
to convert the colour formats before the plt.imshow.