如何获得以像素为单位的固定高度、固定数据 x/y 纵横比并自动删除 Matplotlib 中的删除水平空白边距?
Posted
技术标签:
【中文标题】如何获得以像素为单位的固定高度、固定数据 x/y 纵横比并自动删除 Matplotlib 中的删除水平空白边距?【英文标题】:How to obtain a fixed height in pixels, fixed data x/y aspect ratio and automatically remove remove horizontal whitespace margin in Matplotlib? 【发布时间】:2021-02-14 22:49:28 【问题描述】:让我们以此为起点:Specifying and saving a figure with exact size in pixels
#!/usr/bin/env python3
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
h = int(sys.argv[1])
fig, ax = plt.subplots(nrows=2, ncols=1)
t = np.arange(-10., 10., 1.)
a = ax[0]
a.set_aspect(1)
a.plot(t, t, '.')
a = ax[1]
a.plot(t, -t, '.')
a.set_aspect(1)
plt.savefig(
'main.png',
format='png',
dpi=h/fig.get_size_inches()[1],
facecolor='y',
)
这让我可以这样做:
./main.py 400 && identify main.png
生成正确的所需高度为 400 像素的图像:
main.png PNG 533x400 533x400+0+0 8-bit sRGB 6058B 0.000u 0:00.000
但是,地块的左右两侧有很多空白区域。这个空间是由于我也想要的1
的固定纵横比(x 和 y 数据具有相同的大小)。我们可以通过删除set_aspect
调用来确认这一点,这给出了一个具有合理大小边距的数字:
但我也想要 1/1 的纵横比。
我尝试使用以下几种方法删除此空间:Removing white space around a saved image in matplotlib,但没有一个给出我想要的。
例如,如果我添加:
plt.savefig(bbox_inches='tight',
我得到了想要的图像:
但高度不再是我想要的 400:
main.png PNG 189x345 189x345+0+0 8-bit sRGB 4792B 0.000u 0:00.000
或者如果我尝试添加:
plt.tight_layout(pad=0)
高度是正确的,但它没有删除水平空间:
我可以做的一件事是明确设置宽度,如下所示:
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
h = int(sys.argv[1])
w = int(sys.argv[2])
fig, ax = plt.subplots(nrows=2, ncols=1)
wi, hi = fig.get_size_inches()
fig.set_size_inches(hi*(w/h), hi)
t = np.arange(-10., 10., 1.)
a = ax[0]
a.set_aspect(1)
a.plot(t, t, '.')
a = ax[1]
a.plot(t, -t, '.')
a.set_aspect(1)
plt.tight_layout(pad=1)
plt.savefig(
'main.png',
format='png',
dpi=h/hi,
facecolor='y',
)
然后运行它:
./main.py 400 250 && identify main.png
其中 250 是通过反复试验选择的,它确实提供了准确的像素尺寸和好看的输出:
但我宁愿不必通过反复试验来找到值 250,我希望它由 matplotlib 自动确定。
这可能是matplotlib: Set width or height of figure without changing aspect ratio 所要求的,但如果没有具体的例子就很难确定。
在 matplotlib==3.2.2 上测试。
【问题讨论】:
【参考方案1】:我不知道我是否理解了您的问题,但如果您想使用 1x2 子图布局限制图形中的空白,您只需创建一个宽度为高度一半的图形:
h = 400
nrows = 2
w = h/nrows
dpi = 100
fig, ax = plt.subplots(nrows=nrows, ncols=1, figsize=(w/dpi, h/dpi), dpi=dpi)
t = np.arange(-10., 10., 1.)
a = ax[0]
a.set_aspect(1)
a.plot(t, t, '.')
a = ax[1]
a.plot(t, -t, '.')
a.set_aspect(1)
plt.tight_layout(pad=1)
plt.savefig(
'main.png',
format='png',
dpi=dpi,
facecolor='y',
)
>> identify main.png
main.png PNG 200x400 200x400+0+0 8-bit sRGB 6048B 0.000u 0:00.000
【讨论】:
好的,我应该对此进行更多测试。该解决方案是合理的。但是不得不自己进行 nrow/ncol 操作有点烦人。如果添加了其他垂直的非绘图区域元素,空间也会返回,例如a.set_title('A', fontdict=dict(fontsize='75'))
。虽然在实践中这可能并不常见(字体大小被夸大了)。
我现在还注意到,在更一般的情况下也需要考虑纵横比(此答案适用于 a.set_aspect(1)
的特定情况)。【参考方案2】:
SVG 输出 + plt.savefig(bbox_inches='tight'
+ Inkscape 转换
这很糟糕,但它可以满足我的要求,无需大量额外的样板,所以我们开始吧:
#!/usr/bin/env python3
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
h = int(sys.argv[1])
fig, ax = plt.subplots(nrows=2, ncols=1)
t = np.arange(-10., 10., 1.)
a = ax[0]
a.set_aspect(1)
a.plot(t, t, '.')
a = ax[1]
a.plot(t, -t, '.')
a.set_aspect(1)
plt.savefig(
'main.svg',
format='svg',
dpi=h/fig.get_size_inches()[1],
facecolor='y',
bbox_inches='tight',
)
然后:
inkscape -b FFF -e main.png -h 400 main.svg
输出:
bbox_inches='tight'
提供了一个没有太多边框的不错的图像,但让我失去了确切的尺寸,所以我们使用 SVG 输出来解决这个问题。
这应该很好用,因为 SVG 是一种矢量格式,因此应该可以无缝缩放到任何大小。
我使用 Inkscape 进行转换,因为 Imagemagick 需要您手动计算分辨率:
https://superuser.com/questions/598849/imagemagick-convert-how-to-produce-sharp-resized-png-files-from-svg-files/1602059#1602059 How to convert a SVG to a PNG with ImageMagick?在 Inkscape 0.92.5 上测试。
【讨论】:
以上是关于如何获得以像素为单位的固定高度、固定数据 x/y 纵横比并自动删除 Matplotlib 中的删除水平空白边距?的主要内容,如果未能解决你的问题,请参考以下文章
OpenGL如何使用固定管线下的着色器渲染一个正方形,并用特殊键控制移动