How do I determine the [fig]size of a matplotlib.image.AxesImage in pixel? - matplotlib

This code renders the Lenna image with matplotlib,
import urllib
import matplotlib.pyplot as plt
imgurl = 'https://upload.wikimedia.org/wikipedia/en/thumb/7/7d/Lenna_%28test_image%29.png/330px-Lenna_%28test_image%29.png'
f = urllib.request.urlopen(imgurl)
img = plt.imread(f)
axi = plt.imshow(img)
where axi is an instance of matplotlib.image.AxesImage
How do I determine the [fig]size of the AxesImage in pixel? the expected value might (330, 330)
I tried axi.get_window_extent() and got
Bbox([[112.68, 36.00000000000003], [330.12, 253.44000000000003]])
Where do those values (112.68, 330.12) come from?

To get the raw image size
Use AxesImage.get_size():
axi.get_size()
# (330, 330)
To convert the axes extent into pixels
Adjust the window extent by Figure.dpi:
axi = plt.imshow(img)
fig = plt.gcf()
bbox = axi.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
width = bbox.width * fig.dpi
height = bbox.height * fig.dpi
# 334.79999999999995 217.43999999999997
The reason this is not 330x330 is because of how plt.imshow() handles the aspect ratio. If you plot with aspect='auto', the underlying axes' shape becomes visible:
axi = plt.imshow(img, aspect='auto')
To coerce the underlying axes into desired shape
Manually define figsize and rect using the pixel dimensions and desired dpi:
width_px, height_px, _ = img.shape
dpi = 96
figsize = (width_px / dpi, height_px / dpi) # inches
rect = [0, 0, 1, 1] # [left, bottom, width, height] as fraction of figsize
fig = plt.figure(figsize=figsize, dpi=dpi) # in inches
axes = fig.add_axes(rect=rect)
axi = axes.imshow(img, aspect='auto')
Then the extent pixels will be exactly 330x330:
bbox = axi.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
width = bbox.width * fig.dpi
height = bbox.height * fig.dpi
# 330.0 330.0

axi.get_size() gives (330,330) - why not use that?

Related

set_position and set_size_inches does not work properly when overlaying imshow and scatter in matplotlib

