🤖

ubuntu20.04のmatplotlibでjpgなどの形式で出力できないことがあった話

2021/11/28に公開

タイトル通りの問題に遭遇したので概要を書いておきます。同じ問題で困っている人がいたら参考にしてください。その後に参考として問題検出から自分なりの問題解決までの流れ、および思考プロセスをざっと書きました。

環境

  • Ubuntu 20.04
  • python3-matplotlib 3.1.2-1ubuntu4
  • python3-pil 7.0.0-4ubuntu0.4

上記python3-matplotlibとpythno3-pilの組み合わせでは問題検出時点では最新版です。

問題要旨

matplotlibグラフのファイルへの出力時(つまりsavefig()呼び出し時)にファイル名がjpgだと以下のように失敗しました。

$ ./testplot.py
Traceback (most recent call last):
  File "./testplot.py", line 14, in <module>
    fig.savefig("test.jpg")
  File "/usr/lib/python3/dist-packages/matplotlib/figure.py", line 2180, in savefig
    self.canvas.print_figure(fname, **kwargs)
  File "/usr/lib/python3/dist-packages/matplotlib/backend_bases.py", line 2021, in print_figure
    canvas = self._get_output_canvas(format)
  File "/usr/lib/python3/dist-packages/matplotlib/backend_bases.py", line 1961, in _get_output_canvas
    raise ValueError(
ValueError: Format 'jpg' is not supported (supported formats: eps, pdf, pgf, png, ps, raw, rgba, svg, svgz)

これは既知の問題でmatplotlibの最新版では既に修正済でした。

Ubuntu20.04ではissueは発行されているもののまだ修正されていませんでした。

わたしはめんどくさいのでissueで追加情報を提供して修正を待ちつつ、手元ではいったんpngに保存した後にjpgに変換するというひと手間をかけることにしました。

問題検出から解決まで

トラブルシューティングの経験が浅い人などに役立つかなと思って、上記問題を検出してから私なりの解決までの流れ、および思考プロセスを書いておきます。べつに技術的に高度なことをしているわけでは全然ないんですが、トラシューの型の教科書的な実践例として紹介するにはいい題材かと思います。

検出の契機は単純で、単にmatplotlibで単純なグラフをjpg出力しようとしたら失敗したというもの。以下スクリプト(本物はもうちょっと複雑だけど煩雑なので省略)。

#!/usr/bin/python3

import numpy as np
import matplotlib.pyplot as plt

data = {'foo': 1, 'bar': 2}
names = list(data.keys())
values = list(data.values())

fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.bar(names, values)

fig.savefig("test.jpg")

実行すると既に述べたように失敗しました。

$ ./testplot.py
Traceback (most recent call last):
  File "./testplot.py", line 14, in <module>
    fig.savefig("test.jpg")
  File "/usr/lib/python3/dist-packages/matplotlib/figure.py", line 2180, in savefig
    self.canvas.print_figure(fname, **kwargs)
  File "/usr/lib/python3/dist-packages/matplotlib/backend_bases.py", line 2021, in print_figure
    canvas = self._get_output_canvas(format)
  File "/usr/lib/python3/dist-packages/matplotlib/backend_bases.py", line 1961, in _get_output_canvas
    raise ValueError(
ValueError: Format 'jpg' is not supported (supported formats: eps, pdf, pgf, png, ps, raw, rgba, svg, svgz)

これだと困るので既知障害かどうか、あるいは修正があるかどうかなどを確認することにしました。まずはGoogle先生を頼ります。闇雲に検索してもゴミがひっかかるだけなので、「matplotlib」「backend_bases.py」「canvas.print_figure」などの関係ありそうな、かつ、他のゴミがひっかからなさそうなワードで絞り込みました。すると運よくupstream matplotlibのissueが見つかりました。さらにそこからissueの文面をざっとたどることによって、修正PRがあり、かつ、マージされていることを見つけました。上述のように自分のUbuntu 20.04環境ではすでにpython3-matlplotlibとpython3-pilは最新版だったので、この問題はUbuntu 20.04ではまだ修正されていないことが明らかになりました。

次はUbuntu 20.04のpython3-matplotlib3パッケージのissue一覧の調査です。理由は、今後この問題は修正されるのか、報告だけはされているのか、あるいは誰にも認識されていないのかを明らかにして自分の対応策を決めるためです。検索条件に"pil"を指定すると見事同じ問題についてのissueにhit

I am using Ubuntu 20.04.1 LTS (including python3-matplotlib 3.1.2-1ubuntu4)

To save a figure using matplotlib.pyplot into a jpg file, matplotlib uses Python Imaging Library (PIL). Unfortunately even if we install the package python3-pil 7.0.0-4ubuntu0.1 it fails:

File "/usr/lib/python3/dist-packages/matplotlib/backend_bases.py", line 1961, in _get_output_canvas
        raise ValueError(
    ValueError: Format 'jpg' is not supported (supported formats: eps, pdf, pgf, png, ps, raw, rgba, svg, svgz)

The reason is matplotlib detect PIL version by doing so (in file backend_bases.py line 57):
from PIL import PILLOW_VERSION

PILLOW_VERSION has been removed since PIL version 7.0 to be replaced by PIL.__version__

To solve this issue it seems that you have to upgrade python3-matplotlib package at least to version 3.3 (the version packaged for ubuntu 20.10, I think)

ただし2020年9月に報告されてから修正に動いている形跡はないとわかりました(statusがnew)。

ここからどうするか考えたところ、以下のような選択肢(複数選択可)が思い浮かびました。

  • jpgでの保存をあきらめる
  • 手元で問題が修正された独自版のpython3-matplotlibを使う
  • 問題が発生しないpython3-matplotlibないしpython3-pilの古いバージョンを持ってきて使う
  • 修正されるのを待つ。場合によっては自分で修正を送る
  • Ubuntu 20.10以降に乗り換える

上記の選択肢のうちのどれを採用するかは人によるんですが、わたしには

  • Ubuntu 20.04から乗り換えたくない
  • なるべく多くの人の環境でjpgでグラフが作れるようにしておきたい
  • そもそもあんまりがんばりたくない

という思いがあったので、

  • matplotlibではpngで保存しておいて、同じスクリプト内で別途pngをjpgに変換する、および、
  • upstreamには役に立つかもしれない追加コメントだけしておく

ということにしました。

スクリプトは以下のように修正しました。

#!/usr/bin/python3

import numpy as np
import matplotlib.pyplot as plt
import os
from PIL import Image

data = {'foo': 1, 'bar': 2}
names = list(data.keys())
values = list(data.values())

fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.bar(names, values)

# I want to create a jpg image by matplotlib but it's difficult due to this bug.
# https://bugs.launchpad.net/ubuntu/+source/matplotlib/+bug/1897283
fig.savefig("test.png")
Image.open("test.png").convert("RGB").save("test.jpg")
os.remove("test.png")

ちょっとダサいですがここではダサくて困るわけではないので動けばいいのです。大事なのは最初からjpgで保存すればいいのにわざわざダサいことしてる理由を明示しておくことです。

Ubuntuのissueには以下のようなコメントを残しました。

I encountered the same problem.

> To solve this issue it seems that you have to upgrade python3-matplotlib package at least to version 3.3 (the version packaged for ubuntu 20.10, I think)

v3.1.3 also supports pillow >= 7.0. This version might be more appropriate.

https://github.com/matplotlib/matplotlib/releases/tag/v3.1.3
> - support pillow >= 7

Original fix.
https://github.com/matplotlib/matplotlib/pull/16086

This fix is applied to v3.1.3
https://github.com/matplotlib/matplotlib/blob/v3.1.3/lib/matplotlib/backend_bases.py#L57

このコメントには2つの期待があります。1つめは「俺のところでも問題が起きる」ということによって開発者たちに問題が重要であるとみなしてもらえるかもしれなかったり、あるいは彼らが問題を忘れていた場合に思い出してもらうことです。ダメ元ですが、やったところで必要な時間は1分なのでやりました。

2つめはissue発行者が言うようなmatplotlibのマイナーバージョンアップは必要なくて、より影響の少ないパッチバージョンアップで済むことを伝えることによって修正へのやる気を出してもらうことです。Ubuntu 20.04のような長期サポートOSで、しかもリリースから2年近く経っているようなものでマイナーバージョンアップは嫌がられるかなと思って書きました。実際のところUbuntuのパッケージ更新のポリシーは知らないんですが、これも書くのは一瞬だし書いて邪魔になるものでもないのでやりました。

参考までに書いておくと、この問題検出から解決までの経過時間はおよそ3時間。所要時間はおよそ30分から1時間程度でした。これを早いと思うか遅いと思うかは読者のみなさまのトラシューの経験値などによって変わってくると思います。個人的には「けっこう手間取ったものの、土地勘がないソフトウェアについてのものとしては及第点かな」といったところ。

この投稿が誰かの何かに役立ちますように。

Discussion