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

bxjv4tth  于 2023-10-24  发布在  其他
关注(0)|答案(3)|浏览(132)

我有一个中等大小的数组(例如1500 x3000),我想按比例绘制,因为它是一个图像。然而,垂直和水平比例非常不同。为了简化,假设有一米/行和10/列。然后绘图应产生一个约为1500 x30000的图像。我使用kwarg范围作为比例和方面=无论是使用绘图窗口(QT 4)和imshow()还是使用savefig(),我都没有成功地以全分辨率和比例生成图像。
我已经研究了许多建议的解决方案,如herehere,或者heretherethere,以防它是一个bug。我已经改变了我的matplotlibrc,并将其放置在~/.config/matplotlib中,试图强制我的显示/保存选项,但无济于事。我也尝试了pcolormesh()但没有成功。我使用Python 2.7和matplotlib 1.3从Ubuntu 14.04的repo和QT 4Agg作为后端。我也尝试了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()中,插值= 'none'或'nearest'或'bilinear'不会因为某种原因改变分辨率,尽管我认为如果我使用show()而不是savefig(),它至少应该在Qt 4窗口中改变分辨率。请注意,无论您在plt.figure(dpi=)中设置什么,保存的图中的分辨率都是相同的。
我对这个系统的工作原理一无所知,也没有什么了解。任何帮助都是非常欢迎的。
先谢了。

ylamdve6

ylamdve61#

运行你的例子,在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()

这对我来说是合理的。

zzzyeukh

zzzyeukh2#

首先,当你保存为.pdf时,你隐含地使用了pdf后端,即使你可能在选项中指定了其他后端。这意味着你的图像是以矢量格式保存的,因此dpi是毫无意义的。在任何分辨率下,如果我在一个像样的浏览器中加载你的PDF,(我用的是inkscape,其他的都有),你可以清楚地看到条纹实际上,我发现如果将每隔一行设置为零,观察起来会更容易。当您指定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倍),而不是正方形。

pprl5pva

pprl5pva3#

如果你喜欢它作为一个功能:
这段代码是基于Jumenzooo的解决方案,但数学是为Python 3和matplotlib 3.7.2更新的。

import math
import os
import matplotlib.pyplot as plt
import numpy as np

def calculate_dpi_needed_for_image(n_rows: int, n_columns: int, fig, ax, min_dpi: int=300, dots_per_entry=2):
    ax_width_inches = fig.get_size_inches()[0] * ax.get_position().size[0] # figsize * fraction of figure occupied by axes
    dpi_w = dots_per_entry * n_columns / ax_width_inches # dpi * inches >= n_columns
    ax_height_inches = fig.get_size_inches()[1] * ax.get_position().size[1] # figsize * fraction of figure occupied by axes
    dpi_h = dots_per_entry * n_rows / ax_height_inches # dpi * inches >= n_rows
    dpi = max(dpi_h, dpi_w)
    if min_dpi is not None:
        if dpi < min_dpi: # Ensure DPI is at least 300 for legibility.
            dpi = dpi * math.ceil(300 / dpi)
    return dpi

# Create a sample dataset.
# data = np.random.rand(10, 20)
data = np.random.rand(1280, 1920)
data[::2, :] = 1.0 # make every other row equal one
data[:, ::2] = 1.0 # make every other column equal one

# Plot the data.
fig = plt.figure(figsize=(10, 10))
ax = plt.subplot(1, 1, 1)
ax.imshow(data, cmap='hot', interpolation='none')
dpi = calculate_dpi_needed_for_image(data.shape[0], data.shape[1], fig, ax, dots_per_entry=2, min_dpi=None)
print(f'Using DPI: {dpi}')
path = os.path.join(os.path.dirname(__file__), 'test_dpi_util.png')
plt.savefig(path, dpi=dpi)

相关问题