2023年11月10日金曜日

Matplotlibのカラーマップに例外値を加える方法

Matplotlibのカラーマップ(Colormap)は便利で連続的な数値を滑らかに変化する色(疑似カラー)で可視化することができる. ただ,例外的な値(NaNや-1)の時だけ別の色で描きたいときはどうすれば良いのか?実はMatplotlibの標準的な機能で実現できる.実際に使ってみたのでソースコードをメモしておく.

その方法のポイントは,既存のカラーマップ(ここではjet)を少し変更することと,numpyのndarrayではなくてMaskedArrayというクラスに変換してplt.imshowに渡すこと. Colormapクラスを操作するのでそれに必要なモジュールやクラスをimportしておく.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap

次に描画する対象の配列を準備する. ここではnpzで保存された271$\times$271の画像で,各画素には正の実数が格納されているとする. 確認のためshapeとtypeをprintしておく.

array = np.load("positive.npz")["arr_0"]
print(array.shape)
print(type(array))
(271, 271, 1)
<class 'numpy.ndarray'>

ただし,この正方形の画像の右上半分には有効な値は入っておらず,ここでは便宜的に0が格納されている. これを実数値を虹色で表すカラーマップjetを使って描画するとこうなる.

plt.imshow(array, cmap = "jet")

ただ,これだと0付近の値(青)と値が無いことを意味する数値ラベル0が殆ど同じ色になってしまう.そこで値が無い箇所だけグレーで表すことを考える. そのためにMaskedArrayというクラスを使ってarrayの値が0のときだけ無効であるという情報を保持させる.

marray = np.ma.array(array, mask=array == 0)
type(marray)
(271, 271, 1)
<class 'numpy.ma.core.MaskedArray'>

mask=array == 0の部分が解りにくいので詳しく説明する.arrayが27$\times$27の2次元配列なのでarray == 0とするとbroadcastの効果によりスカラーの0が全要素0の27$\times$27配列に変換される. ==の演算子によってarrayの要素が0のときだけTrue,そうでないときがFalseの値を要素に持つ27$\times$27の配列が生成される.maskにはそれが代入されている.

カラーマップjetを少し改造して,無効な値のときの色をset_bad関数でグレーに設定した上で, ndarrayの代わりにこのMaskedArray型の画像marrayをimshowに代入すると自動的に色分けしてくれる.

jet = plt.get_cmap("jet").copy()
jet.set_bad("gray")
plt.imshow(marray, cmap = jet)