How to fit pyplot image to given axes? - python

I have axes with each axis limited:
ul_lat, ul_long = (45.499426, 9.119963)
br_lat, br_long = (45.434210, 9.235803)
ax = fig.add_axes([0,0,1,1])
ax.set_xlim(ul_long,br_long)
ax.set_ylim(br_lat,ul_lat)
Then I try to put a satelile photo as background and set up grid:
ax.imshow(image,interpolation='none')
plt.grid()
As a result I can see no image, only grid.
Now, If I remove limits:
#ax.set_xlim(ul_long,br_long)
#ax.set_ylim(br_lat,ul_lat)
I can see the image, though the figure scale is wrong and grid is plotted within this wrong scale:
See this thin grey line in the upper part of the picture - it is a wrong-scaled grid. The size of figure equal to size of picture what I don't want to (903x708). I want to use correct latitude-longitude axes same I tried to set up with xlim/ylim.
What should I do to fix?

With imshow, you can specify the extent of your image to match your coordinates:
ul_lat, ul_long = (45.499426, 9.119963)
br_lat, br_long = (45.434210, 9.235803)
ax = fig.add_axes([0,0,1,1])
ax.set_xlim(ul_long, br_long)
ax.set_ylim(br_lat, ul_lat)
ax.imshow(image, interpolation='none', extent=[ul_long, br_long, br_lat, ul_lat])
plt.grid()

Related

Plot data on top of image when image and data have different sizes

There are many questions related to adding an image to a plot. However, in all these questions the data plotted on top of the image has the same size. For example, in this question, the scatter point are plotted directly into the picture coordinates.
What I need is something else. I have the following data (example) and the following images that I wish to combine:
fig, axes = plt.subplots(1,3, figsize=(15,6), sharey=True)
axes = axes.flatten()
for ax in axes:
x = [1,2,3,4,5]
y = x
ax.bar(x,y)
fig, axes = plt.subplots(1,3, figsize=(15,6), sharey=True)
axes = axes.flatten()
for ax, im in zip(axes,images):
ax.imshow(im, alpha=0.5)
Now, if I try to plot the bars and the images unto the same axis, the bars will be so small as to be invisible. See for example the effect of set_xticks.
for ax, im in zip(axes,images):
x = [1,2,3,4,5]
y = x
ax.bar(x,y)
ax.set_xticks([1,2,3,4,5])
ax.imshow(im, alpha=0.5)
Question:
How can I plot the data on top of the images? I tried it with a twinx but I couldn't get that working properly.
I tried to rescale the data to fit the size of the images.
def scale_data(data, size):
return [int(x/max(data)*size) for x in data]
But the imshow function also flips the y-axis. Now, I can flip the image first, then flip the y-axis myself again, rescale all the data so it fits onto the images and then set the proper width of the bars, however this all feels much to complex and I assume there is a much simpler solution that I am simply missing.
What is the best way to achieve the desired behavior?
If you want to plot the graph on top of the image, use the extent parameter to adjust it. In this case, adjust it to match the x-axis of the bar chart. As an example, we use an image from the official reference. Also, subplots are not part of this assignment, so we have used a single graph.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.cbook as cbook
with cbook.get_sample_data('grace_hopper.jpg') as image_file:
image = plt.imread(image_file)
fig, ax = plt.subplots()
x = [1,2,3,4,5]
y = x
ax.bar(x, y)
im = ax.imshow(image, extent=[0,5.5,0,5.5])
plt.show()

Draw grid line on SecondaryAxis - Matplotlib

