27
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

(matplotlib)ピクセル単位でサイズを指定したグラフを描きたい

Last updated at Posted at 2020-01-12

はじめに

matplotlib で作成するグラフについて、次のように プロット領域の大きさ(幅・高さ)をピクセル単位で指定するにはどうすればいいのか についての解説です。

top.png

実行環境(Google Colab.)

  • python 3.6.9
  • matplotlib 3.1.2

注意

Jupyter環境(Google Colab.環境)の場合、plt.show(...)により実行結果セルに表示されるPNG画像と、plt.savefig(...)ファイル出力されるPNG画像では、その大きさが違ってきます

具体的には、実行結果セルに表示されるPNG画像は、**周囲の余白部分が自動的にトリミングされたもの**になります。

例えば、次のコードを Google Colab.環境で自動した場合、出力セルに表示される画像は $348\times 270$ (px)、一方で、test.png として保存される画像は $400\times 300$ (px) となります。

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0,20,100) # 描画用サンプルデータ
y = x ** 0.5              # 描画用サンプルデータ

fig = plt.figure(dpi=100, figsize=(4,3))
plt.plot(x,y)
plt.savefig('test.png') # ファイルに出力される 400 x 300 px 
plt.show()              # 出力セルに表示される 348 x 270 px
  • figsize=(4,3) では、インチ単位で画像の「幅」と「高さ」を指定しています。
  • dpi=100では「Dot per Inch(=1インチあたりのドット数(ピクセル数)」を指定しています。
  • 出力画像サイズは、が $4,\mathrm{inch}\times 100,\mathrm{dpi}=400,\mathrm{pixel}$、高さが $3,\mathrm{inch}\times 100,\mathrm{dpi}=300,\mathrm{pixel}$ になる・・・はずなのですが、出力セルに表示される画像は、周囲余白がトリミングされてひとまわり小さくなったものになります。

以降は、plt.savefig(...)ファイル出力される画像を対象に話を進めます。

画像サイズではなく(正味の)プロット領域のサイズを求めたい

画像サイズ(ピクセル単位)は、上述したように、インチ単位で指定される figure の「サイズ」と「DPI」から求めることができました。これは、figure のインスタンス(fig)から次のように計算できます。

画像サイズ(ピクセル単位)を計算
fig_w_px = int(fig.get_figwidth()  * fig.get_dpi())  
fig_h_px = int(fig.get_figheight() * fig.get_dpi())
print(f'■ Fig size  {fig_w_px} x {fig_h_px} [px]')

まず、ここでは、この画像の全体サイズではなく、次のように赤枠で囲まれた(正味の)プロット領域の大きさ(ピクセル単位)が、どのようになっているのか求めていきます。

top2.png

プロット領域の大きさ(ピクセル単位)は、次のように、Axesオブジェクト(ax)から値を取得して計算することができます。なお、図内に日本語を使っているので、Google Colab.環境などでは、先に !pip install japanize-matplotlib を実行してライブラリをインストールしておいてください。

プロット領域サイズ(ピクセル単位)を計算
import numpy as np
import japanize_matplotlib 
import matplotlib.pyplot as plt

x = np.linspace(0,20,100) # 描画用サンプルデータ
y = x ** 0.5              # 描画用サンプルデータ

fig = plt.figure(dpi=100, figsize=(4,3))
ax = plt.gca()
ax.plot(x,y)
ax.set_xlabel('入力',fontsize=12)
ax.set_ylabel('出力',fontsize=12)
plt.savefig('foo.png')

# Figure(画像全体)のサイズ
fig_w_px = int(fig.get_figwidth()  * fig.get_dpi())  
fig_h_px = int(fig.get_figheight() * fig.get_dpi())
print(f'■ Fig size   {fig_w_px} x {fig_h_px} [px]')

# Axis(プロット領域)のサイズ
ax_size_inch = ax.figure.get_size_inches()
ax_w_inch = ax_size_inch[0] * (ax.figure.subplotpars.right - ax.figure.subplotpars.left)
ax_h_inch = ax_size_inch[1] * (ax.figure.subplotpars.top - ax.figure.subplotpars.bottom)
ax_w_px = int( ax_w_inch * fig.get_dpi() )  
ax_h_px = int( ax_h_inch * fig.get_dpi() )
print(f'■ Axis size  {ax_w_px } x {ax_h_px } [px]')

実行結果は、次のようになります。また、ファイル出力された foo.png を画像編集ソフト等で開き、プロット領域の大きさを確認してみると、確かに下記の結果とも一致します。

実行結果
■ Fig size   400 x 300 [px]
■ Axis size  310 x 226 [px]

プログラムの解説

  • ax.figure.get_size_inches() で、Axesオブジェクトの全体の大きさ(=各軸の目盛やラベル(「入力」や「出力」などの文字)も含んだ領域の大きさ)をインチ単位で取得しています。

  • ax.figure.subplotpars.left では、Axesオブジェクトの幅全体を $1.0$ としたときの プロット領域の左端位置 を表す値($0.0$~$1.0$)です。また ax.figure.subplotpars.right は、プロット領域の右端位置 を表す値です。

    • ここで、両者の差分 ax.figure.subplotpars.right - ax.figure.subplotpars.left は、Axesオブジェクトの幅全体を $1.0$ としたときに、そこでプロット領域の幅が占める割合を表したものになります。
  • プロット領域の幅をインチ単位で求めるために、Axesオブジェクトの全体幅 ax_size_inch[0] (インチ単位)と、プロット領域の幅(割合)ax.figure.subplotpars.right - ax.figure.subplotpars.left を掛け、ax_w_inch に格納しています。

  • インチ単位を、ピクセル単位に変換するために、ax_w_inch に、解像度 fig.get_dpi() をかけて、ax_w_px に格納しています。

プロット領域のサイズをピクセル単位で指定してグラフを描きたい

プロット領域のサイズをピクセル単位で求めることはできました。

今度は、逆に、プロット領域のサイズをピクセル単位で指定してグラフを描いていきます。次のコードは、プロット領域の大きさが、$400\times 300$ (px) になるようにグラフを描画するサンプルです。

なお、原因は不明なのですが、プログラムで最後で、プロット領域の大きさを再計算して出力すると Axis size 387 x 302 [px] となってしまいます。ただ、実際に出力された画像(foo.png)についてプロット領域の大きさを測ってみると、正しく $400\times 300$ (px) となっています。

プロット領域の大きさをピクセル単位で指定してグラフ出力
import numpy as np
import japanize_matplotlib 
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import Divider, Size # 追加
from mpl_toolkits.axes_grid1.mpl_axes import Axes # 追加

x = np.linspace(0,20,100) # 描画用サンプルデータ
y = x ** 0.5              # 描画用サンプルデータ

ax_w_px = 400  # プロット領域の幅をピクセル単位で指定
ax_h_px = 300  # プロット領域の高さをピクセル単位で指定

# サイズ指定のための処理 ↓↓ ここから ↓↓ 
fig_dpi = 100
ax_w_inch = ax_w_px / fig_dpi
ax_h_inch = ax_h_px / fig_dpi
ax_margin_inch = (0.5, 0.5, 0.5, 0.5)  # Left,Top,Right,Bottom [inch]

fig_w_inch = ax_w_inch + ax_margin_inch[0] + ax_margin_inch[2] 
fig_h_inch = ax_h_inch + ax_margin_inch[1] + ax_margin_inch[3]

fig = plt.figure( dpi=fig_dpi, figsize=(fig_w_inch, fig_h_inch))
ax_p_w = [Size.Fixed(ax_margin_inch[0]),Size.Fixed(ax_w_inch)]
ax_p_h = [Size.Fixed(ax_margin_inch[1]),Size.Fixed(ax_h_inch)]
divider = Divider(fig, (0.0, 0.0, 1.0, 1.0), ax_p_w, ax_p_h, aspect=False)
ax = Axes(fig, divider.get_position())
ax.set_axes_locator(divider.new_locator(nx=1,ny=1))
fig.add_axes(ax)
# サイズ指定のための処理 ↑↑ ここまで ↑↑

ax.plot(x,y)
ax.set_xlabel('入力',fontsize=12)
ax.set_ylabel('出力',fontsize=12)
plt.savefig('foo.png')

# Figure(画像全体)のサイズ
fig_w_px = int(fig.get_figwidth()  * fig.get_dpi())  
fig_h_px = int(fig.get_figheight() * fig.get_dpi())
print(f'■ Fig size   {fig_w_px} x {fig_h_px} [px]')

# Axis(プロット領域)のサイズ
ax_size_inch = ax.get_figure().get_size_inches()
ax_w_inch = ax_size_inch[0] * (ax.figure.subplotpars.right - ax.figure.subplotpars.left)
ax_h_inch = ax_size_inch[1] * (ax.figure.subplotpars.top - ax.figure.subplotpars.bottom)
ax_w_px = int( ax_w_inch * fig.get_dpi() )  
ax_h_px = int( ax_h_inch * fig.get_dpi() )
print(f'■ Axis size  {ax_w_px } x {ax_h_px } [px]') 
# 上記の print出力では、400 x 300 にならないが、
# 実際の画像('foo.png')ではプロット領域の大きさが 400 x 300 になっている
27
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?