使用 matplotlib.pyplot、imshow() 和 savefig() 以全分辨率绘图?

Posted

技术标签:

【中文标题】使用 matplotlib.pyplot、imshow() 和 savefig() 以全分辨率绘图?【英文标题】:Plotting at full resolution with matplotlib.pyplot, imshow() and savefig()? 【发布时间】:2016-01-15 10:27:19 【问题描述】:

我有一个中等大小的数组(例如 1500x3000),我想按比例绘制它,因为它是一个图像。然而,垂直和水平的尺度是非常不同的。为简化起见,假设有 1 米/行和 10/列。然后,该图应生成一个图像,即 c。 1500x30000。我将 kwarg 范围用于尺度和 aspect = 1 以避免变形。 无论是使用绘图窗口 (QT4) 和 imshow() 还是使用 savefig(),我都无法成功地以全分辨率和比例生成图像。

我查看了许多建议的解决方案,如 here、here 或 here 和 there 或 there 中所示,以防这是一个错误。我已经更改了我的 matplotlibrc 并将其放在 ~/.config/matplotlib 中以尝试强制我的显示 / savefig 选项,但无济于事。我也尝试过 pcolormesh() 但没有成功。我使用来自 Ubuntu 14.04 和 QT4Agg 的 repo 的 python 2.7 和 matplotlib 1.3 作为后端。我也尝试过 TkAgg,但它很慢并且给出了相同的结果。我的印象是,在 x 轴上,分辨率是正确的,但在垂直方向上肯定是下采样的。这是一段应该模拟我的问题的代码。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors

R, C = 1500, 3000
DATA = np.random.random((R, C))
DATA[::2, :] *= -1  # make every other line negative
Yi, Xi = 1, 10 # increment
CMP = 'seismic'
ImageFormat ='pdf'
Name = 'Image'


DataRange = (np.absolute(DATA)).max() # I want my data centred on 0
EXTENT = [0, Xi*C, 0 ,Yi*R]
NORM = matplotlib.colors.Normalize(vmin =-DataRange, vmax= DataRange, clip =True)

for i in range(1,4):
    Fig=plt.figure(figsize=(45, 10), dpi = 100*i, tight_layout=True)
    Fig.suptitle(Name+str(i)+'00DPI')
    ax = Fig.add_subplot(1, 1, 1)
    Plot = ax.imshow(DATA, cmap=plt.get_cmap(CMP), norm = NORM, extent = EXTENT, aspect = 1, interpolation='none') 
    ax.set_xlabel('metres')
    ax.set_ylabel('metres')
    Fig.savefig(Name+str(i)+'00DPI.'+ImageFormat,  format = ImageFormat, dpi = Fig.dpi)
plt.close()

在 imshow() 中,interpolation = 'none' or 'nearest' or 'bilinear' 不会因为某种原因改变分辨率,尽管我认为如果我使用 show() 而不是至少在 Qt4 窗口中应该这样做保存图()。 请注意,无论您在 plt.figure(dpi=) 中设置什么,保存的图形中的分辨率都是相同的。

我一头雾水,对这个系统如何工作的理解也有限。非常欢迎任何帮助。

提前致谢。

【问题讨论】:

是否可以选择另存为 SVG? plt.savefig("test.svg") 我没有注意到在垂直分辨率方面保存为 svg 的改进。 我修改了代码,使图像垂直交替正值和负值。主要思想是,如果图像被完全解析,我们应该能够区分蓝色和红色水平条纹 您是否考虑过这可能是 .pdf 查看器的问题?当我运行您的示例时,使用 okular 打开它并放大,我看到了条纹。当我缩小时,它们仍然在那里。只有当 okular 决定对图像进行下采样以释放一些内存时,条纹才会消失。 【参考方案1】:

运行您的示例,缩放后 matplotlib 中的一切看起来都很好:无论分辨率如何,结果都是相同的,我看到每个轴单位有一个像素。 此外,尝试使用较小的数组,pdf(或其他格式)也能很好地工作。

这是我的解释:当你设置图形dpi时,你是在设置整个图形的dpi(不仅仅是数据区)。在我的系统上,这导致绘图区域垂直占据整个图形的 20%。如果设置 300 dpi 和 10 高度,则垂直数据轴的总像素为 300x10x0.2=600 像素,不足以表示 1500 个点,这向我解释了为什么必须重新采样输出。注意减小宽度有时会偶然起作用,因为它会改变数据图所占图形的比例。

然后你必须增加 dpi 并设置 interpolation='none' (分辨率是否完美设置无关紧要,但它是否足够接近很重要)。 您也可以调整绘图位置和大小以占据图形的较大部分,但回到最佳分辨率设置,理想情况下,您希望轴上的像素数是数据点的倍数,否则一些必须进行某种插值(想想如何在三个像素上绘制两个点,反之亦然)。

我不知道以下是否是最好的方法,matplotlib中可能有更合适的方法和属性,但我会尝试这样的方法来计算最佳dpi:

vsize=ax.get_position().size[1]  #fraction of figure occupied by axes
axesdpi= int((Fig.get_size_inches()[1]*vsize)/R)  #(or Yi*R according to what you want to do)

然后您的代码(简化为第一个循环)变为:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors

