ImageMagick でサクッとヒートマップを作る

みんな大好き ImageMagick ですが、手軽にサクッとヒートマップを作る記事が意外と出てこなかったのでメモ。 ImageMagick のバージョンは以下のとおり。

```sh
$ convert --version
Version: ImageMagick 6.8.6-3 2013-10-02 Q16 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2013 ImageMagick Studio LLC
Features: DPC Modules
Delegates: bzlib freetype jng jpeg png xml zlib
```

## ヒートマップとは

ヒートマップ (heat map, temperature map) とは何かというと、こんなやつです。

![result.png](https://qiita-image-store.s3.amazonaws.com/0/10299/a645d06f-822d-6d2a-475d-cda0d6640cc3.png)

データが二次元上のどんな位置に分布しているのかを可視化する場合によく使うと思います。

## 使い方

適当な画像とヒートマップ作成の元画像、あと座標データ (x, y) を用意します。画像とデータには特に意味はありません。

![original.png](https://qiita-image-store.s3.amazonaws.com/0/10299/0c467407-f84a-63ec-2a76-a19d63867f7a.png)
![point.png](https://qiita-image-store.s3.amazonaws.com/0/10299/ff255fd9-6432-27e4-d4d3-361b4f60b351.png)
![gradation.png](https://qiita-image-store.s3.amazonaws.com/0/10299/e12c6161-9e45-6726-cd83-1ac471d89d08.png)

```sh
$ cat heatmap.csv
415,130
415,140
415,135
420,140
430,140
425,135
450,120
395,140
400,135
430,135
420,145
470,130
460,130
470,170
470,160
470,130
475,110
470,120
```

データを元に次のようにコマンドを叩きます。各座標データが `point.png` (50px x 50px) の中心に来るように x, y 座標から 25 を引いています。

```sh
size=`identify -format '%wx%h' original.png`
convert -size $size pattern:gray100 heatmap-tmp.png
convert heatmap-tmp.png -page +390+105 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +390+115 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +390+110 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +395+115 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +405+115 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +400+110 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +425+95 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +370+115 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +375+110 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +405+110 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +395+120 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +445+105 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +435+105 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +445+145 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +445+135 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +445+105 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +450+85 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +445+95 point.png -flatten heatmap-tmp.png
convert -channel ALL -clut heatmap-tmp.png gradation.png heatmap-tmp.png
composite -dissolve 50%x100% original.png heatmap-tmp.png result.png
```

なお座標データ CSV ファイル `heatmap.csv` を元に座標入力コマンドを作る Ruby スクリプト `heatmap.rb` は次のような感じです。

```rb
require 'csv'

ORGFILE = 'original.png'
TMPFILE = 'heatmap-tmp.png'
RESFILE = 'result.png'

POINT_PNG = 'point.png'
GRADATION_PNG = 'gradation.png'

CSVFILE = 'heatmap.csv'

puts "size=`identify -format '%wx%h' #{ORGFILE}`"
puts "convert -size $size pattern:gray100 #{TMPFILE}"
CSV.foreach(CSVFILE) do |row|
  puts "convert #{TMPFILE} -page +#{row[0].to_i - 25}+#{row[1].to_i - 25} #{POINT_PNG} -flatten #{TMPFILE}"
end
puts "convert -channel ALL -clut #{TMPFILE} #{GRADATION_PNG} #{TMPFILE}"
puts "composite -dissolve 50%x100% #{ORGFILE} #{TMPFILE} #{RESFILE}"
```

これを次のように一旦コマンドを確認してパイプで sh に食わせると簡単でしょう。

```sh
$ ruby heatmap.rb
size=`identify -format '%wx%h' original.png`
convert -size $size pattern:gray100 heatmap-tmp.png
convert heatmap-tmp.png -page +390+105 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +390+115 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +390+110 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +395+115 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +405+115 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +400+110 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +425+95 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +370+115 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +375+110 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +405+110 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +395+120 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +445+105 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +435+105 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +445+145 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +445+135 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +445+105 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +450+85 point.png -flatten heatmap-tmp.png
convert heatmap-tmp.png -page +445+95 point.png -flatten heatmap-tmp.png
convert -channel ALL -clut heatmap-tmp.png gradation.png heatmap-tmp.png
composite -dissolve 50%x100% original.png heatmap-tmp.png result.png
$ ruby heatmap.rb | sh
```

結果は次のようになります。

![result.png](https://qiita-image-store.s3.amazonaws.com/0/10299/a645d06f-822d-6d2a-475d-cda0d6640cc3.png)

## 各コマンドの説明

一応各コマンドの意味も調べたのでメモ。

```sh
size=`identify -format '%wx%h' original.png`
```

- <http://www.imagemagick.org/script/identify.php>

`identify` コマンドは画像ファイルの各パラメータを取得するコマンドです。ここでは後のコマンドで使いやすいように幅と高さを `600x310` のような形式で取得します。

```sh
convert -size $size pattern:gray100 heatmap-tmp.png
```

- <http://www.imagemagick.org/script/formats.php#builtin-patterns>

ヒートマップデータを作るための一時ファイルを作成しています。
`pattern:gray100` では ImageMagick 組み込みの画像パターンを指定しています。この組み込みパターンはあたかもそのパターンの画像があるかのように扱います。またサイズはヒートマップの元画像と同じサイズです。出力先ファイルとして `heatmap-tmp.png` という PNG ファイルを指定します。

```sh
convert heatmap-tmp.png -page +390+105 point.png -flatten heatmap-tmp.png
```

- <http://www.imagemagick.org/script/command-line-options.php#page>
- <http://www.imagemagick.org/script/command-line-options.php#flatten>
- <http://www.imagemagick.org/script/command-line-options.php#layers>

「どの座標がどのくらい濃いのか」というヒートマップデータを作っていきます。それまでできた `heatmap-tmp.png` を背景に、新たに (x, y) = (390, 105) の位置に `point.png` を重ねていく感じです。

```sh
convert -channel ALL -clut heatmap-tmp.png gradation.png heatmap-tmp.png
```

- <http://www.imagemagick.org/script/command-line-options.php#channel>
- <http://www.imagemagick.org/script/command-line-options.php#clut>

`heatmap-tmp.png` の濃い部分と薄い部分を `gradation.png`
からよしなに選んでマップする感じだと思います。あまりわかっていない。

```sh
composite -dissolve 50%x100% original.png heatmap-tmp.png result.png
```

- <http://www.imagemagick.org/script/composite.php>
- <http://www.imagemagick.org/script/command-line-options.php#dissolve>

オリジナル画像と作成したヒートマップ画像を重ねあわせています。
ここでは `dissolve` オプションで `original.png` を 50% の「濃さ」で、 `heatmap-tmp.png` を 100% の「濃さ」で重ねることを指定しています。