这是尝试从 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>
yields 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
另一个优点是您可以在调用中使用任何 dpifig.savefig
无需手动设置fig
预先指定对象的 dpi。因此,在使用回调函数时,你可以这样做fig.savefig('foo.png')
, (or fig.savefig('foo.png', dpi=whatever)
),您将获得与保存的 .png 文件匹配的输出。 (保存图形时的默认 dpi 为 100,而图形对象的默认 dpi 为 80,这就是为什么您必须将 dpi 指定为与fig.dpi
首先)
希望这至少有点清楚!