Issues removing vertical white spaces between rows of images in matplotlib - python

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:

Related

How to add an image as the background to a matplotlib figure (not to plots, but to the "whitespace" ala set_face() )

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()

matplotlib how to set plot size (with dpi), not figure size

I could find a way to set a figure size with dpi
px = 1/plt.rcParams['figure.dpi']
fig = plt.figure(figsize=(1580*px, 25*px))
(reference: https://matplotlib.org/stable/gallery/subplots_axes_and_figures/figure_size_units.html)
fig = plt.figure(figsize=(1580*px, 25*px))
plt.plot(xx, y[0], label='min')
plt.plot(xx, y[1], label='max')
plt.yticks(y_ticks, y_tick_labels)
plt.ylim(top=y_max)
plt.legend()
However, how do you set the plot size?
I want my plot or graph to be full of (1580px, 25px)
but if I set the figure size and plot graphs using the above code, then the graph does not fit the figure (1580px, 25px). Even worse, labels or ticks are not shown well in the figure like below.
I want my graph size to be the above white space size( for example, 1580px, 25px) and then draw ticks and labels outside the white space (then figure size should be bigger than the given plot size). But I couldn't find a way to set the plot size. I could only find a way to set the figure size.
import matplotlib.pyplot as plt
import numpy as np
def axes_with_pixels(width, height, margin=0.2):
px = 1/plt.rcParams['figure.dpi']
fig_width, fig_height = np.array([width, height]) / (1 - 2 * margin)
fig, ax = plt.subplots(figsize=(fig_width*px, fig_height*px))
fig.subplots_adjust(left=margin, right=1-margin,
bottom=margin, top=1-margin)
return fig, ax
fig, ax = axes_with_pixels(580, 80) # Specify the Axes size in pixels
X = np.linspace(0, 10, 10)
Y0 = np.sin(X)
Y1 = np.cos(X)
plt.plot(X, Y0, label='min')
plt.plot(X, Y1, label='max')
plt.legend()
As you can see, the Axes (plot area) is exactly 580 * 80 pixels. (Note, the shown width of 581 pixels is due to the offset of the right edge.)
However, axes_with_pixels can be only used to set a single Axes with a specified pixels. If you want a figure to have multiple Axes with some specified pixels, then you have to consider wspace and hspace in subplots_adjust to get the figure size.

Over-plot an equation curve over a png image

enter image description hereI'm having trouble overplotting a relation between radial velocity and offset(position). I've looked at various solutions, but it doesn't seem to work. I've converted the equation into numbers, with only one variable.It also doesn't display the picture to the required dimensions.
x = np.linspace(-0.8 ,0.8 , 1000)
y = 0.5*((1.334e+20/x)**0.5)
img = plt.imread('Pictures/PVdiagram1casaviewer.png')
fig, ax = plt.subplots(figsize=(16, 16), tight_layout=True)
ax.set_xlabel('Offset(arcsec)', fontsize=14)
ax.set_ylabel('Radial Velocity (Km/S)', fontsize=14)
ax.imshow(img, extent=[-0.8, 0.8, -5, 15])
ax.plot(x, y, linewidth=5, color='white')
plt.title('PV Diagram')
plt.show()
enter image description here
If I plot your image, you can see that the axis of the image and matplotlib don't match, because the image contains space between the plot and border of the pictures (axis titles, and so on...)
So, first you need to crop the image, so that it contains just the plot area.
Next, you can plot the image with the argument aspect=auto to scale it to your figsize:
ax.imshow(img, extent=[-0.8,0.8,-5,15], aspect='auto')
If you try to plot your y function over the image, you will see that the values of y are much larger, so the curve is above the image (notice the tiny image is at the bottom).
I don't know what the physical background of y is, but if you divide it by 10e9 it fits inside the image-range.
Full code:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-0.8 ,0.8 , 1000)
y = 0.5*((1.334e+20/x)**0.5)/10e9 # Scale it here... but how?
img = plt.imread('hNMw82.png')
fig, ax = plt.subplots(figsize=(16, 16), tight_layout=True)
ax.set_xlabel('Offset(arcsec)', fontsize=14)
ax.set_ylabel('Radial Velocity (Km/S)', fontsize=14)
ax.imshow(img, extent=[-0.8,0.8,-5,15], aspect='auto')
ax.plot(x, y, linewidth=5, color='white')
ax.set_ylim([-5,15])
ax.set_xlim([-0.8,0.8])
plt.title('PV Diagram')
plt.show()
Result:
(I also set the axis limits.)

How to completely remove the white space around a scatter plot

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.

Displaying different images with actual size in matplotlib subplot

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.

Categories

Resources