指定 matplotlib 轴内容大小
specifying matplotlib axis content size
假设我正在尝试绘制一张 x*y
像素(或英寸)的黑洞图片。与许多其他人不同,我不想指定总图形大小,而是指定绘图内容的大小(因此框架中的所有内容,不包括框架本身、刻度、标签、颜色条、边距……)。
首选的方法是什么?
自己的尝试
我已经遇到过几次这个问题,但我现在更喜欢一些其他方法,而不是我之前的试错法猜测大小迭代...这是我的 "playground code":
import numpy as np
%matplotlib inline
import matplotlib
from matplotlib import pyplot as plt
import notebook
print(matplotlib.__version__, notebook.__version__)
# 3.0.0, 5.7.0 in my case
# some data
x, y = 456, 123
a = np.random.randn(y, x)
# the following at least gets the actual figure size in browser right,
# but if possible i'd like to avoid large white space margins as well...
# %config InlineBackend.print_figure_kwargs = {'bbox_inches': None}
dpi = plt.rcParams['figure.dpi']
fig, ax = plt.subplots(figsize=(x/dpi, y/dpi), dpi=dpi)
im = ax.imshow(a, interpolation='none')
cb = fig.colorbar(im)
# print sizes
bbox = ax.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
print(f"content size: ({bbox.width}, {bbox.height}) inch, ({bbox.width*fig.dpi}, {bbox.height*fig.dpi}) px")
print(f"fig size: {fig.get_size_inches()} in, {fig.dpi*fig.get_size_inches()} px")
print(f"dpi: {fig.dpi}")
输出:
content size: (4.023888888888889, 1.3870138888888888) inch, (289.72, 99.865) px
fig size: [6.33333333 1.70833333] in, [456. 123.] px
dpi: 72.0
如您所见,打印的图形尺寸为 456x123 像素,但如果您实际检查上传的图片(从浏览器复制粘贴),您会发现它只有 376x119 像素。虽然这可以修复(如代码中所述),但独立于此的实际 "content" 大小仍为 282x75 px :-/.
有什么想法吗?
不幸的是,没有首选的方法。
我想到了一些或多或少复杂的解决方法。
一个。使图和图一样大,边保存边展开
如果目的主要是生成图形的图像文件,最简单的方法可能是使图像绘图的轴与图形完全一样大,然后通过 bbox_inches="tight"
选项。
虽然需要手动在图外放置一个颜色条。
import numpy as np
import matplotlib.pyplot as plt
#create some image, with lines every second pixel
rows = 123
cols = 456
image = np.zeros((rows,cols))
image[:, np.arange(0,image.shape[1], 2)] = 1
image[np.arange(0,image.shape[0], 2), :] = 0.5
dpi = 100
fig, ax = plt.subplots(figsize=(image.shape[1]/dpi, image.shape[0]/dpi), dpi=dpi)
fig.subplots_adjust(0,0,1,1)
im = ax.imshow(image)
cax = fig.add_axes([1.05, 0, 0.03, 1])
fig.colorbar(im, cax=cax)
fig.savefig("test.png", bbox_inches="tight")
这样做的主要缺点是它可能导致图像有一个像素错误。这是由于位置总是在图形坐标中,导致在将轴大小标记为像素时出现舍入误差。
例如如果在上面选择 dpi=69
,结果将是
交错的线条很容易看出图片的高度太小了一个像素。
乙。使图形大于图像,调整边距
上面的一个缺点是轴装饰和颜色条在图的外面。要将它们放在里面,可以定义所有边距并计算最终数字需要多大。这是一个但是很麻烦的。
import numpy as np
import matplotlib.pyplot as plt
#create some image
rows = 123
cols = 456
image = np.zeros((rows,cols))
image[:, np.arange(0,image.shape[1], 2)] = 1
image[np.arange(0,image.shape[0], 2), :] = 0.5
dpi = 100
left = right = 60
top = bottom = 40
cbarwidth = 24
wspace = 10
width = left + cols + wspace + cbarwidth + right
height = top + rows + bottom
w = width / dpi
h = height / dpi
fig, (ax, cax) = plt.subplots(ncols = 2, figsize=(w,h), dpi=dpi,
gridspec_kw=dict(width_ratios=[cols, cbarwidth]))
fig.subplots_adjust(left = left/width, right = 1-right/width,
bottom = bottom/height, top = 1-top/height,
wspace = wspace / (cols + cbarwidth))
im = ax.imshow(image)
fig.colorbar(im, cax=cax)
fig.savefig("test2.png")
它也会遭受与 A. 相同的缺陷,例如如果使用一些奇数,例如
dpi = 72
left = right = 59
top = bottom = 37
cbarwidth = 19
wspace = 12
C。使用 figimage
并将轴放在顶部。
确保没有混叠效果的唯一方法是使用 figimage
。这会将像素坐标中的图像放入图中。但是,默认情况下将没有任何轴。 @anntzer proposed recently 提出了一个解决方案,即在图中 figimage
所在的位置放置一个轴。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
#create some image, with lines every second pixel
rows = 123
cols = 456
image = np.zeros((rows,cols))
image[:, np.arange(0,image.shape[1], 2)] = 1
image[np.arange(0,image.shape[0], 2), :] = 0.5
dpi = 100
left = right = 60
top = 40
bottom = 65
cbarwidth = 24
wspace = 10
width = left + cols + wspace + cbarwidth + right
height = top + rows + bottom
w = width / dpi
h = height / dpi
fig = plt.figure(figsize=(w,h), dpi=dpi)
im = fig.figimage(image, xo=left, yo=bottom);
# create axes on top
# bbox in pixels
bbox = Bbox([[left, bottom], [left + cols, bottom + rows]])
ax = fig.add_axes(fig.transFigure.inverted().transform_bbox(bbox))
ax.set_facecolor("None")
# recreate axis limits
ax.set(xlim=(-0.5, cols-0.5), ylim=(rows-0.5, -0.5))
# add colorbar
cbbox = Bbox([[left + cols + wspace, bottom],
[left + cols + wspace + cbarwidth, bottom + rows]])
cax = fig.add_axes(fig.transFigure.inverted().transform_bbox(cbbox))
fig.colorbar(im, cax=cax)
fig.savefig("test3.png")
有了这个可以确保图像本身没有失真。但是轴刻度可能会偏离一个像素左右,因为它们经过图形变换。此外,我还没有完全考虑 bbox 坐标是否需要移动半个单位。 (欢迎对最后一点发表评论!)
假设我正在尝试绘制一张 x*y
像素(或英寸)的黑洞图片。与许多其他人不同,我不想指定总图形大小,而是指定绘图内容的大小(因此框架中的所有内容,不包括框架本身、刻度、标签、颜色条、边距……)。
首选的方法是什么?
自己的尝试
我已经遇到过几次这个问题,但我现在更喜欢一些其他方法,而不是我之前的试错法猜测大小迭代...这是我的 "playground code":
import numpy as np
%matplotlib inline
import matplotlib
from matplotlib import pyplot as plt
import notebook
print(matplotlib.__version__, notebook.__version__)
# 3.0.0, 5.7.0 in my case
# some data
x, y = 456, 123
a = np.random.randn(y, x)
# the following at least gets the actual figure size in browser right,
# but if possible i'd like to avoid large white space margins as well...
# %config InlineBackend.print_figure_kwargs = {'bbox_inches': None}
dpi = plt.rcParams['figure.dpi']
fig, ax = plt.subplots(figsize=(x/dpi, y/dpi), dpi=dpi)
im = ax.imshow(a, interpolation='none')
cb = fig.colorbar(im)
# print sizes
bbox = ax.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
print(f"content size: ({bbox.width}, {bbox.height}) inch, ({bbox.width*fig.dpi}, {bbox.height*fig.dpi}) px")
print(f"fig size: {fig.get_size_inches()} in, {fig.dpi*fig.get_size_inches()} px")
print(f"dpi: {fig.dpi}")
输出:
content size: (4.023888888888889, 1.3870138888888888) inch, (289.72, 99.865) px
fig size: [6.33333333 1.70833333] in, [456. 123.] px
dpi: 72.0
如您所见,打印的图形尺寸为 456x123 像素,但如果您实际检查上传的图片(从浏览器复制粘贴),您会发现它只有 376x119 像素。虽然这可以修复(如代码中所述),但独立于此的实际 "content" 大小仍为 282x75 px :-/.
有什么想法吗?
不幸的是,没有首选的方法。 我想到了一些或多或少复杂的解决方法。
一个。使图和图一样大,边保存边展开
如果目的主要是生成图形的图像文件,最简单的方法可能是使图像绘图的轴与图形完全一样大,然后通过 bbox_inches="tight"
选项。
虽然需要手动在图外放置一个颜色条。
import numpy as np
import matplotlib.pyplot as plt
#create some image, with lines every second pixel
rows = 123
cols = 456
image = np.zeros((rows,cols))
image[:, np.arange(0,image.shape[1], 2)] = 1
image[np.arange(0,image.shape[0], 2), :] = 0.5
dpi = 100
fig, ax = plt.subplots(figsize=(image.shape[1]/dpi, image.shape[0]/dpi), dpi=dpi)
fig.subplots_adjust(0,0,1,1)
im = ax.imshow(image)
cax = fig.add_axes([1.05, 0, 0.03, 1])
fig.colorbar(im, cax=cax)
fig.savefig("test.png", bbox_inches="tight")
这样做的主要缺点是它可能导致图像有一个像素错误。这是由于位置总是在图形坐标中,导致在将轴大小标记为像素时出现舍入误差。
例如如果在上面选择 dpi=69
,结果将是
交错的线条很容易看出图片的高度太小了一个像素。
乙。使图形大于图像,调整边距
上面的一个缺点是轴装饰和颜色条在图的外面。要将它们放在里面,可以定义所有边距并计算最终数字需要多大。这是一个但是很麻烦的。
import numpy as np
import matplotlib.pyplot as plt
#create some image
rows = 123
cols = 456
image = np.zeros((rows,cols))
image[:, np.arange(0,image.shape[1], 2)] = 1
image[np.arange(0,image.shape[0], 2), :] = 0.5
dpi = 100
left = right = 60
top = bottom = 40
cbarwidth = 24
wspace = 10
width = left + cols + wspace + cbarwidth + right
height = top + rows + bottom
w = width / dpi
h = height / dpi
fig, (ax, cax) = plt.subplots(ncols = 2, figsize=(w,h), dpi=dpi,
gridspec_kw=dict(width_ratios=[cols, cbarwidth]))
fig.subplots_adjust(left = left/width, right = 1-right/width,
bottom = bottom/height, top = 1-top/height,
wspace = wspace / (cols + cbarwidth))
im = ax.imshow(image)
fig.colorbar(im, cax=cax)
fig.savefig("test2.png")
它也会遭受与 A. 相同的缺陷,例如如果使用一些奇数,例如
dpi = 72
left = right = 59
top = bottom = 37
cbarwidth = 19
wspace = 12
C。使用 figimage
并将轴放在顶部。
确保没有混叠效果的唯一方法是使用 figimage
。这会将像素坐标中的图像放入图中。但是,默认情况下将没有任何轴。 @anntzer proposed recently 提出了一个解决方案,即在图中 figimage
所在的位置放置一个轴。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
#create some image, with lines every second pixel
rows = 123
cols = 456
image = np.zeros((rows,cols))
image[:, np.arange(0,image.shape[1], 2)] = 1
image[np.arange(0,image.shape[0], 2), :] = 0.5
dpi = 100
left = right = 60
top = 40
bottom = 65
cbarwidth = 24
wspace = 10
width = left + cols + wspace + cbarwidth + right
height = top + rows + bottom
w = width / dpi
h = height / dpi
fig = plt.figure(figsize=(w,h), dpi=dpi)
im = fig.figimage(image, xo=left, yo=bottom);
# create axes on top
# bbox in pixels
bbox = Bbox([[left, bottom], [left + cols, bottom + rows]])
ax = fig.add_axes(fig.transFigure.inverted().transform_bbox(bbox))
ax.set_facecolor("None")
# recreate axis limits
ax.set(xlim=(-0.5, cols-0.5), ylim=(rows-0.5, -0.5))
# add colorbar
cbbox = Bbox([[left + cols + wspace, bottom],
[left + cols + wspace + cbarwidth, bottom + rows]])
cax = fig.add_axes(fig.transFigure.inverted().transform_bbox(cbbox))
fig.colorbar(im, cax=cax)
fig.savefig("test3.png")
有了这个可以确保图像本身没有失真。但是轴刻度可能会偏离一个像素左右,因为它们经过图形变换。此外,我还没有完全考虑 bbox 坐标是否需要移动半个单位。 (欢迎对最后一点发表评论!)