【matplotlib】logスケールのカラーバの値そのものを取得する方法

# はじめに
matplotlibで画像を対数(log)スケールで表示するとき、以下のようにその**logスケールにした後の色情報値として持っておきたい**場合があると思います。その値を取得する方法と、対数の正規化の簡易的な実装コードを紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/4699e8f1-fbe1-f864-cca7-e9e09ecb16a1.png)

# 実装
Google Colabで作成した本記事のコードは、[こちら](https://colab.research.google.com/drive/1P8PK-ekVx1mij-bQnXGa4ZoI6jh6pEXW?usp=sharing)にあります。

## 各種インポート
```Python:各種インポート
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
```
実行時の各種バージョン

| Name | Version |
|:-:|:-:|
| numpy | 1.22.4 |
| matplotlib | 3.5.3 |


## 方法
matplotlibの[LogNorm](https://matplotlib.org/stable/api/_as_gen/matplotlib.colors.LogNorm.html)を使います。LogNormクラスのインスタンスを生成して、``__call__()``メソッドを呼び出して、値を受け取れば終了です。

```python:実装例
data = np.array([[1, 10] ,[100, 1000]])

# インスタンス生成
log_norm = LogNorm(vmin=data.min(), vmax=data.max())

# log正規化(__call__()メソッドを呼ぶ)
log_norm_data = log_norm(data, clip=True)

fig, (ax0, ax1, ax2) = plt.subplots(1, 3, figsize=(14, 4))

im0 = ax0.imshow(data)
ax0.set_title('data')
cbar0 = fig.colorbar(im0, ax=ax0, shrink=0.9)

im1 = ax1.imshow(data, norm=log_norm)
ax1.set_title('data with logsale')
cbar1 = fig.colorbar(im1, ax=ax1, shrink=0.9)

im2 = ax2.imshow(log_norm_data)
ax2.set_title('log_norm_data')
cbar2 = fig.colorbar(im2, ax=ax2, shrink=0.9)

plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/9b751744-3b33-bc1f-5609-e024025b7c7b.png)

* 左側のdata: 生データを線形のカラーバで表示
* 中央のdata with logscale: 生データを対数スケールのカラーバで表示
* 右側のlog_norm_data: 生データを対数で正規化した後、線形のカラーバで表示

中央と右側の図で、同じ色情報で表示できているのがわかります。

## 簡易的な実装方法
matplotlibのLogNormの__call__()メソッドの[ソースコード](https://matplotlib.org/stable/api/_as_gen/matplotlib.colors.LogNorm.html#matplotlib.colors.LogNorm.__call__)の本質的な部分だけ抜き出して簡易的に実装すると以下のようになります。

```python:
def my_log_norm(data, vmin, vmax): 
    clip_data = np.clip(data, vmin, vmax)
    log_data = np.log(clip_data)
    log_norm_data = (log_data-log_data.min()) / (log_data.max()-log_data.min())
    return log_norm_data
```
コードの意味は、まず、``np.clip()``で``vmin``, ``vmax``の外側の値をその値に置換します。
そして、あとは``np.log()``でlogスケール化し、[min-max normalization](https://en.wikipedia.org/wiki/Feature_scaling)のコードを書くだけです。
[matplotlib公式のlogスケールの説明](https://matplotlib.org/stable/tutorials/colors/colormapnorms.html#logarithmic)ではlogの底を10としていますが、min-max normalizationで底が打ち消されるので実際は何でも良いと思います。(``my_log_norm()``では、底はeとしています。)
※matplotlibのLogNormでは、もう少し高級なコードになっているので、基本的にこの関数(``my_log_norm()``)は使わない方が無難です。

<details><summary>my_log_norm()関数の動作確認</summary><div>

```python
data = np.array([[1, 10] ,[100, 1000]])
cbar_min, cbar_max = (1, 100)

# インスタンス生成
log_norm = LogNorm(vmin=cbar_min, vmax=cbar_max)

# log正規化(__call__()メソッドを呼ぶ)
log_norm_data = log_norm(data, clip=True)

# 自作のlognorm
my_log_norm_data = my_log_norm(data, vmin=cbar_min, vmax=cbar_max)

fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(9, 4))

im0 = ax0.imshow(log_norm_data)
ax0.set_title('log_norm_data')
cbar0 = fig.colorbar(im0, ax=ax0, shrink=0.9)

im1 = ax1.imshow(my_log_norm_data)
ax1.set_title('my_log_norm_data')
cbar1 = fig.colorbar(im1, ax=ax1, shrink=0.9)

plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/65469a75-fa7f-e92d-0366-84feaba83887.png)

</div></details>

# まとめ
matplotlibのlogスケールの色を値として保持する方法を紹介しました。対数スケールでの正規化は、値の範囲が大きい画像などに便利なので、ぜひご活用していただけると幸いです。

# 参考資料
https://matplotlib.org/stable/api/_as_gen/matplotlib.colors.LogNorm.html

https://matplotlib.org/stable/tutorials/colors/colormapnorms.html

https://en.wikipedia.org/wiki/Feature_scaling