Matplotlibでimshow画像の軸目盛りの調整：extentとsecondary_axisの活用

# はじめに

[Matplotlib](https://matplotlib.org/)を使用して画像を表示する際、軸の目盛りの調整や2軸を使いたい場合があります。本記事では、`imshow`関数の引数である[`extent`](https://matplotlib.org/stable/users/explain/artists/imshow_extent.html)を使用して軸の目盛りを調整する方法を紹介します。

そして、この`extent`の方法を知る前まで筆者が愛用していた[Secondary Axis](https://matplotlib.org/stable/gallery/subplots_axes_and_figures/secondary_axis.html)での実装コードも紹介します。この方法は少し複雑ですが、**`extent`と同様の指定方法で`imshow`で2軸を表示したい場合**に活用していただければ幸いです。

以下が、本記事の概要のイメージです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/b6dbf9d9-92d0-376f-215d-8e268913008b.png)

本記事で使用したコードはGoogle Colabの[こちら](https://colab.research.google.com/drive/1rscMjp_bAvSreIYZmDqc9-JCppl-YSrv?usp=sharing)に載せています。

# デフォルトの軸目盛り
まずは軸ラベルの設定をしなかった場合の結果について一応見ておきます。

<details><summary>軸ラベルの設定をしなかった場合のコード</summary><div>

```python
# デフォルトの目盛り
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(2023)
img = np.random.rand(8,12)

fig, ax = plt.subplots()
ax.imshow(img)
ax.set_title('Original ticks')
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()
```

</div></details>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/87fe9bfc-af58-1594-c514-cf01ff717265.png)

各軸の座標が`0`スタートのお馴染みの目盛り表示になります。

# extentを使った軸目盛りの設定方法

`imshow`関数の`extent`引数を使うことで、任意の軸目盛を設定できます。
`plt.imshow(image, extent=(left, right, bottom, top))`のように使います。ここで、`extent`の引数は以下のようになります。

- left: X軸の最小値
- right: X軸の最大値
- bottom: Y軸の最小値
- top: Y軸の最大値

実際に先の例で、`extent=(-4, 4, -1, 1)`として表示します。

```python
# extentを使った軸目盛りの設定例
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(2023)
img = np.random.rand(8,12)
extent = (-4, 4, -1, 1)

fig, ax = plt.subplots()
ax.imshow(img, extent=extent, aspect='auto')
ax.set_title('Using extent')
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/e7990eb2-0a53-1b03-947d-8249c37ea293.png)

`extent`に指定した通りの目盛り表示になります。
`extent`の`aspect`の見た目の違いは、[Stack Overflow imshow: extent and aspect](https://stackoverflow.com/questions/13384653/imshow-extent-and-aspect)が参考になります。

# Secondary Axisを使った軸目盛りの設定方法

こちらの方法は、`extent`の引数のままに**2軸を表示したい場合**には活用できますが、それ以外の場合は`extent`を使うことをおすすめします。

## 1軸表示
まずは、[`extent`を使った軸目盛りの設定方法](#extentを使った軸目盛りの設定方法)と同じ結果が得られることを確認します。

```python
# secondary axisを使った軸目盛りの設定例
import numpy as np
import matplotlib.pyplot as plt

def secondary_axis(img, ax, extent, location_x, location_y):
    """
    imshowの軸に対して、与えられた画像に対する2次のx軸とy軸を作成する関数

    パラメータ:
    img (ndarray): 表示する画像
    ax (matplotlib.axes._subplots.AxesSubplot): 2次の軸を追加するMatplotlib軸
    extent (tuple): 画像の範囲 (left, right, bottom, top)
    location_x (str): 2次のx軸の位置 ('bottom', 'top')
    location_y (str): 2次のy軸の位置 ('left', 'right')

    戻り値:
    ax: 2次の軸が追加された元の軸
    secax_x: 2次のx軸
    secax_y: 2次のy軸
    """
    X_step = (extent[1]-extent[0]) / (img.shape[1])
    X_start = extent[0]+0.5*X_step
    Y_step = (extent[2]-extent[3]) / (img.shape[0])
    Y_start = extent[3]+0.5*Y_step
    secax_x = ax.secondary_xaxis(location_x,
                                 functions=(lambda x: x*X_step + X_start, lambda x: (x - X_start) / X_step))
    secax_y = ax.secondary_yaxis(location_y,
                                 functions=(lambda y: y*Y_step + Y_start, lambda y: (y - Y_start) / Y_step))
    return ax, secax_x, secax_y


np.random.seed(2023)
img = np.random.rand(8,12)
extent = (-4, 4, -1, 1)

fig, ax = plt.subplots()
ax.imshow(img, aspect='auto')
ax.set_title('Using secondary_axis')
ax.tick_params(labelbottom=False, labelleft=False, bottom=False, left=False)
ax, secax_x, secax_y = secondary_axis(img, ax, extent, 'bottom', 'left')
secax_x.set_xlabel('x')
secax_y.set_ylabel('y')
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/f684c4ae-bc80-7ba6-80dc-d0fcafff47b7.png)

