【问题标题】:specifying matplotlib axis content size指定 matplotlib 轴内容大小
【发布时间】:2019-04-15 14:45:19
【问题描述】:

假设我正在尝试用x*y 像素(或英寸)绘制黑洞图片。与许多其他人不同,我不想指定总图形大小,而是指定绘图内容的大小(因此框架中的所有内容,不包括框架本身、刻度、标签、颜色条、边距......)。

最好的方法是什么?


自己的尝试

我已经遇到过几次这个问题,但我现在更喜欢其他方法,而不是我之前的试错猜测迭代大小......这是我的“操场代码”:

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 像素。虽然这可以修复(如代码中所述),但与此无关的实际“内容”大小保持为 282x75 像素:-/。

有什么想法吗?

【问题讨论】:

  • 遗憾的是没有首选的方法。最简单的解决方案还取决于您是否真的需要颜色条,以及您是否可以承受 1 像素的误差(即,如果瞄准 123 像素,则获得 122 像素的图像)。
  • heh, ouch ;) 是的,颜色条和准确性是需要的(重点是以准确的方式呈现科学数据,尽可能少的混叠效应来源)。如果 cbar 在另一个子图中也没关系。我也不在乎最终的图形大小(只要它不荒谬)。我已经尝试过一个太大的数字,然后把它剪下来,但无法做到这一点,我想:需要一个更简单的方法。

标签: matplotlib jupyter-notebook imshow


【解决方案1】:

不幸的是,没有首选的方法。 一些或多或少复杂的解决方法浮现在脑海中。

A.使图形和图片一样大,边保存边展开

如果目标主要是生成图形的图像文件,最简单的可能是使图像绘图的轴与图形完全一样大,然后通过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,结果将是

交错的线条很容易发现图像的高度太小了一个像素。

B.使图比图片大,调整边距

上面的一个缺点是轴装饰和颜色条在图形之外。要将它们放入其中,可以定义所有边距并计算最终数字需要多大。这是一个但是很麻烦的。

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 坐标是否需要移动半个单位。 (欢迎对最后一点发表评论!)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-07
    • 2019-02-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多