如何将AxesImage中的坐标映射到已保存图像文件中的坐标?

时间:2011-01-12 11:50:25

标签: python matplotlib

我使用matplotlib将数字矩阵显示为图像,沿轴附加标签,并将绘图保存为PNG文件。为了创建HTML图像映射,我需要知道PNG文件中的像素坐标,以显示imshow显示的图像中的区域。

我已经找到an example如何使用常规绘图执行此操作,但是当我尝试使用imshow执行相同操作时,映射不正确。这是我的代码,它保存图像并尝试打印对角线上每个正方形中心的像素坐标:

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
axim = ax.imshow(np.random.random((27,27)), interpolation='nearest')
for x, y in  axim.get_transform().transform(zip(range(28), range(28))):
    print int(x), int(fig.get_figheight() * fig.get_dpi() - y)
plt.savefig('foo.png', dpi=fig.get_dpi())

以下是生成的foo.png,显示为屏幕截图以包含标尺:

Screenshot of foo.png with rulers

脚本的输出开始和结束如下:

73 55
92 69
111 83
130 97
149 112
…
509 382
528 396
547 410
566 424
585 439

如你所见,y坐标是正确的,但x坐标是拉伸的:它们的范围是73到585而不是预期的135到506,并且它们间隔为19像素o.c.而不是预期的14.我做错了什么?

2 个答案:

答案 0 :(得分:12)

这是尝试从matplotlib获取精确像素值的更令人困惑的部分之一。 Matplotlib将绘制精确像素值的渲染器与绘制图形和轴的画布分开。

基本上,最初创建图形时(但尚未显示)时存在的渲染器不一定与显示图形或将其保存到文件时使用的渲染器相同。

你正在做的是正确的,但是它使用的是初始渲染器,而不是保存图形时使用的渲染器。

为了说明这一点,这里是您的代码的略微简化版本:

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)
im = ax.imshow(np.random.random((27,27)), interpolation='nearest')

for i in range(28):
    x, y =  ax.transData.transform_point([i,i])
    print '%i, %i' % (x, fig.bbox.height - y)

fig.savefig('foo.png', dpi=fig.dpi)

这会产生与上面相似的结果:(差异是由于您的机器和我的机器之间的渲染后端不同)

89, 55
107, 69
125, 83
...
548, 410
566, 424
585, 439

但是,如果我们做同样的事情,而是在显示坐标之前绘制图形,我们得到正确的答案!

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)
im = ax.imshow(np.random.random((27,27)), interpolation='nearest')

fig.canvas.draw()

for i in range(28):
    x, y =  ax.transData.transform_point([i,i])
    print '%i, %i' % (x, fig.bbox.height - y)

fig.savefig('foo.png', dpi=fig.dpi)

这会产生:(请记住,数字的边缘在数据坐标中为<-0.5, -0.5>,而不是<0, 0>。(即绘制图像的坐标以像素为中心)这就是为什么<0, 0>产生143, 55,而不是135, 48

143, 55
157, 69
171, 83
...
498, 410
512, 424
527, 439

当然,绘制图形只是为了在保存时再次绘制它是多余的并且计算成本很高。

为避免两次绘制,可以将回调函数连接到绘图事件,并在此函数中输出HTML图像映射。作为一个简单的例子:

import numpy as np
import matplotlib.pyplot as plt

def print_pixel_coords(event):
    fig = event.canvas.figure
    ax = fig.axes[0] # I'm assuming there's only one subplot here...
    for i in range(28):
        x, y = ax.transData.transform_point([i,i])
        print '%i, %i' % (x, fig.bbox.height - y)

fig = plt.figure()
ax = fig.add_subplot(111)
im = ax.imshow(np.random.random((27,27)), interpolation='nearest')

fig.canvas.mpl_connect('draw_event', print_pixel_coords)

fig.savefig('foo.png', dpi=fig.dpi)

产生正确的输出,而只保存一次,保存时:

143, 55
157, 69
171, 83
...
498, 410
512, 424
527, 439

另一个优点是您可以在fig.savefig的调用中使用任何dpi,而无需事先手动设置fig对象的dpi。因此,在使用回调函数时,您只需执行fig.savefig('foo.png'),(或fig.savefig('foo.png', dpi=whatever)),您将获得与保存的.png文件匹配的输出。 (保存图形时的默认dpi为100,而图形对象的默认dpi为80,这就是为什么必须首先指定dpi与fig.dpi相同)

希望这至少有点清楚!

答案 1 :(得分:1)

我不知道我是否理解你的要求,但是 如果我是,你需要图形左下角的像素坐标(尝试在图形的单位中为(-0.5,-0.5)), 从那里,您可以计算每个框的像素坐标,只需使用 盒子边的像素。

要获得这些,请执行:

x = ax.get_xaxis().get_clip_box().x0
y = ax.get_yaxis().get_clip_box().y1

(我得到了给定图像的值128,432)