The question
I am trying to draw grid lines from the ticks of my SecondaryAxis with
ax2.grid(color=color,linestyle='--')
nothing shows up on the figure, I believe I am in the same situation as for Format SecondaryAxis ticklabels Matplotlib, aren't I ?
However, does anybody have a workaround for the issue without reversing the scales ? I mean by reversing the scale is to have the percentages scale on the main axis and the normal scale on the secondary axis.
The full code
import matplotlib.pyplot as plt
import numpy as np
#generate dummy load duration curve
dur=2500
load = np.random.normal(60,30,dur+1)
load[::-1].sort()
x=range(0,dur+1)
perticks = np.linspace(0,1,11)
xticks = perticks*dur
# get yticks from xticks
yticks = np.interp(xticks, range(0,dur+1), load)
print(yticks)
# create figure object with axe object
fig, ax1 = plt.subplots(figsize=(16, 8))
ax1.plot(x, load)
#create second axis
ax2 = ax1.secondary_yaxis('right')
# label and color of the secondaryaxis
perlabels = ['0%', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%']
color ='tab:blue'
ax2.set_yticks(yticks)
ax2.set_yticklabels(labels=perlabels)
ax2.tick_params(axis='y', color=color, labelcolor=color)
# draw grid lines on the secondaryaxis
ax2.grid(color=color,linestyle='--')
# do the same for x axis
ax3 = ax1.secondary_xaxis('top')
ax3.set_xticks(xticks)
ax3.set_xticklabels(labels=perlabels)
ax3.tick_params(axis='x', color=color, labelcolor=color)
ax3.grid(color=color,linestyle='--')
The output
I did some digging on this topic, and opened an issue on GitHub. Here's what I found out:
The SecondaryAxis is "quite new thing", added in matplotlib 3.1.0. (May 2019). Even the v.3.3.3 docs say that the secondary_xaxis() method is experimental.
The SecondaryAxis inherits from _AxesBase, which is an "implementation detail". It is not supposed (as of v.3.3.3) to work as Axes object, and the SecondaryAxis.grid() is not supposed to draw anything (like _AxesBase.grid() does). Although, I agree it is misleading that there is a non-working method.
Therefore, at the time of writing, .grid() is only assumed to work on primaxy axes.
Making the blue axis primary
Since .grid() only works on non-secondary axis, you make the primary axis blue, and move it to top & right.
Code
# Take the x and y-ticks for transfering them to secondary axis
xticks_orig = ax1.get_xticks()
yticks_orig = ax1.get_yticks()
# Make the primary axis blue since we want to draw grid on it
ax1.xaxis.tick_top()
ax1.yaxis.tick_right()
ax1.set_xticks(xticks)
ax1.set_yticks(yticks)
ax1.set_yticklabels(labels=perlabels)
ax1.set_xticklabels(labels=perlabels)
ax1.tick_params(axis="both", color=color, labelcolor=color)
ax1.grid(color=color, linestyle="--")
# Draw the black secondary axis
ax2 = ax1.secondary_yaxis("left")
ax3 = ax1.secondary_xaxis("bottom")
ax2.set_yticks(yticks_orig)
ax3.set_xticks(xticks_orig)
Adding grid lines manually
You could add the grid lines also manually, like this
xlim = ax1.get_xlim()
for y in ax2.get_yticks():
ax1.plot(xlim, (y, y), ls="--", color=color, lw=0.5)
ax1.set_xlim(xlim)
ylim = ax1.get_ylim()
for x in ax3.get_xticks():
ax1.plot((x, x), ylim, ls="--", color=color, lw=0.5)
ax1.set_ylim(ylim)
The output would look like this:
The difference here is now we draw lines on the figure which look like the grid lines.
To add on np8's answer, you can also use axvline to draw the lines. This has the advantage that you do not need to keep track of the y limits manually:
for x in ax2.get_xticks():
ax1.axvline(x, color=color, zorder=-1, linestyle="--", linewidth=0.5)
Note also that you will need to appropriately transform the x-coordinate to match the transform you do from ax1 to ax2.
Also, in my case I first had to render the canvas in order for the tick labels to be generated:
fig1.canvas.draw()

Scaling a plot (matplotlib)

I want to plot a graph with pyplot. The graph is quite big, so in order to not only see many overlapping dots indicating the nodes, I have to scale the output picture.
I used:
f,ax = plt.subplots(1,1)
ax.set_aspect('equal')
zoom=30
w, h = f.get_size_inches()
f.set_size_inches(w * zoom, h * zoom)
But now I have the problem, that I have big white spaces at the edges of the picture. There nothing is drawn and it is caused because the picture is much higher than wide.
How can I avoid this?
Instead of zooming a figure, you can use figsize argument to plt.subplots. You might also be interested in plt.tight_layout.
So you can do something like:
f,ax = plt.subplots(figsize=(10, 10)) # figure size in inches
ax.set_aspect('equal')
plt.tight_layout() # fill empty space
I also removed 1,1 from call to subplots as it's not necessary when you make only one plot.

How do you change the values of a colorbar without changing the coloring of the plot?

I am wondering if there is a way to change the values of a colorbar in a pcolormesh plot without changing the colors of the plot itself. When I manually set the values of the colorbar it always changes how it draws the picture. The original image is below:
The picture looks right, but I would like to have the colorbar match the y-axis to the first plot. So, I manually changed vmin/vmax to those values. What I get instead is
Clearly, it is taking the values of the image, which are all about zero, relatively speaking. Is there an easy way to manually adjust the displayed values of the colorbar without changing how it draws the image?
My original code for the first image is as follows:
import matplotlib.pyplot as plt
from scipy import signal
CF = 2400
fs = 100
plt.figure(figsize=(15,10))
ax1 = plt.subplot(211)
plt.ylabel('Amplitude [dBm]')
plt.xlabel('Frequency [MHz]')
plt.plot(freqArray, dbmArray)
ax2 = plt.subplot(212, sharex = ax1)
f, time, Sxx = signal.spectrogram(dataArray, fs, nfft=4096)
plt.gca().set_xlim([min(f+CF), max(f+CF)])
plt.pcolormesh(f+CF, time*(1e-3), Sxx.T)
plt.xlabel('Frequency [MHz]')
plt.ylabel('Time [ms]')
plt.gca().invert_yaxis()
plt.colorbar()
plt.show()
For the second image, the only difference is
plt.pcolormesh(f+CF, time*(1e-3), Sxx.T, vmin=min(dbmArray), vmax=max(dbmArray))

Matplotlib - unable to save image in same resolution as original image

I am unable to save the image without the white borders and at the initial resolution (1037x627)
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import pyplot, lines
import matplotlib.image as mpimg
from matplotlib.patches import Ellipse
x=[0,0,0,0,0]
y=[0,0,0,0,0]
a=10**1.3*15
inc=25
b=np.cos(np.radians(inc))*a
x[0],y[0]=516.667,313.021
x[1],y[1]=x[0]-a,y[0]
x[2],y[2]=x[0]+a,y[0]
x[3],y[3]=x[0],y[0]+b
x[4],y[4]=x[0],y[0]-b
for pa in range(0,10,5):
fig, ax = plt.subplots()
img=mpimg.imread('IC342.png')
imgplot = plt.imshow(img)
x[1],y[1]=x[0]-a/2*np.cos(np.radians(pa)),y[0]-a/2*np.sin(np.radians(pa))
x[2],y[2]=x[0]+a/2*np.cos(np.radians(pa)),y[0]+a/2*np.sin(np.radians(pa))
x[3],y[3]=x[0]+b/2*np.cos(np.radians(pa+90)),y[0]+b/2*np.sin(np.radians(pa+90))
x[4],y[4]=x[0]-b/2*np.cos(np.radians(pa+90)),y[0]-b/2*np.sin(np.radians(pa+90))
ell = Ellipse(xy=[516.667,313.021], width=a, height=b, angle=pa, edgecolor='b',lw=4, alpha=0.5, facecolor='none')
name='plt'+str(pa)+'.png'
leg='PA='+str(pa)
#ax.text(10, 10, leg, fontsize=15,color='white')
ax.add_artist(ell)
xn=[x[1],x[2],x[0]]
yn=[y[1],y[2],y[0]]
xnw=[x[3],x[4],x[0]]
ynw=[y[3],y[4],y[0]]
line = lines.Line2D(xn, yn, linestyle='-.',lw=5., color='r', alpha=0.4)
line1 = lines.Line2D(xnw, ynw, linestyle='-.',lw=5., color='g', alpha=0.4)
ax.add_line(line)
ax.add_line(line1)
plt.axis('off')
fig.savefig(name, transparent=True, bbox_inches='tight', pad_inches=0,dpi=150 )
initial image
Result
Also I need the white text PA=something to be on the image without changing the resolution. From what I understand adding another figure like text might automatically change the resolution.
Thank you for your time!
There are two factors at play here:
An Axes doesn't take up the entire Figure by default
In matplotlib, the Figure's size is fixed, and the contents are stretched/squeezed/interpolated to fit the figure. You want the Figure's size to be defined by its contents.
To do what you want to do, there are three steps:
Create a figure based on the size of the image and a set DPI
Add a subplot/axes that takes up the entire figure
Save the figure with the DPI you used to calculate figure's size
Let's use a random Hubble image from Nasa http://www.nasa.gov/sites/default/files/thumbnails/image/hubble_friday_12102015.jpg. It's a 1280x1216 pixel image.
Here's a heavily commented example to walk you through it:
import matplotlib.pyplot as plt
# On-screen, things will be displayed at 80dpi regardless of what we set here
# This is effectively the dpi for the saved figure. We need to specify it,
# otherwise `savefig` will pick a default dpi based on your local configuration
dpi = 80
im_data = plt.imread('hubble_friday_12102015.jpg')
height, width, nbands = 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, interpolation='nearest')
# Add something...
ax.annotate('Look at This!', xy=(590, 650), xytext=(500, 500),
color='cyan', size=24, ha='right',
arrowprops=dict(arrowstyle='fancy', fc='cyan', ec='none'))
# Ensure we're displaying with square pixels and the right extent.
# This is optional if you haven't called `plot` or anything else that might
# change the limits/aspect. We don't need this step in this case.
ax.set(xlim=[-0.5, width - 0.5], ylim=[height - 0.5, -0.5], aspect=1)
fig.savefig('test.jpg', dpi=dpi, transparent=True)
plt.show()
The saved test.jpg will be exactly 1280x1216 pixels. Of course, because we're using a lossy compressed format for both input and output, you won't get a perfect pixel match due to compression artifacts. If you used lossless input and output formats you should, though.

Categories