Reputation: 1631
I would like to get the bounding box (dimensions) around some text in a matplotlib figure. The post here, helped me realize that I can use the method text.get_window_extent(renderer)
to get the bounding box, but I have to supply the correct renderer. Some backends do not have the method figure.canvas.get_renderer()
, so I tried matplotlib.backend_bases.RendererBase()
to get the renderer and it did not produce satisfactory results. Here is a simple example
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
fig = plt.figure()
ax = plt.subplot()
txt = fig.text(0.15,0.5,'afdjsklhvvhwd', fontsize = 36)
renderer1 = fig.canvas.get_renderer()
renderer2 = mpl.backend_bases.RendererBase()
bbox1 = txt.get_window_extent(renderer1)
bbox2 = txt.get_window_extent(renderer2)
rect1 = Rectangle([bbox1.x0, bbox1.y0], bbox1.width, bbox1.height, \
color = [0,0,0], fill = False)
rect2 = Rectangle([bbox2.x0, bbox2.y0], bbox2.width, bbox2.height, \
color = [1,0,0], fill = False)
fig.patches.append(rect1)
fig.patches.append(rect2)
plt.draw()
This produces the following plot:
Clearly the red box is too small. I think a Paul's answer here found the same issue. The black box looks great, but I cannot use the MacOSX backend, or any others that do not have the method figure.canvas.get_renderer()
.
In case it matters, I am on Mac OS X 10.8.5, Matplotlib 1.3.0, and Python 2.7.5
Upvotes: 14
Views: 6316
Reputation: 13
The _get_renderer()
method from the Figure
object gives me satisfactory results:
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
fig1, ax1 = plt.subplots()
plotted_text = ax1.text(0.5, 0.5, "afdjsklhvvhwd")
renderer1 = fig1.canvas.get_renderer()
bb1 = plotted_text.get_window_extent(renderer=renderer1).transformed(ax1.transData.inverted())
text_width1 = bb1.width
fig2 = Figure()
ax2 = fig2.subplots()
plotted_text2 = ax2.text(0.5, 0.5, "afdjsklhvvhwd")
renderer2 = fig2._get_renderer()
bb2 = plotted_text2.get_window_extent(renderer=renderer2).transformed(ax2.transData.inverted())
text_width2 = bb2.width
Upvotes: 0
Reputation: 375
If you would like to get the tight bounding box of a rotated text region, here is a possible solution.
# generate text layer
def text_on_canvas(text, myf, ro, margin = 1):
axis_lim = 1
fig = plt.figure(figsize = (5,5), dpi=100)
plt.axis([0, axis_lim, 0, axis_lim])
# place the left bottom corner at (axis_lim/20,axis_lim/20) to avoid clip during rotation
aa = plt.text(axis_lim/20.,axis_lim/20., text, ha='left', va = 'top', fontproperties = myf, rotation = ro, wrap=True)
plt.axis('off')
text_layer = fig2img(fig) # convert to image
plt.close()
we = aa.get_window_extent()
min_x, min_y, max_x, max_y = we.xmin, 500 - we.ymax, we.xmax, 500 - we.ymin
box = (min_x-margin, min_y-margin, max_x+margin, max_y+margin)
# return coordinates to further calculate the bbox of rotated text
return text_layer, min_x, min_y, max_x, max_y
def geneText(text, font_family, font_size, style):
myf = font_manager.FontProperties(fname=font_family, size=font_size)
ro = 0
if style < 8: # rotated text
# no rotation, just to get the minimum bbox
htext_layer, min_x, min_y, max_x, max_y = text_on_canvas(text, myf, 0)
# actual rotated text
ro = random.randint(0, 90)
M = cv2.getRotationMatrix2D((min_x,min_y),ro,1)
# pts is 4x3 matrix
pts = np.array([[min_x, min_y, 1],[max_x, min_y, 1],[max_x, max_y, 1],[min_x, max_y,1]]) # clockwise
affine_pts = np.dot(M, pts.T).T
#print affine_pts
text_layer, _, _, _, _ = text_on_canvas(text, myf, ro)
visualize_points(htext_layer, pts)
visualize_points(text_layer, affine_pts)
return text_layer
else:
raise NotImplementedError
fonts = glob.glob(fonts_path + '/*.ttf')
ret = geneText('aaaaaa', fonts[0], 80, 1)
The result looks like this: The first one is un-rotated, and the second one is rotated text region. The full code snippet is here.
Upvotes: 1
Reputation: 1631
Here is my solution/hack. @tcaswell suggested I look at how matplotlib handles saving figures with tight bounding boxes. I found the code for backend_bases.py on Github, where it saves the figure to a temporary file object simply in order to get the renderer from the cache. I turned this trick into a little function that uses the built-in method get_renderer()
if it exists in the backend, but uses the save method otherwise.
def find_renderer(fig):
if hasattr(fig.canvas, "get_renderer"):
#Some backends, such as TkAgg, have the get_renderer method, which
#makes this easy.
renderer = fig.canvas.get_renderer()
else:
#Other backends do not have the get_renderer method, so we have a work
#around to find the renderer. Print the figure to a temporary file
#object, and then grab the renderer that was used.
#(I stole this trick from the matplotlib backend_bases.py
#print_figure() method.)
import io
fig.canvas.print_pdf(io.BytesIO())
renderer = fig._cachedRenderer
return(renderer)
Here are the results using find_renderer()
with a slightly modified version of the code in my original example. With the TkAgg backend, which has the get_renderer()
method, I get:
With the MacOSX backend, which does not have the get_renderer()
method, I get:
Obviously, the bounding box using MacOSX backend is not perfect, but it is much better than the red box in my original question.
Upvotes: 11