Wenqing Peng

为matplotlib.figure设置figsize/DPI

当我第一次尝试学术发表时,我得到的审稿意见之一是使用>300DPI (Dot Per Inch)清晰度的图表,以确保打印后的图标足够清晰。因此之后在代码中保存图片时,我都将参数设置为dpi=300。但今天当我调试matplotlib的图时,面对大小以英尺为单位的figsize,我很疑惑究竟应该怎么设置。

我的任务是将10个subfigure水平排列到一个Figure中,如果我是用默认的figsize(6.4x4.8inches,一个不怎么长的长方形),每个subfigure都变成了非常窄的一竖条,因此我需要调整figsize从而保证每个subfigure都有足够的水平空间。

Figure的figsize参数的默认值是[6.4, 4.8] ,单位是英尺(inch)。如果我需要将图片的比例变得更宽,例如接近10:1,我应该将6.4变成6.4*10,还是将4.8变成4.8/10?

DPI? Pixel? FigSize? PrintSize?

我搜索到的第一个结论是,DPI,即打印时每英尺的点数,只有在确定打印尺寸的情况下才有意义。对于数字图片来说,确定他们清晰度的唯一属性是像素点(pixel)的数目。一张分辨率为3840 × 2160的图片在我们的电脑屏幕上总是清晰的。当打印他们时,打印的大小决定了打印后的清晰度,如果300dpi是清晰打印的经验法则,那我们可以确保它打印到12.8x7.2英寸(32.512x18.288cm,约A4大小)的纸上后还是清晰的。因此确保打印清晰度的第一步时知道打印的目标大小。

假设我们的图表将要打印在期刊中,我们可以得知图片打印后的大致大小,以Elsevier给出的建议为例:

TARGET SIZE Image width Image width Pixels at 300 dpi Pixels at 500 dpi Pixels at 1000 dpi
Minimal size 30 mm 85 pt 354 591 1181
Single column 90 mm 255 pt 1063 1772 3543
1.5 column 140 mm 397 pt 1654 2756 5512
Double column (full width) 190 mm 539 pt 2244 3740 7480

Elsevier_size

保险起见我们可以假设打印宽度是20cm,那我们10:1大小的图片的打印尺寸可以设定为20x2cm,也就是大约7.87x0.787inches,让我们试一试 figsize=(7.87,0.787), dpi=300

fig_naive_scale

所有东西都糊在了一起。。。

让图上元素与版面协调

看起来subfigure中画出来的元素都太大了,无论是线条、文字、坐标还是标记等等。这是因为这些元素的单位是Point(1/72inches),和figsize一样也是打印尺寸。这些默认值对于6.4x4.8inches的版面正合适,但当我们将每个subfigure分到的版面缩小到0.787x0.787inches,这些元素就显得相对太大了。我们可以改变rcParams里面对应的参数,将他们变小:

params = {
    'axes.linewidth': 0.5,  # 0.8
  	'axes.labelsize': 5,  # medium
  	'axes.titlesize': 4,  # large
  	'font.size': 5,  # 10.0
  	'lines.linewidth': 0.5,  # 1.5
  	'lines.markersize': 2,  # 6.0
  	'lines.markeredgewidth': 0.5,  # 1.0
    'boxplot.boxprops.linewidth': 0.5,  # 1.0
    'boxplot.capprops.linewidth': 0.5,  # 1.0
    'boxplot.flierprops.markeredgewidth': 0.5,  # 1.0
    'boxplot.meanprops.linewidth': 0.5,  # 1.0
    'boxplot.medianprops.linewidth': 0.5,  # 1.0
    'boxplot.whiskerprops.linewidth': 0.5,  # 1.0
    'xtick.major.width': 0.5,  # 0.8
    'ytick.major.width': 0.5,  # 0.8
}
plt.rcParams.update(params)
fig, ax = plt.subplots(1, 10, figsize=(7.87,0.787), dpi=300)

这时的图片合理了许多:

fig_param

但设置这么多参数实在太不优雅了,况且他们本身的默认值本身就是相互协调的,例如全部使用默认值时候的效果:

fig_default

为了保持这种协调,我们可以保持每个subfigure使用默认的版面尺寸(6.4x4.8inches),但由于我没找单独设置figure对象中axes对象大小的参数,我只好把figure对象的figsize参数设置为(6.4*10)x4.8inches(162.56x12.192cm):

fig_multiply

每个subfigure中绘制的元素像上图一样协调,但他们看起来实在太小了!他们得打印到有65寸电视那么大的纸上才看的顺眼。想让绘图元素相对与版面再大一点,那么把figsize再缩小一点,比如缩小1/4到(6.4*10*0.25)x(4.8*0.25)inches就看起来相当不错了:fig_ratio_scale

勤俭节约像素

最后一个小问题,我们这张16x1.2inches的图片按照300dpi保存会得到一张4800x360pixel的图片,宽度4800个像素超出了保证期刊清晰打印需要的2244个像素两倍。

Pixels = Resolution (DPI) × Print size (in inches)

这是因为我们figure对象的实际版面尺寸比目标打印尺寸多了约一倍,也就是说我们的figure将要以更小的尺寸打印,那我们我们只需要让dpi = 300 * print_size / figsize,就能有足够的像素使得打印的时候满足300dpi的清晰度。因此在这个例子里,Figure对象的dpi只需要设置为300 * 7.87 / 16 = 147.5625。

简而言之

def my_subplots(nrow, ncol, dpi=300, scale=1, print_width=19/2.54):
    """Subplots with sufficient DPI

    Args:
        nrow: number of rows of subfigures
        ncol: number of column of subfigures
        dpi: target print DPI
        scale: compact factor in range (0, 1], smaller means plot elements are more compact
        print_width: target print witdth in inches, default is Elsevier double column width

    Returns:
        figure: matplotlib Figure object
        ax: list of matplotlib Axes objects

    """
    default_width, default_height = (6.4, 4.8)  # inches
    fig_width = default_width * ncol * scale
    fig_height = default_height * nrow * scale
    my_dpi = dpi * print_width / fig_width

    try:
        figure, ax = plt.subplots(nrow, ncol, figsize=(fig_width, fig_height), dpi=my_dpi, layout="constrained")
        return (figure, ax)
    except NameError:
        print("===ERROR===(my_subplot): import matplotlib first by: import matplotlib.pyplot as plt")
        return (None, None)
 
 fig, ax = my_subplots(1, 10, scale=0.25)  # output image has exact 2244 pixels in width

© pvvq 2024. 以上通过 知识共享署名-相同方式共享 4.0 国际许可协议许可。 邮件评论