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

时间:2015-10-16 15:36:00

标签: python matplotlib plot imshow

我有一个中等大小的阵列(例如1500x3000)我想要大规模绘制,因为它是一个图像。但是,垂直和水平刻度是非常不同的。为简化起见,假设有一米/行和10 /列。然后该图应该产生一个c的图像。 1500x30000。我使用kwarg范围为scale和aspect = 1以避免变形。 通过使用绘图窗口(QT4)和imshow()或使用savefig(),我从未成功地以大规模和全分辨率生成图像。

我已经查看了hereherehere以及therethere中所示的许多建议解决方案,以防它出现错误。我已经改变了我的matplotlibrc并将其放在〜/ .config / matplotlib中以尝试强制我的display / 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'或'nearest'或'bilinear'由于某种原因不会改变分辨率,虽然我认为至少在Qt4窗口中如果我做show()而不是savefig()。 请注意,无论您在plt.figure(dpi =)中设置了什么,保存的数字中的分辨率都是相同的。

我没有想法,并且在我对这个系统的工作原理的理解极限。非常欢迎任何帮助。

提前致谢。

2 个答案:

答案 0 :(得分:1)

首先,当您作为.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倍),而不是正方形。

答案 1 :(得分:1)

运行你的例子,在缩放后matplotlib中的一切看起来都很好:无论分辨率如何,结果都是一样的,我看到每个轴单位有一个像素。 此外,尝试使用较小的数组,pdfs(或其他格式)也可以正常工作。

这是我的解释:当您设置图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()

这对我来说是合理的。