Add image behind scatter subplot independent of scatter points axes - python

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.

Related

How to fit/size subplots with aspect and box_aspect in matplotlib (python)

I'm trying to fit the sizes of 2x2 subplots so they line up correctly.
I want to create the following subplot/axes structure:
ax1 has a set aspect ratio in data coordinates (e.g. data is not scaled when aspect=1)
ax2 and ax3 have a set (box) aspect ratio in display/figure coordinates (e.g. they appear as squares when box_aspect=1)
ax1 and ax3 share the x axis limits and have the same width
ax1 and ax2 share the y axis limits and have the same height
ax4 fits in the last box so that it has the width of ax2 and the height of ax3
I need this for the following project (it's an animation):
Imagine having ax1 as the world space in which a point is moving. (the coordinates should not be warped)
The adjacent plots show the x(ax3) and y(ax2) coordinates of the point over time. (I want to be able to set the aspect ratio of these subplots)
The last axes/plot is not related to any of the coordinates but should fit in nicely.
I created a minimal example and added descriptive text to the picture:
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = plt.subplot(221, anchor='SE', aspect=1, xlim=(0,1), ylim=(0,2))
ax2 = plt.subplot(222, anchor='SW', box_aspect=1, sharey=ax1, xlim=(0,3))
ax3 = plt.subplot(223, anchor='NE', box_aspect=1, sharex=ax1, ylim=(0,4))
ax4 = plt.subplot(224, anchor='NW', xlim=(0,5), ylim=(0,6))
plt.show()
Notes:
the anchors are set so there is not so much whitespace between the subplots
the x and y limits are arbitrarily chosen and should not matter
I encountered a similar issue as I was plotting GeoJson data in matplotlib.
I solved it by creating a one subplot figure setting aspect=1
# import the required libraries
import geopandas as gpd
import matplotlib.pyplot as plt
# Define the file path
fp = r"\Your-file-full-path\file.geojson"
# Read the GeoJSON file similarly as Shapefile
mygeojson = gpd.read_file(fp)
# Create a figure with one subplot
fig = plt.figure()
# Plot the grid with column-to-plot (as you set cmap, scheme, and aspect hyper-params)
mygeojson.plot(aspect=1, column = 'geojson-column-to-plot', cmap = 'gist_rainbow', scheme = 'equalinterval', k=9, linewidth=0, legend=True);
# Add title
plt.title("Your GeoDataFrame object title");
# Remove white space around the figure
plt.tight_layout()

Mirror the color scale of a Seaborn heatmap, both colors and labels

I have a heatmap produced with the Seaborn module as shown here.
As the values in the table generally increase with the distance from MSL (the values increase going down in the table), I want to mirror the color scale such that the dark blue color is at the bottom with the corresponding label (12), and the light yellow color is at the top, with label (3).
I only found a way to invert the colors, but then the labels remain in place. How can I mirror the entire scale (both colors and labels)?
Thanks!
invert_yaxis() on the ax of the colorbar reverses both the ticklabels as well as the colorbar (invert_xaxis() does the same for a horizontal colorbar).
Seaborn's heatmap doesn't directly return a handle to the generated colorbar. It can be obtained via the plot's ax: cbar = ax.collections[0].colorbar.
Here is an example:
import seaborn as sns
import numpy as np
import matplotlib.pylab as plt
x, y = np.meshgrid(np.arange(20), np.arange(20))
arr = (x * y) % 10
ax = sns.heatmap(arr, annot=True, cbar=True)
cbar = ax.collections[0].colorbar
cbar.ax.invert_yaxis()
plt.show()

Python matplotlib - add borders to grid plot based on value

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

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.

matplotlib: colour bar resizing image?

I'm assuming I have a really simple question, which has been driving me insane for the past hour. So, I am trying to produce a contour plot with the following axis lengths x=37,y=614. I can produce a contour plot no problem, but when I add a colour bar the image becomes resized to what i'm assuming is the size of the colour bar.
Image without colour bar:
Image with colour bar:
The figure becomes resized and I do not know why.
How can I plot a figure like my first figure but with the colour scheme of the second figure and with a colour bar?
code:
import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
from matplotlib import pylab
y = np.arange(1, 615)
x = np.arange(1, 37)
z = np.loadtxt('145_contact_matrix_605.txt')
fig = plt.figure()
ax = plt.subplot(111)
CS = ax.contour(x, y, z)
plt.clabel(CS, inline=1, fontsize=10)
# COLOUR BAR CODE
im_out = ax.imshow(z, cmap=cm.jet)
ax.matshow(z,cmap=plt.cm.jet)
axcolor = fig.add_axes([0.9,0.1,0.02,0.8]) # adjust these vaules to position colour bar
pylab.colorbar(im_out, cax=axcolor)
plt.show()
It's the imshow command that's changing the aspect ratio of the axes, not the colorbar.
imshow assumes you want an aspect ratio of 1.0 so that a square in data coordinates will appear square (i.e. square pixels).
If you want it to behave like contour, the just specify aspect='auto'.
ax.imshow(z, cmap=cm.jet)
You should also remove the ax.matshow line (or use it instead of imshow). As it is, you'll have two images that partially overlap and hide each other.
If you do decide to use matshow instead of imshow, you'll need to specify aspect='auto' for it, as well.

Categories

Resources