I have a GUI that displays a plot. I want to fit that plot to an existing image. I displayed the image under the plot using:
myaxe.plot(...)
myaxeimage = myaxe.imshow(myimage, axpect='auto', extent=myaxe.axis(), zorder=-1)
I'm already able to play with the opacity of the image, using
myaxeimage.set_alpha()
Now I'd like to be able to zoom in and out and to move around the image, using the GUI, without touching to the existing plot and axes, in order to align it with my plot. In other words, I want to scale to given sx and sy factors, and to put origin of the image at a given (x,y) point, clipping parts of the image going outside the axes. How can I do that?
There is a watermark example distributed with matplotlib that is sort of similar. Starting from that code, we can modify as follows:
Use ax.imshow to plot the image first. I do this because the extent parameter affects the final extent of ax. Since we want the final extent to be governed by the plt.plot(...), let's put it last.
myaximage = ax.imshow(im, aspect='auto', extent=(1,15,0.3,0.7), alpha=0.5, origin='upper', zorder=-1)
Instead of extent=myaxe.axis(), use extent to control the position and size of the image. extent=(1,15,0.3,0.7) places the image in the rectangle with (1, 0.3) as the bottom left corner and (15, 0.7) as the top right corner.
With origin='upper', the [0,0] index of the array im is placed at the upper left corner of the extent. With origin='lower' it would have been placed at the lower left corner.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cbook as cbook
import matplotlib.image as image
np.random.seed(1)
datafile = cbook.get_sample_data('logo2.png', asfileobj=False)
im = image.imread(datafile)
fig, ax= plt.subplots()
myaximage = ax.imshow(im, aspect='auto', extent=(1,15,0.3,0.7), alpha=0.5, zorder=-1)
ax.plot(np.random.rand(20), '-o', ms=20, lw=2, alpha=1.0, mfc='orange')
ax.grid()
plt.show()
If you want to expand the image and clip it to the extent of the plot, you might need to use ax.set_xlim and ax.set_ylim as well:
myaximage = ax.imshow(im, aspect='auto', extent=(-1,25,0.3,0.7), alpha=0.5, zorder=-1,
origin='upper')
ax.plot(np.random.rand(20), '-o', ms=20, lw=2, alpha=1.0, mfc='orange')
ax.set_xlim(0,20)
ax.set_ylim(0,1)
Or, for more control, you can clip the image to an arbitrary path by using myaximage.set_clip_path:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cbook as cbook
import matplotlib.image as image
import matplotlib.patches as patches
np.random.seed(1)
datafile = cbook.get_sample_data('logo2.png', asfileobj=False)
im = image.imread(datafile)
fig, ax= plt.subplots()
myaximage = ax.imshow(im, aspect='auto', extent=(-5,25,0.3,0.7),
alpha=0.5, origin='upper',
zorder=-2)
# patch = patches.Circle((300,300), radius=100)
patch = patches.Polygon([[5, 0.4], [15, 0.4], [15, 0.6], [5, 0.6]], closed=True,
transform=ax.transData)
myaximage.set_clip_path(patch)
ax.plot(np.random.rand(20), '-o', ms=20, lw=2, alpha=1.0, mfc='orange',
zorder=-1)
ax.set_xlim(0, 20)
ax.set_ylim(0, 1)
plt.show()
Finally, I followed tcaswell suggestion and used 2 different axes. This way, I simply have to play with set_xlim() and set_ylim() of my image axes to change the origin and/or the zooming factor of my image. I order to get the image below my plot, without hiding it with the frame of the plot, I removed the frame of the plot and used the frame of the image axes instead. I also hidden the ticks from the image axes.
from matplotlib import pyplot
f = pyplot.figure()
a = f.add_subplot(111, frameon=False) # Remove frame
a.plot(...)
myimg = pyplot.imread(...)
imgaxes = f.add_axes(a.get_position(), # new axes with same position
label='image', # label to ensure imgaxes is different from a
zorder=-1, # put image below the plot
xticks=[], yticks=[]) # remove the ticks
img = imgaxes.imshow(myimg, aspect='auto') # ensure image takes all the place
# now, to modify things
img.set_alpha(...)
imgaxes.set_xlim((x1, x2)) # x1 and x2 must be calculated from
# image size, origin, and zoom factor
Related
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.)
I wonder if you can help me with this. I have a grid of 0's and 1's that I want to add a border colour to the plot cell area if it's a 1. I've used imshow to produce a grid coloured according to value, e.g.:
a = np.random.randint(2, size=(10,10))
im = plt.imshow(a, cmap='Blues', interpolation='none', vmin=0, vmax=1, aspect='equal')
However, I can't find any border properties to change for each grid cell in imshow. I've read add_patch could be used to place a rectangle at certain points to mimic a border using on the axes values, but is there a better way than looping and applying cell-wise?
Thanks for any help you can give.
There is no build-in function to partially colorize imshow edges. The easiest option is probably indeed to draw a rectangle for each cell.
import matplotlib.pyplot as plt
import numpy as np
a = np.random.randint(2, size=(10,10))
im = plt.imshow(a, cmap='Blues', interpolation='none', vmin=0, vmax=1, aspect='equal')
def rect(pos):
r = plt.Rectangle(pos-0.5, 1,1, facecolor="none", edgecolor="k", linewidth=2)
plt.gca().add_patch(r)
x,y = np.meshgrid(np.arange(a.shape[1]),np.arange(a.shape[0]))
m = np.c_[x[a.astype(bool)],y[a.astype(bool)]]
for pos in m:
rect(pos)
plt.show()
import xarray as xr
import cartopy.crs as ccrs
USA_PROJ = ccrs.AlbersEqualArea(central_longitude=-97., central_latitude=38.)
g_simple = ds_by_month.t2m.plot(x='longitude',
y='latitude',
col='month',
col_wrap=6,
aspect=ds.dims['longitude'] / ds.dims['latitude'],
subplot_kws=dict(projection=USA_PROJ),
add_colorbar=False,
transform=ccrs.PlateCarree())
g_simple.add_colorbar(orientation='horizontal')
for ax in g_simple.axes.ravel():
ax.coastlines()
ax.set_extent([-121, -72, 22.5, 50])
plt.tight_layout()
plt.show()
On running the code above, I get the foll. figure:
How do I ensure that the colorbar is not overlapping the plots? the overlap happens even if I use the xarray default colorbar.
You could give the color bar its own set of axes and set the "bottom" value to negative so that it exceeds the bounding box, or otherwise set the subplots_adjust function using a keyword argument (i.e. hspace = 2 etc).
Here's an example with random data below (modified from matplotlib subplots example):
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(nrows=2, ncols=6, figsize=(15,5))
for ax in axes.flat:
im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)
# color bar
fig.subplots_adjust(right=0.875) #also try using kwargs bottom, top, or hspace
cbar_ax = fig.add_axes([0.1, -0.1, .8, .05]) #left, bottom, width, height
fig.colorbar(im, cax=cbar_ax, orientation="horizontal")
plt.show()
Currently, the figure I plot is all transparent shown as below, which makes it differentiate between zoomed part and the original part.
Another thing is the location of the zoomed part, "loc" keyword only has 1,...9, 9 options, can I specify the location I prefer, using coordinate for example?
axins = zoomed_inset_axes(ax, 3, loc=5) # zoom = 6
I wrote a simple code your modification purpose.
from pylab import *
import re
rc('font',family='Arial')
matplotlib.rc('legend', fontsize=24)
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
font = {'family' : 'Arial',
'weight' : 'normal',
'size' : 24}
fig = figure(figsize=(8,8))
fig.set_alpha(0.0)
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
x=[0,1]
y=[0,1]
plot(x,y)
axins = zoomed_inset_axes(ax, 3, loc=5) # zoom = 6
axins.plot(x,y)
# sub region of the original image
x1, x2, y1, y2 = 0.3, 0.4, 0.3,0.4
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)
plt.xticks(visible=False)
plt.yticks(visible=False)
# draw a bbox of the region of the inset axes in the parent axes and
# connecting lines between the bbox and the inset axes area
mark_inset(ax, axins, loc1=2, loc2=3, fc="none", ec="0.5")
plt.draw()
plt.show()
fig.savefig('1.png', transparent=True)
Below is the plot of this simple code.
Just before your call to savefig, execute:
fig.patch.set_alpha(0)
ax.patch.set_alpha(0)
axins.patch.set_alpha(1)
axins.patch.set_facecolor('#909090')
This will make the figure background transparent, as well as that of the main axes, but not of the zoomed axes.
Then, make sure not to call savefig with the option transparent=True, because that will remove all backgrounds. Just set transparent=False in that call, which is also the default for savefig.
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.