如何将AxesImage中的坐标映射到已保存的图像文件中的坐标? [英] How to map coordinates in AxesImage to coordinates in saved image file?

查看:26
本文介绍了如何将AxesImage中的坐标映射到已保存的图像文件中的坐标?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I use matplotlib to display a matrix of numbers as an image, attach labels along the axes, and save the plot to a PNG file. For the purpose of creating an HTML image map, I need to know the pixel coordinates in the PNG file for a region in the image being displayed by imshow.

我找到了一个例子来说明如何做到这一点使用常规图,但是当我尝试使用imshow进行相同操作时,映射不正确.这是我的代码,它保存图像并尝试打印对角线上每个正方形中心的像素坐标:

I have found an example of how to do this with a regular plot, but when I try to do the same with imshow, the mapping is not correct. Here is my code, which saves an image and attempts to print the pixel coordinates of the center of each square on the diagonal:

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,显示为屏幕截图以包含标尺:

Here is the resulting foo.png, shown as a screenshot in order to include the rulers:

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

The output of the script starts and ends as follows:

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像素点.而不是预期的14.我在做什么错了?

As you see, the y-coordinates are correct, but the x-coordinates are stretched: they range from 73 to 585 instead of the expected 135 to 506, and they are spaced 19 pixels o.c. instead of the expected 14. What am I doing wrong?

推荐答案

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

This is one of the more confusing parts of trying to get exact pixel values from matplotlib. Matplotlib separates the renderer that handles exact pixel values from the canvas that the figure and axes are drawn on.

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

Basically, the renderer that exists when the figure is initially created (but not yet displayed) is not necessarily the same as the renderer that is used when displaying the figure or saving it to a file.

您所做的是正确的,但是它使用的是初始渲染器,而不是保存图形时使用的渲染器.

What you're doing is correct, but it's using the initial renderer, not the one that's used when the figure is saved.

为了说明这一点,这里有一个稍微简化的代码版本:

To illustrate this, here's a slightly simplified version of your code:

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)

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

This yields similar results to what you have above: (the differences are due to different rendering backends between your machine and mine)

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

但是,如果我们做的完全相同,但是在显示坐标之前绘制图形,我们将得到正确的答案!

However, if we do the exact same thing, but instead draw the figure before displaying the coordinates, we get the correct answer!

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>code>.(即绘制图像的坐标以像素为中心)这就是为什么 <0, 0> 产生 143, 55 而不是 135,48 )

This yields: (Keep in mind that the edge of the figure is at <-0.5, -0.5> in data coordinates, not <0, 0>. (i.e. the coordinates for the plotted image are pixel-centered) This is why <0, 0> yields 143, 55, and not 135, 48)

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

当然,仅在保存时再次绘制图形是多余的,而且计算量很大.

Of course, drawing the figure just to draw it again when it's saved is redundant and computationally expensive.

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

To avoid drawing things twice, you can connect a callback function to the draw event, and output your HTML image map inside this function. As a quick example:

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)

保存的图形只绘制一次,从而产生正确的输出:

Which yields the correct output, while only drawing the figure once, when it is saved:

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,这就是为什么您必须首先指定与 fig.dpi 相同的 dpi)

Another advantage is that you can use any dpi in the call to fig.savefig without having to manually set the fig object's dpi beforehand. Therefore, when using the callback function, you can just do fig.savefig('foo.png'), (or fig.savefig('foo.png', dpi=whatever)) and you'll get output that matches the saved .png file. (The default dpi when saving a figure is 100, while the default dpi for a figure object is 80, which is why you had to specify the dpi to be the same as fig.dpi in the first place)

希望这至少是清楚的!

这篇关于如何将AxesImage中的坐标映射到已保存的图像文件中的坐标?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