I am trying to create an image from a matrix z2 over a raster defined by np.meshgrid(grid_x, grid_y) such that the value of the image at vx=grid_x[i], vy=grid_y[j] is z2[i, j]. On top of this image, I am trying to add a scatter plot of a number of points obtained by three vectors x, y, z such that the i-th point has the coordinate (x[k], y[k]) and the value z[k]. All of these scattered points lies within the region of the aforementioned raster.
Here's an example of the aforementioned data I am trying to plot.
import numpy as np
np.random.seed(1)
z2 = np.ones((1000, 1000)) * 0.66
z2[0, 0] = 0
z2[-1, -1] = 1
x = np.random.rand(1000) * 1000
y = np.random.rand(1000) * 1000
z = np.random.rand(1000)
grid_x = np.linspace(0, 999, 1000)
grid_y = np.linspace(0, 999, 1000)
In order to do this, I am using a 2D plot where the x and y values are used to define the position of the points and z is indicated by a color drawn from a colormap.
What is required of this image is that 1) there should be no white space between the actual plot and the edge of the figure; 2) the unit length on the x and y axis should be equal; 3) the image should not be too large. In order to achieve these, I am using the following code for plotting.
import matplotlib.pyplot as plt
from matplotlib import cm
def plot_img(x, y, z, grid_x, grid_y, z2, set_fig_size=True):
# determine the figure size
if set_fig_size:
height, width = np.array(z2.shape, dtype=float)
dpi = max(max(640 // height, 640 // width), 1)
width, height = width * dpi, height * dpi
plt.gcf().set_size_inches(width, height)
plt.gcf().set_dpi(dpi)
# plot the figure
plt.gca().axis('off')
plt.gca().axis('equal')
plt.gca().set_position([0, 0, 1, 1])
plt.xlim((grid_x[0], grid_x[-1]))
plt.ylim((grid_y[0], grid_y[-1]))
# the raster
cmap = cm.get_cmap('gray')
cmap.set_bad(color='red', alpha=0.5)
plt.imshow(z2, cmap=cmap, interpolation='none', origin='lower',
extent=(grid_x[0], grid_x[-1], grid_y[0], grid_y[-1]))
# the scatter plot
min_z, max_z = np.min(z), np.max(z)
c = (z - min_z) / (max_z - min_z)
plt.scatter(x, y, marker='o', c=c, cmap='Greens')
plt.show()
Strangely, when I run plot_img(x, y, z, grid_x, grid_y, z2) using the aforementioned example data, the following image shows up.
Essentially, only the raster data got plotted, while the scattered data is not.
I then tried plot_img(x, y, z, grid_x, grid_y, z2, set_fig_size=False). The result is
Note that here to clearly show the white spaces in the figure, I kept the background of PyCharm surrounding it. Essentially, there are white spaces that I do not wish included in this figure.
I wonder why this is happening, and how I can fix the code to get the correct output, which is essentially the second result without the white spaces. Thanks!
Replace your dpi and figsize code by
# determine the figure size
height, width = np.array(z2.shape, dtype=float)
dpi = 200
# get size in inches:
width, height = height / dpi, width / dpi
plt.gcf().set_size_inches(width, height)
plt.gcf().set_dpi(dpi)
and you will have a 1000x1000 pixel figure, which at 200 dpi is 5"x5".

How do I invert matplotlib bars at a specific point instead of when negative?

I'd like to invert the bars in this diagram when they are below 1, not when they are negative. Additionally I'd like to have even spacing between the ticks/steps on the y-axis
Here is my current code
import matplotlib.pyplot as plt
import numpy as np
labels = ['A','B','C']
Vals1 = [28.3232, 12.232, 9.6132]
Vals2 = [0.00456, 17.868, 13.453]
Vals3 = [0.0032, 1.234, 0.08214]
x = np.arange(len(labels))
width = 0.2
fig, ax = plt.subplots()
rects1 = ax.bar(x - width, Vals1, width, label='V1')
rects2 = ax.bar(x, Vals2, width, label='V2')
rects3 = ax.bar(x + width, Vals3, width, label='V3')
ax.set_xticks(x)
ax.set_xticklabels(labels)
plt.xticks(rotation=90)
ax.legend()
yScale = [0.0019531,0.0039063,0.0078125,0.015625,0.03125,0.0625,0.125,0.25,0.5,1,2,4,8,16,32]
ax.set_yticks(yScale)
plt.show()
I believe I've stumbled upon the answer, here it is for anyone else looking for the solution. Add the argument bottom='1' to ax.bar instantiation, and then flip the values in the array.
for i in range(len(Vals1)):
Vals1[i] = (1 - Vals1[i]) * -1
As you mentioned, the key is the bottom param of Axes.bar:
bottom (default: 0): The y coordinate(s) of the bars bases.
But beyond that, you can simplify your plotting code using pandas:
Put your data into a DataFrame:
import pandas as pd
df = pd.DataFrame({'V1': Vals1, 'V2': Vals2, 'V3': Vals3}, index=labels)
# V1 V2 V3
# A 28.3232 0.00456 0.00320
# B 12.2320 17.86800 1.23400
# C 9.6132 13.45300 0.08214
Then use DataFrame.sub to subtract the offset and DataFrame.plot.bar with the bottom param:
bottom = 1
ax = df.sub(bottom).plot.bar(bottom=bottom)

Shrink matplotlib parasite axis horizontally to take up approximately 25% of the image length

I have an image like the one below:
The issue is I need the curves to only take up about 25% - 30% of the image. In other words I need to shrink the size of the two parasite axes horizontally. Is this even possible?
Here is what I have so far:
"""
Plotting _____________________________________________________________________________________________________________
"""
fig = plt.figure(figsize=(20,15))
host1 = host_subplot(211, axes_class=AA.Axes)
plt.subplots_adjust(right=0.75)
#Create custom axes
cax1 = plt.axes(frameon=False)
# Now create parasite axis
par11 = host1.twiny()
par12 = host1.twiny()
top_offset = 50
new_fixed_axis1 = par12.get_grid_helper().new_fixed_axis
par12.axis["top"] = new_fixed_axis1(loc="top",
axes=par11,
offset=(0, top_offset))
par11.axis["top"].toggle(all=True)
par12.axis["top"].toggle(all=True)
# Bottom Axis
bottom_offset1 = -50
bottom_offset2 = -100
par21 = host1.twiny()
par22 = host1.twiny()
new_fixed_axis2 = par21.get_grid_helper().new_fixed_axis
par21.axis["bottom"] = new_fixed_axis2(loc="bottom",
axes=par12,
offset=(0, bottom_offset1))
# Set Host Axis Labels
host1.set_xlabel("UTC Time")
host1.set_ylabel("Elevation (km")
# Set Top Axis Labels
par11.set_xlabel("Sonde Potential Temperature (K)")
par12.set_xlabel("Sonde Relative Humidity %")
vmin, vmax = np.min(chan_1064), np.max(chan_1064)
im = host1.imshow(chan_1064, aspect="auto", cmap=get_a_color_map(), vmin=-2e-4, vmax=0.6e-2,
extent=(min(xs), max(xs), min(bin_alt_array), max(bin_alt_array)))
scatter = host1.scatter(xs, ys, s=100, color='gold')
host1.set_xlim(min(xs), max(xs))
fig.colorbar(im)
plt.draw()
leg = plt.legend( loc = 'lower right')
# Adjust Fonts
font = {'family' : 'normal',
'weight' : 'bold',
'size' : 12}
mpl.rc('font', **font)
plt.tight_layout()
plt.show()
Sorry if it's a simple solution but, I have not been able to figure it out for the life of me.

colormap for 3d bar plot in matplotlib applied to every bar

Does anyone know how to implement easily colormaps to 3d bar plots in matplotlib?
Consider this example, how do I change each bar according to a colormap? For example, short bars should be mainly blue, while taller bars graduate their colors from blue towards the red...
In the physical sciences, it's common to want a so-called LEGO plot, which is I think what the original user is going for. Kevin G's answer is good and got me to the final result. Here's a more advanced histogram, for x-y scatter data, colored by height:
xAmplitudes = np.random.exponential(10,10000) #your data here
yAmplitudes = np.random.normal(50,10,10000) #your other data here - must be same array length
x = np.array(xAmplitudes) #turn x,y data into numpy arrays
y = np.array(yAmplitudes) #useful for regular matplotlib arrays
fig = plt.figure() #create a canvas, tell matplotlib it's 3d
ax = fig.add_subplot(111, projection='3d')
#make histogram stuff - set bins - I choose 20x20 because I have a lot of data
hist, xedges, yedges = np.histogram2d(x, y, bins=(20,20))
xpos, ypos = np.meshgrid(xedges[:-1]+xedges[1:], yedges[:-1]+yedges[1:])
xpos = xpos.flatten()/2.
ypos = ypos.flatten()/2.
zpos = np.zeros_like (xpos)
dx = xedges [1] - xedges [0]
dy = yedges [1] - yedges [0]
dz = hist.flatten()
cmap = cm.get_cmap('jet') # Get desired colormap - you can change this!
max_height = np.max(dz) # get range of colorbars so we can normalize
min_height = np.min(dz)
# scale each z to [0,1], and get their rgb values
rgba = [cmap((k-min_height)/max_height) for k in dz]
ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=rgba, zsort='average')
plt.title("X vs. Y Amplitudes for ____ Data")
plt.xlabel("My X data source")
plt.ylabel("My Y data source")
plt.savefig("Your_title_goes_here")
plt.show()
Note: results will vary depending on how many bins you choose and how much data you use. This code needs you to insert some data or generate a random linear array. Resulting plots are below, with two different perspectives:
So maybe not exactly what you're looking for (perhaps a good starting point for you), but using
Getting individual colors from a color map in matplotlib
can give varying solid colors for the bars:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.cm as cm # import colormap stuff!
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x, y = np.random.rand(2, 100) * 4
hist, xedges, yedges = np.histogram2d(x, y, bins=4, range=[[0, 4], [0, 4]])
# Construct arrays for the anchor positions of the 16 bars.
# Note: np.meshgrid gives arrays in (ny, nx) so we use 'F' to flatten xpos,
# ypos in column-major order. For numpy >= 1.7, we could instead call meshgrid
# with indexing='ij'.
xpos, ypos = np.meshgrid(xedges[:-1] + 0.25, yedges[:-1] + 0.25)
xpos = xpos.flatten('F')
ypos = ypos.flatten('F')
zpos = np.zeros_like(xpos)
# Construct arrays with the dimensions for the 16 bars.
dx = 0.5 * np.ones_like(zpos)
dy = dx.copy()
dz = hist.flatten()
cmap = cm.get_cmap('jet') # Get desired colormap
max_height = np.max(dz) # get range of colorbars
min_height = np.min(dz)
# scale each z to [0,1], and get their rgb values
rgba = [cmap((k-min_height)/max_height) for k in dz]
ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=rgba, zsort='average')
plt.show()
Personally, I find that ugly as sin! But it probably won't look too bad with a sequential colormap - https://matplotlib.org/examples/color/colormaps_reference.html

