2015-10-16 2152 views
13

我有一个中等大小的数组(例如1500x3000),因为它是一个图像,所以我想要按比例绘制。但是,纵向和横向尺度是非常不同的。为了简化,假设有一米/行和10 /列。该图应该产生一个图像c。 1500x30000。我使用kwarg的范围和aspect = 1来避免变形。通过使用绘图窗口(QT4)和imshow()或使用savefig(),我从未成功地按比例和全分辨率生成图像。使用matplotlib.pyplot,imshow()和savefig()以完整分辨率绘图?

我看过很多提出的解决方案,如情况herehere,或heretherethere表示,这是一个错误。我已经改变了我的matplotlibrc,并将其放在〜/ .config/matplotlib中,试图强制显示/ savefig选项,但无济于事。我也尝试了pcolormesh()但没有成功。我使用Ubuntu 14.04和QT4Agg的回购库中的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(),插值=“无”或“最近”或“双线”不出于某种原因更改分辨率,虽然我认为它应该至少在Qt4的窗口,如果我做节目()而不是savefig()。 请注意,无论您在plt.figure(dpi =)中设置了什么,分辨率都与保存的数字相同。

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

在此先感谢。

+1

是节约作为SVG的选择吗? 'plt.savefig(“test.svg”)' – Eric

+0

我没有注意到在垂直分辨率方面节省了svg。 – Boorhin

+0

我修改了代码,以便图像垂直地交替正值和负值。主要想法是,如果图像全部解析,我们应该能够区分蓝色和红色的横条纹 – Boorhin

回答

1

运行你的榜样,一切看起来变焦后良matplotlib:没有问题的解决,结果都是一样的,我看到每轴单位的一个像素。 另外,尝试使用更小的数组,pdf(或其他格式)可以很好地工作。

这是我的解释:当你设置DPI数字,您是在设置整个人物(不仅是数据区)的DPI。在我的系统中,这导致绘图区域占据整个图形的大约20%。如果设置300 dpi和10的高度,则垂直数据轴的总数为300x10x0.2 = 600像素,这不足以表示1500点,这说明为什么输出必须重新采样。请注意,减小宽度有时会偶然发挥作用,因为它会更改数据图占用的图形的分数。

然后你不得不增加DPI并设置插值=“无”(它不应该的问题,如果分辨率设定完美,但它很重要,如果它足够正好接近)。 您也可以调整地块位置和大小,取图的较大部分,但要回最佳分辨率设置,最好要拥有数轴上像素的就是你的数据点的整数倍,否则某种内插必须发生(想想如何在三个像素上绘制两个点,反之亦然)。

我不知道下面是做到这一点的最好办法,有可能是在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() 

这工作得相当适合我。

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倍),而不是方形。

+0

只是为了澄清,增量是以米为单位的比例。数据每米垂直和水平每10米采样一次。所以我不想重复这些值,我更喜欢插值(如中值滤波器)。但是我明白我的代码目前并不是为此设计的(为了简单起见,我删除了该部分)。感谢您的解释,我不知道np.repeat函数。我也将使用光栅格式,这是更有意义的。只是我喜欢用pdf格式渲染字体/轴,然后我可以在Inkscape中编辑它们以便发布。 – Boorhin