pyplot axes title not showing - python

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.

Related

How can I adjust 2 plot with one button in matplotlib

I'm just studying Python for a month and have no experience.
I'm trying to hide/show two graphs with one Check button in matplotlib. But with my code, when clicking the button, there is only one graph hidden. Please see my code and show me my mistake.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import math
from matplotlib.widgets import Button, RadioButtons, CheckButtons
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
p = ax.scatter(5,6,7) and ax.scatter(1,2,3, color='red', marker='+', s=1e2)
lines = [p]
labels = ["Hide/Show"]
def func1(label):
index = labels.index(label)
lines[index].set_visible(not lines[index].get_visible())
fig.canvas.draw()
a = [True]
# xposition, yposition, width, height
ax_check = plt.axes([0, 0.01, 0.25, 0.25])
plot_button = CheckButtons(ax_check, labels, a)
plot_button.on_clicked(func1)
plt.show()
# D.L Your suggestion is perfect.
Just add another line to the figure and in the function fun1 add a calling of the line 2:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import math
from matplotlib.widgets import Button, RadioButtons, CheckButtons
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
p1 = ax.scatter(5,6,7)
p2 = ax.scatter(1,2,3, color='red', marker='+', s=1e2)
lines = [p1, p2]
labels = ["Hide/Show"]
def func1(label):
index = labels.index(label)
lines[index].set_visible(not lines[index].get_visible())
lines[index+1].set_visible(not lines[index+1].get_visible())
fig.canvas.draw()
a = [True]
# xposition, yposition, width, height
ax_check = plt.axes([0, 0.01, 0.25, 0.25])
plot_button = CheckButtons(ax_check, labels, a)
plot_button.on_clicked(func1)
plt.show()

Using fig.transFigure to draw a patch on ylabel