同じものが得られました。

簡単にコートの説明をします。
1. `extent`の引数指定と同じような見た目になるように頑張って`secondary_axis(img, ax, extent, location_x, location_y)`関数を作ります。具体的には、変換前後の軸目盛り関数を作ります。詳しくは、公式の[Secondary Axis](https://matplotlib.org/stable/gallery/subplots_axes_and_figures/secondary_axis.html)をご覧ください。
1. 今のケースでは1軸目は不要なので、`ax.tick_params()`で消します。
1. 2軸目のオブジェクト情報を`secax_x`, `secax_y`で受け取り軸ラベルなどを書く場合は、`secax_x.set_xlabel('x')`のように書きます。

## 2軸表示
1軸表示の例とほとんど同じです。1軸表示との違いは、1軸目を削除するか、2軸目の配置場所くらいです。

<details><summary>2軸表示のサンプルコード</summary><div>

```python:secondary axisを使った2軸目盛りの設定例
# secondary axisを使った2軸目盛りの設定例
import numpy as np
import matplotlib.pyplot as plt

def secondary_axis(img, ax, extent, location_x, location_y):
    """
    imshowの軸に対して、与えられた画像に対する2次のx軸とy軸を作成する関数

    パラメータ:
    img (ndarray): 表示する画像
    ax (matplotlib.axes._subplots.AxesSubplot): 2次の軸を追加するMatplotlib軸
    extent (tuple): 画像の範囲 (left, right, bottom, top)
    location_x (str): 2次のx軸の位置 ('bottom', 'top')
    location_y (str): 2次のy軸の位置 ('left', 'right')

    戻り値:
    ax: 2次の軸が追加された元の軸
    secax_x: 2次のx軸
    secax_y: 2次のy軸
    """
    X_step = (extent[1]-extent[0]) / (img.shape[1])
    X_start = extent[0]+0.5*X_step
    Y_step = (extent[2]-extent[3]) / (img.shape[0])
    Y_start = extent[3]+0.5*Y_step
    secax_x = ax.secondary_xaxis(location_x,
                                 functions=(lambda x: x*X_step + X_start, lambda x: (x - X_start) / X_step))
    secax_y = ax.secondary_yaxis(location_y,
                                 functions=(lambda y: y*Y_step + Y_start, lambda y: (y - Y_start) / Y_step))
    return ax, secax_x, secax_y


np.random.seed(2023)
img = np.random.rand(8,12)
extent = (-4, 4, -1, 1)

fig, ax = plt.subplots()
ax.imshow(img, aspect='auto')
ax.set_title('Using secondary_axis 2 axis show')
ax.set_xlabel('x')
ax.set_ylabel('x')
# 2軸目
ax, secax_x, secax_y = secondary_axis(img, ax, extent, 'top', 'right')
secax_x.set_xlabel('x second')
secax_y.set_ylabel('y second')
plt.show()
```

</div></details>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/637ce85c-c323-d25f-265a-704f212f65c9.png)

2軸表示できているのがわかります。このように、`imshow`関数の`extent`引数の指定方法と同じように2軸が表示できます。

# まとめ

本記事では、Matplotlibを使用して画像を表示する際に、軸の目盛りを調整する方法について説明しました。以下はまとめのポイントです。

- extentを使用した軸目盛の設定:

    - imshow関数のextent引数を使用することで、軸の目盛りを自由に設定できます。
    - extentを使うことで、画像データの各ピクセルが指定された範囲内にどのようにマッピングされるかを制御できます。

- Secondary Axisを使用した軸目盛りの設定:
    - secondary_xaxisとsecondary_yaxisを使用する方法を紹介しました。
    - この方法は、extentの代替方法として活用できますが、やや複雑であり、2つの軸を組み合わせたい場合に有用です。


本記事が、どなたかの役に立てば幸いです。