【问题标题】:Get text bounding box, independent of backend获取文本边界框,独立于后端
【发布时间】:2014-03-26 16:24:08
【问题描述】:

我想在 matplotlib 图中获取一些文本周围的边界框(尺寸)。帖子here 帮助我意识到我可以使用方法text.get_window_extent(renderer) 来获取边界框,但我必须提供正确的渲染器。有些后端没有figure.canvas.get_renderer()的方法,所以我尝试matplotlib.backend_bases.RendererBase()获取渲染器并没有产生令人满意的结果。这是一个简单的例子

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

这会产生以下情节:

显然红色框太小了。我认为保罗的回答here 发现了同样的问题。黑匣子看起来很棒,但我不能使用 MacOSX 后端,或者任何其他没有方法figure.canvas.get_renderer()

如果重要的话,我使用的是 Mac OS X 10.8.5、Matplotlib 1.3.0 和 Python 2.7.5

【问题讨论】:

  • 问题是文本大小在渲染之前是未知的。 osx 后端问题是由于石英事件循环的工作方式,您只能在回调 (iirc) 中渲染。你需要边界框做什么?如果是画一个盒子,我想annotate会帮你做的。
  • 并确保所有渲染器上的 dpi 都是正确的(屏幕空间以像素为单位)。
  • 我正在使用边界框来帮助我确定文本框与其他内容的距离。我通常将文本放置在任意位置,获取它的边界框,然后将文本移动到它的最终位置,部分基于边界框的大小。至于dpi,我知道figure.canvas.get_renderer()的dpi怎么设置,但是mpl.backend_bases.RendererBase()的dpi怎么设置呢?
  • 我怀疑你可以通过巧妙地使用 ha 和 va 来做很多你想做的事情。还可以看看紧密边界框代码是如何工作的。
  • 我认为水平对齐 (ha) 和/或垂直对齐 (va) 在许多情况下都不够用。假设我想让两行文本堆叠在一起。一条线很长,另一条线很短。我可以使用 ha 确保顶线的水平中心位于底线的水平中心的正上方。但是,如果我希望长线的左侧位于某个坐标处呢?我需要知道边界框的左边缘在哪里。感谢您对紧密边界框代码的建议。我去看看。

标签: python matplotlib


【解决方案1】:

这是我的解决方案/技巧。 @tcaswell 建议我看看 matplotlib 如何处理保存带有紧密边界框的图形。我在 Github 上找到了 backend_bases.py 的 the code,它只是将图形保存到一个临时文件对象中,以便从缓存中获取渲染器。我把这个技巧变成了一个小函数,如果它存在于后端,则使用内置方法get_renderer(),否则使用保存方法。

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)

以下是使用find_renderer() 的结果,对我的原始示例中的代码稍作修改。使用具有 get_renderer() 方法的 TkAgg 后端,我得到:

使用没有 get_renderer() 方法的 MacOSX 后端,我得到:

显然,使用 MacOSX 后端的边界框并不完美,但比我原来问题中的红色框要好得多。

【讨论】:

  • 它失败并出现错误 'AttributeError: 'FigureCanvasMac' object has no attribute 'print_pdf' on my MAC.
  • 我也遇到了没有print_pdf() 属性的问题,但更改为print_figure()(如该行上方的评论)对我有用。
【解决方案2】:

如果您想获得旋转文本区域的紧密边界框,这里有一个可能的解决方案。

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

结果如下所示:第一个是未旋转的,第二个是旋转的文本区域。完整的代码 sn -p 是here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-31
    • 1970-01-01
    • 2011-03-21
    相关资源
    最近更新 更多