HOWTO · Matplotlib

Matplotlib でプロットを表示せずに画像ファイルへ保存する方法

非対話型バックエンド、savefig()、正しい保存後クローズの順序を使い、ウィンドウを開かずに Matplotlib の Figure を保存します。

このページの内容

GUI ウィンドウを開かずに Matplotlib のプロット全体を画像として保存するには、show() を呼び出さず、対象の Figure を savefig() で保存してから閉じます。ディスプレイのない環境では、pyplot をインポートする前に非対話型バックエンド Agg を選択してください。

Matplotlib のプロットを表示せずに保存する

GUI ウィンドウを開かずに Matplotlib の Figure 全体を保存するには、show() を呼び出さず、対象の Figure で savefig() を実行し、ファイルを書き込んだ後にだけ close() を呼び出します。サーバー、CI ジョブ、コンテナなど、明示的にヘッドレスバックエンドが必要な環境では、matplotlib.pyplot をインポートする前に静的な Agg バックエンドを選択します。

基本の順序は バックエンドを選択し、Figure を作成し、保存してから閉じる です。Figure への参照を保持すれば保存対象が明確になり、pyplot が現在アクティブとみなす Figure に依存しません。

Agg の明示指定は、ディスプレイサーバーがないマシンでもスクリプトを一定の動作にしたい場合に有効です。ただし、ファイルを保存するだけなら常に必要というわけではなく、Matplotlib が適切な非対話型バックエンドを自動選択することもあります。編集できないコマンドには環境変数 MPLBACKEND=Agg も使えます。複数の選択方法を理由なく併用すると、優先順位によって最後に適用される設定が勝つため避けてください。

ヘッドレスな Agg バックエンドで Figure を保存する

次の検証済み例は、show() を呼ばずに正弦波グラフを sine-wave.png として保存します。Agg は非対話型バックエンドで、ラスター出力を書き込むため画面上のウィンドウを必要としません。

from pathlib import Path

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt
import numpy as np

output = Path("sine-wave.png")
x = np.linspace(0, 2 * np.pi, 200)

fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(x, np.sin(x), color="#2166ac", linewidth=2)
ax.set(title="Sine wave", xlabel="x", ylabel="sin(x)")
ax.grid(alpha=0.25)

fig.savefig(output, dpi=150, bbox_inches="tight")
plt.close(fig)

saved = plt.imread(output)
assert output.is_file() and saved.size > 0
print("backend:", matplotlib.get_backend())
print("saved:", output.name)
print("valid PNG:", True)

検証した出力は次のとおりです。

backend: Agg
saved: sine-wave.png
valid PNG: True

ラベル付きの軸とグリッドを含む、保存済みの Matplotlib 正弦波 Figure。

画像はプロセスの現在の作業ディレクトリを基準に書き込まれます。スケジューラーやサービスが別の場所からスクリプトを起動する可能性がある場合は、絶対 Path または既知のプロジェクトディレクトリから組み立てたパスの方が安全です。

アサーションは生成した PNG を再度開き、ファイルが存在して画像データを含むことを確認します。これにより、出力がない場合や読み取れない場合を検出できます。上のプレビューでは、期待した曲線、ラベル、グリッドも確認できます。本番環境でも、空の出力や保存先の誤りが問題になるなら具体的な検証を行ってください。

画像形式と savefig() のオプションを選ぶ

通常、Figure.savefig() はファイル名の拡張子から形式を推測します。広く対応するラスター出力には PNG、Web で拡大縮小するグラフィックには SVG、ベクタードキュメントには PDF を使います。適切な拡張子がない場合は format 引数を明示します。

ラスターでは dpi が出力解像度を制御しますが、SVG や PDF のベクターパスを鮮明にするものではありません。bbox_inches="tight" は余分な周囲の空白を切り詰め、transparent=True は別の色を設定していなければ Figure と Axes の背景を透明にします。これらは出力ファイルに作用する設定であり、ウィンドウの表示には影響しません。利用できる形式は、バックエンドやオプションライブラリによって異なる場合があります。

用途に合わせて形式を選びます。文書や Web ページに埋め込むグラフには PNG、ブラウザで線や文字を拡大するなら SVG、印刷向けの処理には PDF が便利です。高い dpi はラスターの寸法とファイルサイズを増やすため、必要以上に大きな値を使わないでください。

ioff()、show()、保存順序を理解する

plt.ioff() は pyplot の対話モードを無効にしますが、汎用的なヘッドレス切り替えではありません。特にノートブックのフロントエンドは、対話モードが無効でもセル末尾の Figure を自動表示することがあります。GUI を使わないレンダラーが必要なら Agg を選び、ノートブックでは末尾の Figure 値を抑制するか、保存後に明示的に閉じます。

一方、savefig() の呼び出し自体に対話モードや show() は不要です。show() は対話型バックエンドで Figure を提示するためのもので、出力専用スクリプトでは呼ばないのが通常です。したがって、対話モードを切ることとファイル用バックエンドを選ぶことは、関連していても別の問題を解決します。

close(fig) より先に保存してください。先に閉じると pyplot からその Figure への参照が削除され、その後のステートフルな plt.savefig() が別の Figure または新しく作られた Figure を対象にすることがあります。同様に、show() のドキュメントでも、ブロッキングな show() の後に保存すると空の Figure になる可能性があると説明されています。先に保存するか、Figure オブジェクトを保持してその savefig() メソッドを呼びます。

ループでは Figure を解放し、メモリにも保存する

Pyplot はそのインターフェースで作成した Figure への参照を保持します。多数のプロットを作るバッチジョブでは、保存に成功するたびに close(fig) を呼び、Figure とそのメモリを解放します。後続処理が失敗する可能性がある場合は try/finally のクリーンアップを使います。

別の API が画像のバイト列を必要とし、ディスク上のファイルが不要なら、パスの代わりに io.BytesIO オブジェクトを fig.savefig() に渡します。読み取りやアップロードの前に seek(0) でバッファの先頭へ戻し、保存後は Figure も閉じてください。

出力先ディレクトリがない場合に対処する

savefig() は画像ファイルを作成しますが、存在しない親ディレクトリは作りません。次の検証済み境界例は、その失敗を捕捉し、どの場合でも Figure を閉じます。

from pathlib import Path

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt

output = Path("missing") / "plot.png"
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [2, 4, 3])

try:
    fig.savefig(output)
except FileNotFoundError:
    print("Create the parent directory before savefig().")
finally:
    plt.close(fig)
Create the parent directory before savefig().

実際の出力では、先に output.parent.mkdir(parents=True, exist_ok=True) でディレクトリを作成します。プロセスに書き込み権限があることも確認してください。

数値配列には imsave() を使う

入力が Axes、ラベル、凡例などを含むプロット済み Figure ではなく、2D または RGB(A) の数値配列なら、matplotlib.pyplot.imsave() を使います。配列値を画像ピクセルへ対応付ける関数であり、完全なプロットに対する Figure.savefig() の代わりではありません。

from pathlib import Path

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt
import numpy as np

pixels = np.array([[0.0, 0.5, 1.0], [1.0, 0.5, 0.0]])
output = Path("array-image.png")

plt.imsave(output, pixels, cmap="gray", vmin=0, vmax=1)

saved = plt.imread(output)
assert output.is_file() and saved.shape[:2] == pixels.shape
print("saved array:", output.name)
print("pixel grid:", saved.shape[0], "x", saved.shape[1])
saved array: array-image.png
pixel grid: 2 x 3