matplotlib - imshow 'extents' definiton killed plt.text

I am quite the novice at matplotlib, so bear with me. I have the code below that plots a cylindrical equidistant grid of precipitation. I set the 'extents' limits that finally aligned my basemap with the data. Now, it appears to have "broken" my plt.text capability as I can no longer see the text 'Precipitation Rate (mm/hour)'. Thanks for any help.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from pylab import *
import pickle
from mpl_toolkits.basemap import Basemap
fp = open('uneven_rgb.pkl', 'rb')
uneven_rgb = pickle.load(fp)
fp.close()
num_lon = 1440
num_lat = 400
precipfile = "/Users/bolvin/3B43.20111001.7.HDF_precip.bin"
fileobj = open(precipfile, mode='rb') # Open file as read only binary
data = np.fromfile (fileobj, dtype ='f')
datat = np.reshape(data, (num_lon, num_lat), order = 'FORTRAN')
datam = datat * 24.0
my_cmap = matplotlib.colors.LinearSegmentedColormap('my_colormap',uneven_rgb)
plt.figure(figsize = (20,10))
mapproj = Basemap(projection = 'cyl', llcrnrlat=-50.0, llcrnrlon=0.0, urcrnrlat=50.0,urcrnrlon=360.0)
mapproj.drawcoastlines()
mapproj.drawcountries()
mapproj.drawparallels(np.array([-30.0, 0.0, 30.0]), labels=[0,0,0,0])
mapproj.drawmeridians(np.array([90.0, 180.0, 270.0]), labels=[0,0,0,0])
myplot = plt.imshow(datam.T, interpolation = 'nearest', cmap = my_cmap, vmin = 0.0, vmax = 20.0, extent = (0.0, 360.0, -50.0, 50.0))
plt.title('October 2011 3B43 Precipitation', fontsize = 36, y = 1.03)
plt.text(1.0, 435.0, 'Precipitation Rate (mm/hour)', size = 20)
cbar = plt.colorbar(myplot, orientation='horizontal', shrink = 0.5, pad = 0.03)
cbar.ax.tick_params(labelsize=20)
plt.gca().axes.get_xaxis().set_visible(False)
plt.gca().axes.get_yaxis().set_visible(False)
plt.show()
fileobj.close()
plt.text gets as first argument the x and y coordinates on which your text will be put.
As you transformed your imshow plot into the bordes 0-360 for x and -50 to 50 for y, y=435 is not in the plot anymore.
You can check your limits with plt.gca().get_xlim().
You have to move it somewhere in your limits.
Your defining the units you are plotting with this text, right? So the natural place for this would be the label of the colorbar:
cbar = plt.colorbar(myplot, orientation='horizontal', shrink = 0.5,
pad = 0.03, label='Precipitation Rate (mm/hour)')