I created a sequence of points that I would like to convert into a Patch.
The goal is then to draw the patch on the left side of the y-label (see in Red in the figure), or draw it in any other part of the figure.
Although it can be accomplished with Gridspec, I would like to do it with a Patch.
import matplotlib.pyplot as plt
import numpy as np
plt.figure()
npoints = 100
td = np.linspace(np.pi*3/4, np.pi*5/4, npoints)
xd = np.cos(td)
yd = np.sin(td)
plt.plot(xd,yd)
EDIT1:
I am now able to make a Patch (just need to move it outside the axis):
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.path as mpath
import matplotlib.patches as mpatches
npoints = 100
td = np.linspace(np.pi*3/4, np.pi*5/4, npoints)
xd = np.cos(td)
yd = np.sin(td)
fig, ax = plt.subplots()
ax.axis([-2, 0, -1, 1])
verts=np.c_[xd,yd]
codes = np.ones(len(xd))*2 # Path.LINETO for all points except the first
codes[0] = 1 #Path.MOVETO only for the first point
path1 = mpath.Path(verts, codes)
patch = mpatches.PathPatch(path1, facecolor='none')
ax.add_patch(patch)
The result:
Now, I only need to move it outside the axis, maybe using a translation or scale.
I'm sure the key to do it is somewhere in this Matplotlib Transforms tutorial, more specifically, I am pretty sure the solution is using fig.transFigure.
EDIT 2: Almost there!
In order to use Figure coordinates (that are between [0,1]) I normalized the points that define the path. And instead of using ax.add_patch() that adds a patch to the axis, I use fig.add_artist() that adds the patch to the figure, over the axis.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.path as mpath
import matplotlib.patches as mpatches
#Normalized Data
def normalize(x):
return (x - min(x)) / (max(x) - min(x))
#plt.figure()
npoints = 100
td = np.linspace(np.pi*3/4, np.pi*5/4, npoints)
xd = np.cos(td)
yd = np.sin(td)
#plt.plot(xd,yd)
xd = normalize(xd)
yd = normalize(yd)
fig, ax = plt.subplots()
ax.axis([-2, 2, -1, 1])
verts=np.c_[xd,yd]
codes = np.ones(len(xd))*2 # Path.LINETO for all points except the first
codes[0] = 1 #Path.MOVETO only for the first point
path1 = mpath.Path(verts, codes)
patch1 = mpatches.PathPatch(path1, facecolor='none')
ax.add_patch(patch1)
patch2 = mpatches.PathPatch(path1, fc='none', ec='red', transform=fig.transFigure)
fig.add_artist(patch2)
And the result so far:
Doing this, I just need to scale and translate the patch, maybe using Affine2D.
EDIT 3: Done!
Finally I was able to do it! I used Try and Error in the scale() and translate() parameters as I did not get what coordinate system they were using. However, it would be great to get the exact y center (0.5 in Figure coordinates).
Here is the complete code:
import numpy as np
import matplotlib.path as mpath
import matplotlib.patches as mpatches
#Normalized Data
def normalize(x):
return (x - min(x)) / (max(x) - min(x))
npoints = 100
td = np.linspace(np.pi*3/4, np.pi*5/4, npoints)
xd = np.cos(td)
yd = np.sin(td)
xd = normalize(xd)
yd = normalize(yd)
fig, ax = plt.subplots()
ax.axis([-2, 2, -1, 1])
verts=np.c_[xd,yd]
codes = np.ones(len(xd))*2 # Path.LINETO for all points except the first
codes[0] = 1 #Path.MOVETO only for the first point
path1 = mpath.Path(verts, codes)
patch1 = mpatches.PathPatch(path1, fc='none', ec='green')
ax.add_patch(patch1) #draw inside axis
patch2 = mpatches.PathPatch(path1, fc='none', ec='C0', transform=fig.transFigure)
fig.add_artist(patch2) #this works! Draw on figure
import matplotlib.transforms as mtransforms
tt = fig.transFigure + mtransforms.Affine2D().scale(0.02, 0.8).translate(10,45)
patch3 = mpatches.PathPatch(path1, fc='none', ec='red', transform=tt)
fig.add_artist(patch3)
And the resulting figure:
As #Pedro pointed out, most of this can be found in the tutorial that he linked. However, here is a short answer.
Basically, it's almost as if you're creating a line plot. Just specify the points you want to pass through, add them to a list and that's it.
In this example I want to pass through some points on the plot, then "lift the pen off of the paper" and continue from another point. So we create two lists - one containing the points I want to use and the second list which describes what I want to do with those points. Path.MOVETO will move your "pen" to the given point without drawing a line, so we use this to set our initial startpoint. Path.LINETO creates a straight line starting from your current pen position towards the next line in the list.
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
# Points you want to "pass through"
pts = [
(0, 0),
(0.2, 0.2),
(0.4, 0.2),
(0.4, 0.4),
(0.4, 0.6),
(0.6, 0.6),
(0.8, 0.8)
]
# What you want to "do" with each point
codes = [
Path.MOVETO, # inital point
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.MOVETO, # pick up the pen
Path.LINETO,
Path.LINETO
]
# Create path object
# https://matplotlib.org/stable/tutorials/advanced/path_tutorial.html
path = Path(pts, codes)
patch = patches.PathPatch(path, lw='2', color='r', fill=False)
# patch the path to the figure
fig, ax = plt.subplots()
ax.add_patch(patch)
plt.show()
Result of code execution:

Change Colorbar limit for changing scale with matplotlib 3.3+

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

plt.add_patch causes an error, how do I add a rectangle over a set of points?

All of my attempts failed. I tried to draw rectangle over set of points with pyplot but I keep getting different errors. Can someone help? I need to add rectangle of size width = 4 and height= 2sqrt(3)
import matplotlib.pyplot as plt
import matplotlib.patches as patches
def main():
print("hello")
if __name__ == "__main__":
x = []
y = []
for k in range(30):
for l in range(30):
x.append(4*k + 2*(l % 2))
y.append(2*l*3**(1/2))
rect = patches.Rectangle((0,0),4,2*3**(1/2),linewidth=1,edgecolor='b',facecolor='none')
plt.plot(x, y, 'ro')
plt.axis([0, 10, 0, 10])
#plt.add_patch(rect)
plt.show()
add_patch is an axes method, not something directly under pyplot. Just change your commented line to:
plt.gca().add_patch(rect)
gca() gets the current active axes in pyplot.

Matplotlib save only text without whitespace

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:

Categories