R, C = 1500, 3000
DATA = np.random.random((R, C))
DATA[::2, :] *= -1  # make every other line negative
Yi, Xi = 1, 10 # increment
CMP = 'seismic'
ImageFormat ='pdf'
Name = 'Image'


DataRange = (np.absolute(DATA)).max() # I want my data centred on 0
EXTENT = [0, Xi*C, 0 ,Yi*R]
NORM = matplotlib.colors.Normalize(vmin =-DataRange, vmax= DataRange, clip =True)

for i in (1,):
    print i 
    Fig=plt.figure(figsize=(45, 10), dpi = 100*i, tight_layout=True)
    Fig.suptitle(Name+str(i)+'00DPI')
    ax = Fig.add_subplot(1, 1, 1)
    Plot = ax.imshow(DATA, cmap=plt.get_cmap(CMP), norm = NORM, extent = EXTENT, aspect = 1, interpolation='none') 
    ax.set_xlabel('metres')
    ax.set_ylabel('metres')
    vsize=ax.get_position().size[1]  #fraction of figure occupied by axes
    axesdpi= int((Fig.get_size_inches()[1]*vsize)/R)  #(or Yi*R according to what you want to do)
    Fig.savefig(Name+str(axesdpi)+'DPI.'+ImageFormat,  format = ImageFormat, dpi = axesdpi)
    #plt.close()

这对我来说是合理的。

【讨论】:

【参考方案2】:

首先,当您保存为.pdf 时,您会隐式使用 pdf 后端,即使您可能在选项中指定了其他后端。这意味着您的图像以矢量格式保存,因此 dpi 毫无意义。在任何分辨率下,如果我在一个像样的查看器中加载你的 PDF(我使用了 inkscape,其他的可用),你可以清楚地看到条纹 - 我实际上发现如果你将每隔一行设置为零更容易观察。生成的所有 PDF 都包含完整的信息以再现条纹,因此几乎相同。当您指定 figsize=(45, 10) 时,所有生成的 PDF 都建议显示尺寸为 45 英寸 x 10 英寸。

如果我将png 指定为图像类型,我会根据dpi 参数看到文件大小的差异,我认为这正是您所期望的。如果你看 100 dpi 的图像,它有 4500000,200 dpi 的图像有 18000000 像素(4 倍),300 dpi 的图像有 40500000(9 倍)。您会注意到 4500000 == 1500 x 3000 即原始数组的每个成员一个像素。因此,更大的 dpi 设置并不能真正为您提供任何进一步的定义 - 相反,您的条纹分别是 2 或 3 像素宽,而不是 1。

认为您想要做的是有效地绘制每一列 10 次,因此您将获得 1500 x 30000 像素的图像。为此,使用您自己的所有代码,您可以使用np.repeat 执行以下操作:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors

R, C = 1500, 3000
DATA = np.random.random((R, C))
DATA[::2, :] = 0  # make every other line plain white
Yi, Xi = 1, 10 # increment
DATA = np.repeat(DATA, Xi, axis=1)
DATA = np.repeat(DATA, Yi)

CMP = 'seismic'
ImageFormat ='pdf'
Name = 'Image'


DataRange = (np.absolute(DATA)).max() # I want my data centred on 0
EXTENT = [0, Xi*C, 0 ,Yi*R]
NORM = matplotlib.colors.Normalize(vmin =-DataRange, vmax= DataRange, clip =True)

for i in range(1,4):
    Fig=plt.figure(figsize=(45, 10), dpi = 100*i, tight_layout=True)
    Fig.suptitle(Name+str(i)+'00DPI')
    ax = Fig.add_subplot(1, 1, 1)
    Plot = ax.imshow(DATA, cmap=plt.get_cmap(CMP), norm = NORM, extent = EXTENT, aspect = 1, interpolation='none') 
    ax.set_xlabel('metres')
    ax.set_ylabel('metres')
    Fig.savefig(Name+str(i)+'00DPI.'+ImageFormat,  format = ImageFormat, dpi = Fig.dpi)
plt.close()

警告:这是一个内存密集型解决方案 - 可能有更好的方法。如果不需要pdf的矢量图形输出,可以将ImageFormat变量改为png


令我震惊的是,您可能关心的另一件事是为图片提供适当的纵横比(即宽是高的 20 倍)。这是你已经在做的。因此,如果您查看pdf 中像素的每个表示,它们是矩形的(宽度是高度的 10 倍),而不是正方形。

【讨论】:

澄清一下,增量是以米为单位的。数据在垂直方向每米采样一次,水平方向每 10 米采样一次。所以我真的不想重复这些值,我更喜欢插值(如中值滤波器)。但是我知道我目前的代码不是为此而设计的(为简单起见,我删除了该部分)。感谢您的解释,我不知道 np.repeat 功能。我也会使用光栅格式,它更有意义。只是我更喜欢在 pdf 中呈现字体/轴,然后我可以在 Inkscape 中编辑它们以进行发布。

以上是关于使用 matplotlib.pyplot、imshow() 和 savefig() 以全分辨率绘图?的主要内容,如果未能解决你的问题,请参考以下文章

使用 matplotlib.pyplot 更新条形图

matplotlib.pyplot 导引

服务器上使用matplotlib.pyplot绘图

如何使用 matplotlib.pyplot 更改表格的字体大小

matplotlib.pyplot的使用

使用 OpenCV 或 Matplotlib/Pyplot 可视化 MNIST 数据集