I have found multiple similar questions with this subject but so far I couldn't adapt any solution to my needs, so I'm sorry for reposting.
I'm trying to plot a grid of png images using matplotlib, the closest I've got to what I want is using the code below, which can be found here https://matplotlib.org/stable/gallery/axes_grid1/simple_axesgrid.html .
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
import numpy as np
im1 = np.arange(100).reshape((10, 10))
im2 = im1.T
im3 = np.flipud(im1)
im4 = np.fliplr(im2)
fig = plt.figure(figsize=(4., 4.))
grid = ImageGrid(fig, 111, # similar to subplot(111)
nrows_ncols=(2, 2), # creates 2x2 grid of axes
axes_pad=0.1, # pad between axes in inch.
)
for ax, im in zip(grid, [im1, im2, im3, im4]):
# Iterating over the grid returns the Axes.
ax.imshow(im)
plt.show()
My question is, how do I get rid of the x and y ticks/labels and also give each image a title?
Again, I'm sorry for repeating the question.
This code
import matplotlib.pyplot as plt
image = plt.imread("sample.png")
fig, axes = plt.subplots(2, 3)
for row in [0, 1]:
for column in [0, 1, 2]:
ax = axes[row, column]
ax.set_title(f"Image ({row}, {column})")
ax.axis('off')
ax.imshow(image)
plt.show()
is going to produce
Related
I'm trying to create a 4x2 plot on a slightly non-rectangular dataset (x-axis range is smaller than y-axis range) using plt.subplots and assign colorbars using make_axes_locatable to have the colorbars nice and flush wih the subplots. I always end up with huge paddings/margins between the two subplot columns which I suspect originate from the colorbars... I've tried multiple things from many stackoverflow questions (e.g. using fig.subplots_adjust(), constrained_layout=True etc.) but to no avail. The margins between the two columns stay really large (see img below) and make the image unreadable...
Any input would be much appreciated!
Code used to reproduce the issue:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
data = np.random.random((10, 6))
fig, axs = plt.subplots(nrows=4, ncols=2, figsize=(16, 8), sharex=True, sharey=False, constrained_layout=True)
for idx in range(4):
# plot image
im1 = axs[idx, 0].imshow(data, cmap=cm.coolwarm)
im2 = axs[idx, 1].imshow(data, cmap=cm.viridis)
# make colorbars
ax1_divider = make_axes_locatable(axs[idx, 0])
cax1 = ax1_divider.append_axes("right", size="2%", pad=0.05)
cb1 = fig.colorbar(im1, cax=cax1, orientation="vertical")
ax2_divider = make_axes_locatable(axs[idx, 1])
cax2 = ax2_divider.append_axes("right", size="2%", pad=0.05)
cb2 = fig.colorbar(im2, cax=cax2, orientation="vertical")
You are plotting multiple images (which by default it tries to keep an equal aspect ratio), in which the height is greater than the width. Therefore, total height of the images > total width of the images.
It follows that one way to reduce the white spacing between columns is to reduce the width of the figure.
Try setting this:
fig, axs = plt.subplots(nrows=4, ncols=2, figsize=(4, 8), sharex=True, sharey=False, constrained_layout=True)
I've got code that produces a square image with smaller plots to the left and below the main image plot by using GridSpec with width_ratios and height_ratios:
import matplotlib.pyplot as plt
import numpy as np
# Some fake data.
imdata = np.random.random((100, 100))
extradata1 = np.max(imdata, axis=1)
extradata2 = np.max(imdata, axis=0)
fig = plt.figure(constrained_layout=True)
spec = fig.add_gridspec(ncols=2, nrows=2, width_ratios=(1, 8), height_ratios=(8, 1))
# Main image plot.
ax1 = fig.add_subplot(spec[:-1, 1:], aspect='equal')
ax1.imshow(imdata, cmap='viridis')
# Vertical (left) plot.
ax2 = fig.add_subplot(spec[:-1, 0], sharey=ax1)
ax2.plot(extradata1, range(imdata.shape[0]))
# Horizontal (bottom) plot.
ax3 = fig.add_subplot(spec[-1, 1:], sharex=ax1)
ax3.plot(range(imdata.shape[1]), extradata2)
plt.show()
I'd like the height of the left plot and the width of the bottom plot to be equal to the height and width of the main image, respectively. Currently as you can see the horizontal plot is wider than the image's horizontal size, and they also scale differently as the figure is scaled. Is it possible to constrain axis dimensions to those of other axes?
Calling imshow() with aspect='auto' should fix your problem:
ax1.imshow(imdata, cmap='viridis',aspect='auto')
For some more explanation on this, please look here:
Imshow: extent and aspect
import matplotlib.pyplot as plt
import numpy as np
# Some fake data.
imdata = np.random.random((100, 100))
extradata1 = np.max(imdata, axis=1)
extradata2 = np.max(imdata, axis=0)
fig = plt.figure(constrained_layout=True)
spec = fig.add_gridspec(ncols=2, nrows=2, width_ratios=(1, 8), height_ratios=(8, 1))
# Main image plot.
ax1 = fig.add_subplot(spec[:-1, 1:])
ax1.imshow(imdata, cmap='viridis',aspect='auto')
# Vertical (left) plot.
ax2 = fig.add_subplot(spec[:-1, 0], sharey=ax1)
ax2.plot(extradata1, range(imdata.shape[0]))
# Horizontal (bottom) plot.
ax3 = fig.add_subplot(spec[-1, 1:], sharex=ax1)
ax3.plot(range(imdata.shape[1]), extradata2)
Result:
Fourier's answer worked nicely, but I also found that I could get the desired behaviour by changing constrained_layout=True to constrained_layout=False in the plt.figure call.
Using aspect aspect="auto" works, it has the disadvantage of giving you non-square pixels.
For this kind of tasks, I found that the axes_grid toolkit is pretty useful
from mpl_toolkits.axes_grid1 import make_axes_locatable
# Some fake data.
imdata = np.random.random((100, 100))
extradata1 = np.max(imdata, axis=1)
extradata2 = np.max(imdata, axis=0)
fig, main_ax = plt.subplots()
divider = make_axes_locatable(main_ax)
bottom_ax = divider.append_axes("bottom", 1.2, pad=0.1, sharex=main_ax)
left_ax = divider.append_axes("left", 1.2, pad=0.1, sharey=main_ax)
bottom_ax.xaxis.set_tick_params(labelbottom=False)
left_ax.yaxis.set_tick_params(labelleft=False)
main_ax.imshow(imdata, cmap='viridis')
left_ax.plot(extradata1, range(imdata.shape[0]))
bottom_ax.plot(range(imdata.shape[1]), extradata2)
plt.show()
I'm trying to put a little arrow in the corner of each of my subplots. Below is the sample code I'm using:
import matplotlib.pyplot as plt
import matplotlib.image as image
from numpy import linspace
xs = linspace(0, 1, 100)
im = image.imread('arrow.png')
def multi_plot():
fig, axes = plt.subplots(4, 1)
x = 0
for axis in axes:
axis.plot(xs, xs**2)
axis.imshow(im, extent=(0.4, 0.6, .5, .7), zorder=-1, aspect='auto')
plt.show()
multi_plot()
Unfortunately, this produces 4 subplots that are entirely dominated by the arrows and the plots themselves are not seen.
Example output - Incorrect:
What do I need to do so that each individual subplot has a small image and the plot itself can be seen?
I think it's worthwhile thinking about putting the image in a box and place it similar to the legend, using a loc argument. The advantage is that you don't need to care about extents and data coordinates at all. You also wouldn't need to take care of what happens when zooming or panning the plot. Further it allows to keep the image in it's original resolution (zoom=1 in below code).
import matplotlib.pyplot as plt
import matplotlib.image as image
from numpy import linspace
from matplotlib.offsetbox import OffsetImage,AnchoredOffsetbox
xs = linspace(0, 1, 100)
im = image.imread('arrow.png')
def place_image(im, loc=3, ax=None, zoom=1, **kw):
if ax==None: ax=plt.gca()
imagebox = OffsetImage(im, zoom=zoom*0.72)
ab = AnchoredOffsetbox(loc=loc, child=imagebox, frameon=False, **kw)
ax.add_artist(ab)
def multi_plot():
fig, axes = plt.subplots(4, 1)
for axis in axes:
axis.plot(xs, xs**2)
place_image(im, loc=2, ax=axis, pad=0, zoom=1)
plt.show()
multi_plot()
You'll notice that the limits on the x and y axis have been set to the extent of the imshow, rather than 0-1, which your plot needs to see the line.
You can control this by using axis.set_xlim(0, 1) and axis.set_ylim(0, 1).
import matplotlib.pyplot as plt
import matplotlib.image as image
from numpy import linspace
xs = linspace(0, 1, 100)
im = image.imread('arrow.png')
def multi_plot():
fig, axes = plt.subplots(4, 1)
x = 0
for axis in axes:
axis.plot(xs, xs**2)
axis.imshow(im, extent=(0.4, 0.6, .5, .7), zorder=-1, aspect='auto')
axis.set_xlim(0, 1)
axis.set_ylim(0, 1)
plt.show()
multi_plot()
Alternatively, if you want to maintain the extra 5% margin around your data that matplotlib uses by default, you can move the imshow command to before the plot command, then the latter will control the axis limits.
import matplotlib.pyplot as plt
import matplotlib.image as image
from numpy import linspace
xs = linspace(0, 1, 100)
im = image.imread('arrow.png')
def multi_plot():
fig, axes = plt.subplots(4, 1)
x = 0
for axis in axes:
axis.imshow(im, extent=(0.4, 0.6, .5, .7), zorder=-1, aspect='auto')
axis.plot(xs, xs**2)
plt.show()
multi_plot()
I'm using python 3.5.2 and matplotlib 1.5.3.
I'm doing some colormaps with the "y" axis shared.
The problem is that when placing the colorbar in the last subplot I lose the first and last tick on the x axis. But if you enlarge the figure (figsize = (12,3) for example) some white space appears at the edges of the other subplot.
import numpy as np
import matplotlib.pyplot as plt
matrix = np.random.random((10, 10, 3))
fig = plt.figure(figsize=(10, 3)) # Try a figsize=(12, 3)
for i in range(3):
if i == 0:
ay1 = plt.subplot(1, 3, i+1)
else:
plt.subplot(1, 3, i+1, sharey=ay1)
plt.imshow(matrix[:, :, i], interpolation='nearest')
if i == 2:
plt.colorbar()
plt.show()
What is the correct way to do it?
Using sharey only makes sense, if you use different sized images. But when the images are differently sized, in some parts of the figure there is nothing - which will be painted white.
On the other hand, if you do have same size pictures as here, there is no need to use sharey. In that case, you can simply plot your data and add a colorbar.
import numpy as np
import matplotlib.pyplot as plt
matrix = np.random.random((10, 10, 3))
fig, ax = plt.subplots(1,3, figsize=(12, 3))
plt.subplots_adjust(left=0.05, right=0.85)
for i in range(3):
im = ax[i].imshow(matrix[:, :, i], interpolation='nearest')
ax[i].set_aspect("equal")
plt.draw()
p = ax[-1].get_position().get_points().flatten()
ax_cbar = fig.add_axes([0.9,p[1], 0.02, p[3]-p[1]] )
plt.colorbar(im, cax=ax_cbar)
plt.show()
The following code snippet
import matplotlib.pyplot as plt
import numpy as np
arr1 = np.arange(100).reshape((10,10))
arr2 = np.arange(25).reshape((5,5))
fig, (ax1, ax2, ) = plt.subplots(nrows=2, figsize=(3,5))
ax1.imshow(arr1, interpolation="none")
ax2.imshow(arr2, interpolation="none")
plt.tight_layout()
plt.show()
produces two images with the same size, but a much lower "pixel density" in the second one.
I would like to have the second image plotted at the same scale (i.e. pixel density) of the first, without filling the subfigure, possibly correctly aligned (i.e. the origin of the image in the same subplot position as the first one.)
Edit
The shapes of arr1 and arr2 are only an example to show the problem. What I'm looking for is a way to ensure that two different images generated by imshow in different portions of the figure are drawn at exactly the same scale.
The simplest thing I could think of didn't work, but gridspec does. The origins here aren't aligned explicitly, it just takes advantage of how gridspec fills rows (and there's an unused subplot as a spacer).
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import gridspec
sizes = (10, 5)
arr1 = np.arange(sizes[0]*sizes[0]).reshape((sizes[0],sizes[0]))
arr2 = np.arange(sizes[1]*sizes[1]).reshape((sizes[1],sizes[1]))
# Maybe sharex, sharey? No, we pad one and lose data in the other
#fig, (ax1, ax2, ) = plt.subplots(nrows=2, figsize=(3,5), sharex=True, sharey=True)
fig = plt.figure(figsize=(3,5))
# wspace so the unused lower-right subplot doesn't squeeze lower-left
gs = gridspec.GridSpec(2, 2, height_ratios = [sizes[0], sizes[1]], wspace = 0.0)
ax1 = plt.subplot(gs[0,:])
ax2 = plt.subplot(gs[1,0])
ax1.imshow(arr1, interpolation="none")
ax2.imshow(arr2, interpolation="none")
plt.tight_layout()
plt.show()