So in python I have the following code, taken from this answer:
import matplotlib.pyplot as plt
import sympy
x = sympy.symbols('x')
y = 1 + sympy.sin(sympy.sqrt(x**2 + 20))
lat = sympy.latex(y)
#add text
plt.text(0, 0.6, r"$%s$" % lat, fontsize = 50)
#hide axes
fig = plt.gca()
fig.axes.get_xaxis().set_visible(False)
fig.axes.get_yaxis().set_visible(False)
plt.savefig('out.png', bbox_inches='tight', pad_inches=0)
plt.show()
This opens the text and exports it to a png file just fine:
But this includes whitespace beyond the whitespace outside of the frame. How would you go about cropping the image to export only the text, like a bounding box, like so?
The following is not a perfect solution, but it will hopefully give you some ideas on how to progress:
import matplotlib.pyplot as plt
import sympy
x = sympy.symbols('x')
y = 1 + sympy.sin(sympy.sqrt(x**2 + 2))
lat = sympy.latex(y)
fig = plt.figure()
renderer = fig.canvas.get_renderer()
t = plt.text(0.001, 0.001, f"${lat}$", fontsize=50)
wext = t.get_window_extent(renderer=renderer)
fig.set_size_inches(wext.width / 65, wext.height / 40, forward=True)
fig.patch.set_facecolor('white')
plt.axis('off')
plt.tight_layout()
plt.savefig('out.png', bbox_inches='tight', pad_inches=0)
plt.show()
The idea being that you can determine the size of your text by getting the window extent using the current renderer. It is then also possible to manually specify a figure size. I am though not sure on the correct approach to convert between the two. Note, I added a border to the image so you can see that amount of remaining padding:
As a workaround to this problem, the following approach simply makes use of Python's PIL library to automatically crop the image before saving it:
import io
from PIL import Image, ImageOps
import matplotlib.pyplot as plt
import sympy
x = sympy.symbols('x')
y = 5 /sympy.sqrt(1 + sympy.sin(sympy.sqrt(x**2 + 2)))
lat = sympy.latex(y)
fig = plt.figure()
t = plt.text(0.001, 0.001, f"${lat}$", fontsize=50)
fig.patch.set_facecolor('white')
plt.axis('off')
plt.tight_layout()
with io.BytesIO() as png_buf:
plt.savefig(png_buf, bbox_inches='tight', pad_inches=0)
png_buf.seek(0)
image = Image.open(png_buf)
image.load()
inverted_image = ImageOps.invert(image.convert("RGB"))
cropped = image.crop(inverted_image.getbbox())
cropped.save('out.png')
The cropped version looks like:
Related
I am trying to plot some meteorological data onto a map and I would like to add an image of a plane using imshow. Plotting i) the trajectory, ii) some contour-data and iii) the image, works fine. But as soon as I add a contourf-plot (see below) the image dissapears!
Any ideas how to fix this?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import cartopy.crs as crs
import cartopy.feature as cfeature
def plot_test():
#DEFINE DATA
x,y = np.meshgrid(np.linspace(0,90,100),np.linspace(0,90,100))
z = x**3 + y**3
#BEGIN FIGURE (IN THIS CASE A MAP, IM PLOTTING METEOROLOGICAL DATA)
fig = plt.figure(figsize = (6,6))
ax1 = plt.axes(projection=crs.PlateCarree(central_longitude=0))
ax1.set_extent([0,90,0,90], crs=crs.PlateCarree())
ax1.coastlines(resolution='auto', color='k')
#EXAMPLE DATA PLOTTED AS CONTOURF
v_max = int(z.max())
v_min = int(z.min())
qcs = ax1.contourf(x, y, z, cmap = "Blues", vmin = v_min, vmax = v_max)
sm = plt.cm.ScalarMappable(cmap="Blues",norm=qcs.norm)
sm._A = []
cbar = plt.colorbar(sm, ax=ax1,orientation="vertical")
cbar.ax.set_ylabel("some contourf data", rotation=90, fontsize = 15)
#PLOT IMAGE OF A PLANE (THIS IS NOT SHOWING UP ON THE PLOT!)
x0 = 50
y0 = 40
img=plt.imread("plane2.png")
ax1.imshow(img,extent=[x0,x0 - 10, y0, y0-10], label = "plane")
plt.show()
without contourf (code from above with lines 14-20 commented out):
with contourf:
Thank you 1000 times #JohanC (see comments). I simply had to place the z-order:
ax1.imshow(img, ...., zorder=3)
which made the plane show up!
The code at the bottom does exactly what I want it to do, but exclusively to a matplotlib version below at least 3.3.4 . For this version, 3.3.4, I get the following error message:
AttributeError: 'ColorBar' object has no attribute 'set_clim'
Accordingly, I tried to find out, how to do this in today's version, but failed.
So, how can I change the color scale of the image and the Colobar in the newer versions?
Working Code (tested in 2.2.2):
import matplotlib.pyplot as plt
import numpy as np
import time
x = np.linspace(0, 10, 100)
y = np.cos(x)
y = y.reshape(10,10)
plt.ion()
figure = plt.figure()
line1 = plt.imshow(y)
cbar = plt.colorbar(line1)
for p in range(100):
updated_y = np.random.randint(0,10)*np.cos(x-0.05*p).reshape(10,10)
line1.set_data(updated_y)
cbar.set_clim(vmin=np.min(updated_y),vmax=np.max(updated_y)) #this line creates the error
cbar.draw_all()
figure.canvas.draw()
figure.canvas.flush_events()
time.sleep(1)
I found a solution with the by #Trenton McKinneys provided link in the following post: Question by Merk.
Solved code:
import matplotlib.pyplot as plt
import numpy as np
import time
x = np.linspace(0, 10, 100)
y = np.cos(x)
y = y.reshape(10,10)
plt.ion()
figure = plt.figure()
line1 = plt.imshow(y)
cbar = plt.colorbar(line1)
for p in range(100):
updated_y = np.random.randint(0,10)*np.cos(x-0.05*p).reshape(10,10)
line1.set_data(updated_y)
#cbar.set_clim(vmin=np.min(updated_y),vmax=np.max(updated_y)) #this line creates the error
cbar.mappable.set_clim(vmin=np.min(updated_y),vmax=np.max(updated_y)) #this works
cbar.draw_all()
figure.canvas.draw()
figure.canvas.flush_events()
time.sleep(1)
(One) provided image:
See matplotlib: api_changes_3.1.0/ColorbarBase inheritance
Per matplotlib: api_changes_3.3.0/removals.rst:
Using colorbar.ColorbarBase.set_clim results in AttributeError: 'ColorBar' object has no attribute 'set_clim'
Use matplotlib.cm.ScalarMappable.set_clim instead
See matplotlib.cm, matplotlib.cm.ScalarMappable, set_clim(), and matplotlib.colorbar
Also, .set_clim can be used on a returned image object, instead of the colorbar object.
matplotlib: Image Tutorial Example modified to use object oriented interface.
Using stinkbug.png
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
img = mpimg.imread('stinkbug.png')
lum_img = img[:, :, 0]
fig, (ax1, ax2)= plt.subplots(1, 2, figsize=(10, 7))
im1 = ax1.imshow(lum_img)
im1.set_cmap('nipy_spectral')
ax1.set_title('Before')
fig.colorbar(im1, ax=ax1, ticks=[0.1, 0.3, 0.5, 0.7], orientation='horizontal')
im2 = ax2.imshow(lum_img)
im2.set_cmap('nipy_spectral')
im2.set_clim(0.0, 0.7) # set clim on the im2 image object
ax2.set_title('After')
fig.colorbar(im2, ax=ax2, ticks=[0.1, 0.3, 0.5, 0.7], orientation='horizontal')
I would like to add color bar with jet color map into my frame exactly like the picture below. tried to look over the web but did not find anything.
any ideas?
You could do the following:
create a transparent heatmap with correct alpha values
use plt.contourf to add the heatmap on the image
use inset_axes to position the colorbar
Example:
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
def transparent_cmap(cmap, N=255):
fix_cmap = cmap
fix_cmap._init()
fix_cmap._lut[:,-1] = np.linspace(0, 0.8, N+4)
return fix_cmap
fix_cmap = transparent_cmap(plt.cm.Reds)
im = Image.open('test.jpg')
w, h = im.size
y, x = np.mgrid[0:h, 0:w]
heat = np.zeros((h,w))
heat[500:550,500:550] = np.random.random((50,50))
fig, ax = plt.subplots(1, 1)
ax.imshow(im)
plt.contourf(x, y, heat, 15, cmap=fix_cmap)
cbaxes = inset_axes(ax, width="3%", height="30%", loc=1)
plt.colorbar(cax=cbaxes)
plt.show()
This will give:
I'm trying to produce an image from an array using imshow, and export it to file without having any whitespace added.
In the case in which the data has equal width and height I managed to achieve this by following this answer:
import numpy as np
import matplotlib.pyplot as plt
def borderless_imshow_save(data, outputname, size=(1, 1), dpi=80):
fig = plt.figure()
fig.set_size_inches(size)
ax = plt.Axes(fig, [0, 0, 1, 1])
ax.set_axis_off()
fig.add_axes(ax)
ax.imshow(data);
plt.savefig(outputname, dpi=dpi)
data = np.random.randn(40, 40)
borderless_imshow_save(data, 'test.png', dpi=100)
This works perfectly.
However, I actually need to do this for data that is rectangular, that is, something like np.random.randn(40, 100).
In this case, the code above does not work, as again whitespace is produced in the final image.
I tried playing with the size parameter and the arguments of plt.Axes but without success.
What's the best way to achieve this?
Note that imsave actually works here with something like
plt.imsave('test.png', np.random.randn(40, 100))
the problem with this is that with imsave I do not have access to same amount of options I have with imshow.
The problem is you are specifying a square figure size with size=(1,1) and then plotting a rectangular image. I have modified your code to eliminate the white space around the figure by automatically setting the figure size to match the dimensions of the input data. The size parameter now specifies the width of the image, and the height is scaled from that:
import numpy as np
import matplotlib.pyplot as plt
def borderless_imshow_save(data, outputname, size = 1, dpi=80):
width = 1*size
height = data.shape[0] / data.shape[1] * size
size=(width, height)
fig = plt.figure(figsize=size, dpi=dpi)
ax = fig.add_axes([0, 0, 1, 1])
ax.set_axis_off()
ax.imshow(data);
fig.savefig(outputname, dpi=dpi)
data = np.random.randn(40, 100)
borderless_imshow_save(data, 'test.png', size=5, dpi=100)
Saved image:
An easy option is to not care about the actual size of the figure and just crop the image automatically while saving.
import numpy as np
import matplotlib.pyplot as plt
data = np.random.randn(40, 100)
fig, ax = plt.subplots()
ax.imshow(data)
ax.set_axis_off()
fig.savefig("data.png", bbox_inches="tight", pad_inches=0)
I have written this code to check object bounding box but when I give title to the axes, it doesn't show up. (I was going to give the file number as title).
#!/home/ckim/anaconda2/bin/python
#%pylab
import os.path as osp
import sys
import cv2
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as patches
def add_path(path):
if path not in sys.path:
sys.path.insert(0, path)
def move_figure(f, x, y):
backend = matplotlib.get_backend()
f.canvas.manager.window.move(x,y)
plt.show()
# Add lib to PYTHONPATH
lib_path = osp.join('/home/ckim/Neuro/py-faster-rcnn/', 'lib')
add_path(lib_path)
import datasets
import datasets.pascal_voc as pv
#plt.ion()
fig, ax = plt.subplots(figsize=(8,8))
im = cv2.imread(osp.join('/home/ckim/Neuro/py-faster-rcnn/data/VOCdevkit2007/VOC2007/JPEGImages/', '{0:06d}'.format(eval(sys.argv[1])) + '.jpg'))
#im = cv2.imread(osp.join('000005.jpg'))
im = im[:, :, (2, 1, 0)]
ax.imshow(im, aspect='equal')
#res = pv._load_pascal_annotation(sys.argv[1])
d = datasets.pascal_voc('trainval', '2007')
res = d._load_pascal_annotation('{0:06d}'.format(eval(sys.argv[1])))
# return {'boxes' : boxes,
# 'gt_classes': gt_classes,
# 'gt_overlaps' : overlaps,
# 'flipped' : False}
for i in range(len(res['boxes'])):
x1 = res['boxes'][i][0]
y1 = res['boxes'][i][1]
x2 = res['boxes'][i][2]
y2 = res['boxes'][i][3]
ax.add_patch(patches.Rectangle((x1,y1), x2-x1, y2-y1, fill=False, edgecolor='red', linewidth=1.5))
ax.text(x1, y1 - 5, '{:s}'.format(d._classes[res['gt_classes'][i]]), \
bbox=dict(facecolor='blue', alpha=0.5), fontsize=14, color='white')
#thismanager = get_current_fig_manager()
#thismanager.window.SetPosition((500, 0))
#thismanager.window.wm_geometry("+500+0")
move_figure(fig, 500, 500)
#fig.show()
#fig.suptitle("Title x")
ax.set_title("Title x")
plt.pause(0)
To test what is the problem, I reduced the code to below, but this abridged version works either for graph plot (case 1) and image display (case 2). I can't find the difference from above code. Could anyone tell me what has gone wrong in above code? (about title now showing)
#!/home/ckim/anaconda2/bin/python
import cv2
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(8,8))
# case 1 : plot
#ax.plot([1,2,3,4],[1,4,9,16])
# case 2 : image display
im = cv2.imread('000005.jpg')
im = im[:, :, (2, 1, 0)]
ax.imshow(im, aspect='equal')
ax.set_title("Title x")
plt.pause(0)
There is a call to plt.show() in move_figure which means the figure is shown. As this is a blocking command, no further code will be run until you close this figure. As a result, the title is not set until the figure has disappeared. If you swap the last few lines of you first code as follows,
ax.set_title("Title x")
move_figure(fig, 500, 500)
plt.pause(0)
the title should appear. Alternatively, I'd suggest removing plt.show from move_figure so you can show when you want or savefig etc later on.