【Python】matplotlibでurlからimreadした複数の画像をいい感じにしてpdf出力する

## はじめに
いい加減なタイトルにも関わらずこのページを開いてくださりありがとうございます！！

皆さんはネット上の散らばった画像を集めて1つのファイルにいい感じに並べたいと思ったことはありますでしょうか？
私は並べたいと思ってしまったので今この記事を書いております。

私の場合は各種天気図を1つのファイルにまとめたいと思いました。。。(専門天気図って画像が分かれていて全部開いたりするの面倒なんですよね。。。)

## やりたいこと
* 散らばったネット上の画像を1つのファイルにまとめる
* レイアウトはいい感じにしたい
* 用紙サイズはA4が良い(印刷したいため)
* PDFで出力したい

## 作戦
### 使用する天気図
各種天気図は北海道放送「[予想にチャレンジ！専門天気図](https://www.hbc.co.jp/weather/pro-weather.html)」のホームページから拝借します。

このページは以下のように各種天気図が羅列してありアクセスしやすくなっており便利です。
![スクリーンショット 2023-04-09 161741.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/e25249ca-0057-8669-a17e-d1e1a56fde5c.png)

今回は以下の天気図たちをまとめます。

速報天気図(SPAS)
https://www.hbc.co.jp/tecweather/SPAS.jpg
![9991f12c-398a-4538-a938-1eb0706825fe.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/03b1113e-eb4c-b407-90d1-6ba1b4297fe9.png)

アジア太平洋地上天気図(ASAS)
https://www.hbc.co.jp/tecweather/ASAS.jpg
![6244224c-ae80-45dd-9870-2a4f3d98ef50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/daa5d86e-06a8-7c1f-8bd6-cd2fb99639ff.png)

日本850hPa風・相当温位12・24・36・48時間予想図(FXJP854)
https://www.hbc.co.jp/tecweather/FXJP854.jpg
![5741e9da-6f14-42a0-9989-7b69fad001af.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/4a910793-6932-3534-7ec2-60b4590afe4c.png)

アジア850hPa・700hPa天気図(AUPQ78)
https://www.hbc.co.jp/tecweather/AUPQ78.jpg
![a3fb0675-68f8-4d1e-a890-cf2377b0dc6a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/9b92432b-7495-9de5-920e-768a476f74ab.png)

アジア500hPa・300hPa天気図(AUPQ35)
https://www.hbc.co.jp/tecweather/AUPQ35.jpg
![3b1d384c-90d5-4d33-98f8-41b13c8bcf86.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/de7bb2da-9ed0-3489-85cf-e8ff11f9ebaf.png)


500hPa高度・渦度+極東地上気圧・降水量・海上風 
12・24時間予想図(FXFE502)
https://www.hbc.co.jp/tecweather/FXFE502.jpg
![da6c99ce-6697-405b-9124-bf507635c5d7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/53ae7619-a048-a7e7-d5d0-ab88cd09fade.png)

極東850hPa気温･風､700hPa上昇流+極東500hPa気温､
700hPa湿数12・24時間予想図(FXFE5782)
https://www.hbc.co.jp/tecweather/FXFE5782.jpg
![074ca3bc-a37f-4885-98fd-3dd3eb497a28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/e68a36f2-2623-ea08-faf0-3e0465814724.png)

### 使用言語
今回はPythonでやります。
使用する主なライブラリは以下の通りです。
* opencv
* python-opencv-utils
* matplotlib

## 実装
では順に実装していきます。

### import, setting
```python
import cv2
import cv2u # urlから画像を読み込むために使用
import datetime # いつのファイル、天気図かがわかるようにするため
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages

# matplotlibでの日本語の文字化け(豆腐)を回避するための設定
plt.rcParams['font.family'] = 'MS Gothic'

date = datetime.date.today()
height, width = 8.27, 11.69 # matplotlibのfigureのサイズ、A4縦だとこのサイズらしい
```

## 使用する画像周りの設定
```python
# 使用する画像を「タイトル：url」のdictで保持
dict_urls = {
    "速報天気図(SPAS)": "https://www.hbc.co.jp/tecweather/SPAS.jpg",
    "アジア太平洋地上天気図(ASAS)": "https://www.hbc.co.jp/tecweather/ASAS.jpg",
    "日本850hPa風・相当温位12・24・36・48時間予想図(FXJP854)": "https://www.hbc.co.jp/tecweather/FXJP854.jpg",
    "アジア850hPa・700hPa天気図(AUPQ78)": "https://www.hbc.co.jp/tecweather/AUPQ78.jpg",
    "アジア500hPa・300hPa天気図(AUPQ35)": "https://www.hbc.co.jp/tecweather/AUPQ35.jpg",
    "500hPa高度・渦度+極東地上気圧・降水量・海上風 \n12・24時間予想図(FXFE502)": "https://www.hbc.co.jp/tecweather/FXFE502.jpg",
    "極東850hPa気温･風､700hPa上昇流+極東500hPa気温､\n700hPa湿数12・24時間予想図(FXFE5782)": "https://www.hbc.co.jp/tecweather/FXFE5782.jpg",
}
# matplotlibのsubplotでの各画像の位置
pos = [421, 422, 412, 223, 224, 211, 212]
# 改ページを行うindex(指定した場所から新しいページになる)
i_reset_page = [0, 5]
# 画像の上半分を使う場合のflg(下半分はいらない画像があったので。。。)
half = [False, False, True, False, False, False, False]
# 画像数(なくてもよい。。。)
n = len(dict_urls)
```

#### 3桁のintの意味
3桁のint(仮に $ijk$ とあらわされるとする)は左から順に各桁が
figureを $i$ 行 $j$ 列のグリッドに分割したときの $k$ 番目のグリッドを指定する！という意味になります。
※具体的な位置に関しては下の方の「plt.subplotの補足」で少し触れています。

## 処理本体
```python
def main():
    # pdf書き出し用のobject生成、ファイル名は「日付.pdf」
    pp = PdfPages(f"{date}.pdf")
    # 現在のページ数(スタートは1ページ目、出力用)
    i_page = 1
    for i, (k, v) in enumerate(dict_urls.items()):
        if i in i_reset_page:
            # figureの初期化
            fig = plt.figure(figsize=(height, width))
            plt.suptitle(f"{date} ({i_page}/{len(i_reset_page)})")
            i_page += 1
        # 画像をurlから取得
        img = cv2u.urlread(v)
        
        # pos[i]に画像、タイトルの表示
        plt.subplot(pos[i])
        plt.title(k)
        # 画像は白黒画像なのでimshow字にRGBに変換している
        if half[i]: plt.imshow(cv2.cvtColor(img[:len(img)//2], cv2.COLOR_GRAY2RGB))
        else: plt.imshow(cv2.cvtColor(img, cv2.COLOR_GRAY2RGB))
        plt.axis("off") # 目盛りはいらない

        # 各ページの最後の画像をimshowしたらファイルにページを書き出す
        if i+1 in i_reset_page or i+1==n: pp.savefig(fig)
    pp.close()
```

### plt.subplotの補足
ざっくりとした補足です。

plt.subplotに渡すposですが
3桁のint(例：```plt.subplot(111)```)かintを3つ並べたもの(例：```plt.subplot(1, 1, 1)```)になります。
今回は前者の方法を使用しております。

例として、423で指定した場合は以下の図の③の箇所に画像を書くことになります。
![423](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/3cf7e860-121e-f024-d8f6-2c9f16de1e2c.png)

また、同じfigure内で違う大きさで画像を貼りたい(例えば、上の図の③④の位置に張りたい)場合には、plt.subplot(412)と指定すればよいです。
412と場所を指定することで以下の図のようになるため、先ほどの4行2列の箇所の③④部分に画像を貼ることができます。
![412](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/13f59abd-f216-ef45-c151-166bdc537ae0.png)

これを踏まえて、
```python
# 4行2列の図の①に貼り付け
plt.subplot(421)
plt.imshow(im1)
# 4行2列の図の②に貼り付け
plt.subplot(422)
plt.imshow(im2)
# 4行1列の図の②に貼り付け
plt.subplot(412)
plt.imshow(im3)
```
とすることで、サイズの違う大きさで画像を複数並べることができます。

## 処理結果
以下のようになりました。
記事用にpngで出力していますが、2ページのpdfで両面印刷すると1枚になるため満足です。
使用した北海道放送さんのホームページは毎日同じurlでも最新の天気図に更新されるので、今回のコードを使いまわすことで最新の情報がまとまって得られるため手間が省けると感じます。(私と同じことをやりたい人はあまりいないと思いますが。。。)

![ec1d4101-4d37-481d-86b4-7482d41da088.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/71cd44b1-4f5d-b006-a23d-ed32709a69e8.png)
![cd3ef408-c8f8-403f-b622-16a008623c07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/da639822-49db-fa7f-40aa-85030bd10b0e.png)

## まとめ
今回はネット上に散らばっている画像を1ファイルにまとめてpdf出力するということをやりました。

opencv-utilsにurlreadがあったり、matplotlibにpdf出力用のPdfPagesがあったりと知らなかったけど便利な機能を見つけることもできて良かったです。

## おまけ：全体のコード
```python
import cv2
import cv2u
import datetime
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages

plt.rcParams['font.family'] = 'MS Gothic'

date = datetime.date.today()
height, width = 8.27, 11.69

dict_urls = {
    "速報天気図(SPAS)": "https://www.hbc.co.jp/tecweather/SPAS.jpg",
    "アジア太平洋地上天気図(ASAS)": "https://www.hbc.co.jp/tecweather/ASAS.jpg",
    "日本850hPa風・相当温位12・24・36・48時間予想図(FXJP854)": "https://www.hbc.co.jp/tecweather/FXJP854.jpg",
    "アジア850hPa・700hPa天気図(AUPQ78)": "https://www.hbc.co.jp/tecweather/AUPQ78.jpg",
    "アジア500hPa・300hPa天気図(AUPQ35)": "https://www.hbc.co.jp/tecweather/AUPQ35.jpg",
    "500hPa高度・渦度+極東地上気圧・降水量・海上風 \n12・24時間予想図(FXFE502)": "https://www.hbc.co.jp/tecweather/FXFE502.jpg",
    "極東850hPa気温･風､700hPa上昇流+極東500hPa気温､\n700hPa湿数12・24時間予想図(FXFE5782)": "https://www.hbc.co.jp/tecweather/FXFE5782.jpg",
}
pos = [421, 422, 412, 223, 224, 211, 212]
i_reset_page = [0, 5]
half = [False, False, True, False, False, False, False]
n = len(dict_urls)

def main():
    pp = PdfPages(f"{date}.pdf")
    i_page = 1
    for i, (k, v) in enumerate(dict_urls.items()):
        if i in i_reset_page:
            fig = plt.figure(figsize=(height, width))
            plt.suptitle(f"{date} ({i_page}/{len(i_reset_page)})")
            i_page += 1
        img = cv2u.urlread(v)
        
        plt.subplot(pos[i])
        plt.title(k)
        if half[i]: plt.imshow(cv2.cvtColor(img[:len(img)//2], cv2.COLOR_GRAY2RGB))
        else: plt.imshow(cv2.cvtColor(img, cv2.COLOR_GRAY2RGB))
        plt.axis("off")

        if i+1 in i_reset_page or i+1==n: pp.savefig(fig)
    pp.close()
    
if __name__ == "__main__":
    main()

```