N行N列のnumpy arrayを扱う際に気をつけるべき方向と座標の定義

[matplotlibを使って二次元検出器の二次元強度マップを作る](https://qiita.com/skotaro/items/d1c02681fef4783838e3)に引き続き、二次元検出器のデータ解析を念頭に置いた話ですが、NxNの2D numpy arrayを扱う際に気をつけるべき点とも言えるのでタイトルを少し一般的な表現にしました。

# パッケージ
```python
import numpy as np
import matplotlib.pyplot as plt
import pyFAI

# 軸の目盛り位置とラベル表示の調整用
from matplotlib.ticker import MultipleLocator, AutoMinorLocator
from matplotlib.ticker import StrMethodFormatter
```
[pyFAI](https://pypi.python.org/pypi/pyFAI)はEUの放射光施設である[ESRFのソフトウェアチームが開発] (http://www.esrf.eu/UsersAndScience/Publications/Highlights/2012/et/et3/)した極座標ベースのデータ抽出に使えるパッケージです。詳細は二次元データの一次元化に関する[別エントリ](https://qiita.com/skotaro/items/7c68012f85312332df8d)を参照してください。このエントリでは解析の際に座標の定義に気をつけるべき例に使います。condaのconda-forgeチャンネルから以下でインストールできます。

```
conda install -c conda-forge pyfai
```
#方向に関する注意点
## 二次元検出器の測定データにまつわる様々な方向
物質科学で使われる二次元検出器から得られるのは、たいていの場合はデータの形(`np.ndarray.shape`)からは方向の区別がつかない正方形の二次元データです。強度分布がビームの中心に対して点対称な等方的データならば方向のことはあまり気にする必要はありませんが、角度依存性のある異方的データの場合は次のようなそれぞれ意味の異なる方向について考慮する必要があります。

 1. 試料、磁場や電場などの外場と検出器の物理的方向関係
 1. 検出器の物理的な方向と出力された二次元データの方向の関係
 1. 2D numpy arrayの中身を表示した際の行と列
 1. 2次元データを可視化する`contourf`, `imshow`, `pcolormesh`などにおける原点と正方向の定義
 1. 議論や発表の際に使う慣習的なx軸とy軸（または横軸と縦軸）
 
1と2はそれぞれの場合について実験者が自分で確認するしかないので、3と4及び両者のややこしい関係について例を使って説明して5の慣習的な表現とどう合わせていくか述べます[^2]。

## 1D arrayと2D arrayの見た目とインデックスの関係を混同しない

 - 1D arrayのインデックス`[i]`は0始まりインデックスで`i`番目の要素を指す。
 - 2D arrayのインデックス`[i,j]`は0始まりインデックスで`i`行`j`列の要素を指す。

この二つの説明には特にわかりにくい部分はないと思いますが、jupyter notebook上で1D arrayと2D arrayを表示させて中身を確認しながら作業していると2D arrayのインデックスの順番をたまに間違えることがあります。以下に示す通り、1D arrayの`data[i]`は中身を表示させた際の __水平方向の`i`番目__ である一方で、2D arrayの`data[i,j]`は中身を表示させた際の __垂直方向(=行)の`i`番目__ 、__水平方向(=列)の`j`番目__ だからです。

```python:input
# 1D arrayのインデックス[i]はprint時の水平方向のi番目
data = np.zeros(5)
data[2] = 2
print(data)
```

```:output
[ 0.  0.  2.  0.  0.]
```

```python:input
# 2D arrayのインデックス[i,j]はprint時の垂直方向のi行目、水平方向のj列目
data = np.zeros([10,10])
data[1] = 1
data[:,2] = 2
data[8,6] = 3 # 左上を[0,0]として8行目6列目の要素が3
print(data)
```
```:output
[[ 0.  0.  2.  0.  0.  0.  0.  0.  0.  0.]
 [ 1.  1.  2.  1.  1.  1.  1.  1.  1.  1.]
 [ 0.  0.  2.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  2.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  2.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  2.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  2.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  2.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  2.  0.  0.  0.  3.  0.  0.  0.]
 [ 0.  0.  2.  0.  0.  0.  0.  0.  0.  0.]]
```

```python:input
print(data[1]) # 行のみの指定
print(data[:,2]) # 列のみの指定
```

```:output
[ 1.  1.  2.  1.  1.  1.  1.  1.  1.  1.]
[ 2.  2.  2.  2.  2.  2.  2.  2.  2.  2.]
```

例えば、ここで詳しくは述べませんが、私はよく`X, Y = np.meshgrid(...)`の`X`と`Y`が`i`と`j`に対応してるという勘違いをしていました。どうも意図した挙動と違うなと気づいても`Y, X = np.meshgrid(...)`と書き換えて余計ことをややこしくしていました。

## インデックス`[i,j]`と可視化時の(x,y)座標を混同しない

2D numpy arrayのインデックスは数学の行列と同じ定義なので、`data[0,0]`は中身を表示させた時の左上にあり、`[i,j]`の`i`の正方向は下で`j`の正方向は右です。ところで、`data`の可視化の際は慣習的なx軸とy軸の正方向と`i`と`j`の正方向を合わせるために、`data[0,0]`が左下にくるように設定するのがよいでしょう。このとき（転置をしない限り[^1]）、 ___`data[i,j]`のxy座標上の位置は`(j,i)`___ となります。これは、たとえば強度分布の重心を求める計算で出てできたインデックスベースの重心座標`[i0, j0]`を原点を左下にした2D arrayの上に重ねて表示して位置を確認したい時に、重心のxy座標を`(j0, i0)`としなければならないことを意味します。

<img width="600" alt="スクリーンショット 2017-12-16 0.52.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/222849/ba4d56f1-5552-f127-6891-8c880d31e922.png">


## `contourf`,`pcolormesh`,`imshow`における原点と方向の定義
以前の記事の[imshowかpcolormeshか](https://qiita.com/skotaro/items/d1c02681fef4783838e3#imshow%E3%81%8Bpcolormesh%E3%81%8B) で書いたように一番速い`imshow`がおすすめなのですが、せっかくなので`imshow`だけでなく他の二次元データ可視化関数のデフォルト設定で方向や原点の定義がどうなっているかも比較してみます。

```python
fig, ax = plt.subplots(1,4, figsize=(20, 5))
plt.suptitle('contourf, pcolormesh, and imshow')
ax[0].contourf(data)
ax[0].set(aspect='equal', title='contourf(data)')
ax[1].pcolormesh(data)
ax[1].set(aspect='equal', title='pcolormesh(data)')
ax[2].imshow(data)
ax[2].set_title('imshow(data)')
ax[3].imshow(data, origin='lower')
ax[3].set_title("imshow(data, origin='lower')")
```
![download.png](https://qiita-image-store.s3.amazonaws.com/0/222849/698701c8-d1a6-7798-36f1-5c9386e19fb7.png)

`imshow`を使って`data[0,0]`を慣習的な原点の位置である左下に設定するには`origin='lower'`オプションを使えばよいことがわかります。

# 座標に関する注意点
## `imshow`におけるピクセル座標の定義と`extent`によるピクセル座標ベースの拡大
私の[実験屋のためのPythonシリーズ](https://qiita.com/tags/%E5%AE%9F%E9%A8%93%E5%B1%8B%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AEPython)では二次元検出器のデータの可視化に描画処理の速い`imshow`を一貫して推奨していますが、`imshow`で測定データを拡大して詳しく調べたいときは、`imshow`のピクセル座標の定義と`extent`オプションの挙動について注意する必要があります。ここをきちんと押さえておかないと、拡大して詳しく調べたがゆえに解釈を間違ってしまう可能性もあります。

真ん中に十字のある10x10と128x128のテストデータで説明します。

```python
data128 = np.zeros((128,128))
data10 = np.zeros((10,10))
data128[63:65] = [i for i in range(64)] + [i for i in range(63,-1, -1)]
data128[:, 63:65] = [[i] for i in range(64)] + [[i] for i in range(63, -1, -1)]
data10[4:6]=[i for i in range(5)] + [i for i in range(4, -1, -1)]
data10[:,4:6]=[[i] for i in range(5)] + `[[i] for i in range(4, -1, -1)]

# 中心部を拡大して表示します
print(data128[60:68, 60:68])
print(data10)
```

```
[[  0.   0.   0.  60.  60.   0.   0.   0.]
 [  0.   0.   0.  61.  61.   0.   0.   0.]
 [  0.   0.   0.  62.  62.   0.   0.   0.]
 [ 60.  61.  62.  63.  63.  62.  61.  60.]
 [ 60.  61.  62.  63.  63.  62.  61.  60.]
 [  0.   0.   0.  62.  62.   0.   0.   0.]
 [  0.   0.   0.  61.  61.   0.   0.   0.]
 [  0.   0.   0.  60.  60.   0.   0.   0.]]
[[ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  1.  1.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  2.  2.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  3.  3.  0.  0.  0.  0.]
 [ 0.  1.  2.  3.  4.  4.  3.  2.  1.  0.]
 [ 0.  1.  2.  3.  4.  4.  3.  2.  1.  0.]
 [ 0.  0.  0.  0.  3.  3.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  2.  2.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  1.  1.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]]
```

`data10`と`data128`の強度分布（十字）の中心はインデックスベースの座標で表現するとそれぞれ`[63.5 ,63.5]`とです。座標の定義と`extent`の挙動の説明のために中心近傍の点を含めて以下のようなリストを用意します。

```python
cen128 = [(63, 63), (63.5, 63.5), (64, 64)]
cen10 = [(4, 4), (4.5, 4.5), (5, 5)]
```

拡大と`extent`オプションの有無を変えて四通りの図を作ると`imshow`の座標の定義と`extent`の正しい設定の仕方がわかります。

```python
fig, axes = plt.subplots(1,4, figsize=(16,4))

axes[0].imshow(data10, origin='lower')
axes[1].imshow(data10[0:10, 0:10], origin='lower', extent=[0, 10, 0, 10])
axes[2].imshow(data10[2:8, 2:8], origin='lower')
axes[3].imshow(data10[2:8, 2:8], origin='lower', extent=[1.5, 7.5, 1.5, 7.5])

titles= ['1. data10\nno extent\ncen = {}', 
         '2. data10[0:10, 0:10](=data10)\nextent=[0, 10, 0, 10]\ncen = {}',
         '3. data10[2:8, 2:8]\nno extent\ncen = {}',
         '4. data10[2:8, 2:8]\nextent=[1.5,7.5,1.5,7.5]\ncen = {}']

for ax, title in zip(axes, titles):
    ax.grid(True, alpha=0.6)
    ax.xaxis.set_major_locator(MultipleLocator(1))
    ax.yaxis.set_major_locator(MultipleLocator(1))
    ax.set_title(title.format(cen10))
    ax.scatter(np.array(cen10)[:, 0], np.array(cen10)[:, 1])
```

![download-11.png](https://qiita-image-store.s3.amazonaws.com/0/222849/ff53a32e-b39a-944e-23eb-791684edcfe2.png)


四つの図からそれぞれ以下のことがわかります。

 1. __全データ表示、`extent`なし__
  - `imshow`のピクセル座標はピクセルの中央である
 2. __全データ表示、`extent`あり（表示範囲のインデックスをそのまま指定するありがちな間違い）__
  - `extent`オプションは図の両端の値を設定
  - `ax.scatter`は`extent`オプションで調整された軸の値を使う
 3. __拡大あり、`extent`なし__
  - `extent`を指定しないと、軸は常に0始まりインデックス
 4. __拡大あり、`extent`あり（推奨）__
  - slicingに使っているインデックスより0.5小さい値を`extent`に指定すると1を正しく拡大した図になる

重心位置の確認などをするために上図2のように`extent`を指定して拡大すると、`imshow`のインデックスベースでプロットしたつもりの点が縦横座標ともに0.5ずれて表示されてしまいます。これはもっと大きなデータを扱っている時に勘違いの原因になります。先に定義した`data128`を使って見てみます。

```python
fig, axes = plt.subplots(1, 3, figsize=(12,4))

axes[0].imshow(data128, origin='lower')
axes[1].imshow(data128[60:68, 60:68], origin='lower', extent=[60, 68, 60, 68])
axes[2].imshow(data128[60:68, 60:68], origin='lower', extent=[59.5, 67.5, 59.5, 67.5])
titles = ['(a) data\nno extent\ncen = {}',
          '(b) data[60:68, 60:68]\nextent=[60, 68, 60, 68]\ncen = {}',
          '(c) data[60:68, 60:68]\nextent=[59.5, 67.5, 59.5, 67.5]\ncen = {}']

for ax, title in zip(axes, titles):
    if not '(a)' in title:
        ax.xaxis.set_major_locator(MultipleLocator(1))
        ax.yaxis.set_major_locator(MultipleLocator(1))
        ax.grid(True, alpha=0.6)
        
    ax.set_title(title.format(cen128))
    ax.scatter(np.array(cen128)[:, 0], np.array(cen128)[:, 1])
```
![download-5.png](https://qiita-image-store.s3.amazonaws.com/0/222849/a8c59bd9-fe4c-c930-acaf-4f14d8c1a198.png)

十字テストデータの中心部分を`print`して確認した通り、`data128`のインデックスベースの重心座標は`[63.5, 63.5]`です。ところが拡大につかったインデックスと同じ値を`extent`に設定してしまうよくある間違いをしている上図(b)では`[64, 64]`が中心であるかに見えてしまいます。拡大をしていない(a)では中心付近に点を打っても正確な位置は当然わからないので、実際の測定データを扱っていると上図(b)がおかしいことにはなかなか気づきません。

### 拡大範囲と`extent`の順番に注意
上の例では横軸縦軸ともにおなじインデックス範囲を拡大しているので`extent`の数字の並び順を気にする必要はありませんでしたが、`extent`の指定でも前述したインデックスベースの座標`[i,j]`と慣習的なxy座標での`(x,y)`の混同が起きやすいです。

```python
# iminからimax-1、jminからjmax-1までの範囲を拡大したい場合
# 間違い
plt.imshow(data[imin:imax, jmin:jmax], extent=[imin-0.5, imax-0.5, jmin-0.5, jmax-0.5])
# 正しい
plt.imshow(data[imin:imax, jmin:jmax], extent=[jmin-0.5, jmax-0.5, imin-0.5, imax-0.5])
```

```python
# data128による具体例
fig, axes = plt.subplots(1, 2, figsize=(8,4))
axes[0].imshow(data128[60:68, 58:66], origin='lower', extent=[59.5, 67.5, 57.5, 65.5])
axes[1].imshow(data128[60:68, 58:66], origin='lower', extent=[57.5, 65.5, 59.5, 67.5])
titles = ['(a) data[60:68, 58:66]\nextent=[59.5, 67.5, 57.5, 65.5]\ncen = {}',
          '(b) data[60:68, 58:66]\nextent=[57.5, 65.5, 59.5, 67.5]\ncen = {}']

for ax, title in zip(axes, titles):
    ax.xaxis.set_major_locator(MultipleLocator(1))
    ax.yaxis.set_major_locator(MultipleLocator(1))
    ax.grid(True, alpha=0.6)
    ax.set_title(title.format(cen128))
    ax.scatter(np.array(cen128)[:, 0], np.array(cen128)[:, 1])
```
![download-6.png](https://qiita-image-store.s3.amazonaws.com/0/222849/df3aef72-75bc-148f-a5f7-761b50745ad4.png)

上の図では中心部にあるはずの三点の位置が違うので何かおかしいとすぐに気づきますが、軸の値をきちんと確認しないと見過ごしやすい間違いです。

## `extent`を使ったピクセル座標から物理量への変換
`extent`はピクセル座標がピクセル中心にくるように拡大表示するときだけでなく、縦軸横軸の数字を0始まり整数のピクセル座標から物理量に変換するためにも使えます[^3]。ただし、ここでも`extent`の指定の仕方に注意が必要です。

```python
# ピクセルの位置をはっきりさせるため10x10の乱数を作る
data10_rand = np.random.rand(10, 10)

# 各ピクセルの位置と対応する仮の物理量。二次元検出器の場合は角度や散乱ベクトルの値を使う。
x = np.linspace(-0.3, 0.5, 10)
y = np.linspace(-0.2, 0.6, 10)
dx = x[1]-x[0] # ピクセル間の差分
dy = y[1]-y[0] # dxと同じ値だが明示的に計算しておく
print('x:', x)
print('y:', y)

fig, axes = plt.subplots(1, 4, figsize=(16,4))
fig.subplots_adjust(wspace=0.3)

axes[0].imshow(data10_rand, origin='lower')

extent = [min(x), max(x), min(y), max(y)]
axes[1].imshow(data10_rand, origin='lower', extent=extent)
extent = [min(x)-dx/2, max(x)+dx/2, min(y)-dy/2, max(y)+dy/2]
axes[2].imshow(data10_rand, origin='lower', extent=extent)
axes[3].imshow(data10_rand, origin='lower', extent=extent)

titles = ['(a) data10\nno extent',
          '(b) data10\n[min(x), max(x), min(y), max(y)]',
          '(c) data10\n[min(x)-dx/2, max(x)+dx/2,\n min(y)-dy/2, max(y)+dy/2]',
          '(d) data10\n[min(x)-dx/2, max(x)+dx/2,\n min(y)-dy/2, max(y)+dy/2]']

for ax, title in zip(axes, titles):
    ax.set_title(title, y=1.04)
    ax.grid(True, which='both', alpha=0.6)
    
    if '(a)' in title:
        ax.xaxis.set_major_locator(MultipleLocator(1))
        ax.yaxis.set_major_locator(MultipleLocator(1))

    else:
        ax.set(xticks=x[::2], yticks=y[::2])
        ax.xaxis.set_major_formatter(StrMethodFormatter('{x:.3f}'))
        ax.xaxis.set_minor_locator(AutoMinorLocator(2))
        ax.yaxis.set_major_formatter(StrMethodFormatter('{x:.3f}'))
        ax.yaxis.set_minor_locator(AutoMinorLocator(2))
        
        # extentを使うと拡大表示にxとyの値がそのまま使える
        if '(d)' in title:
            ax.set(xlim=(-0.1, 0.3), ylim=(-0.05, 0.35))
```
```:output
x: [-0.3        -0.21111111 -0.12222222 -0.03333333  0.05555556  0.14444444
  0.23333333  0.32222222  0.41111111  0.5       ]
y: [-0.2        -0.11111111 -0.02222222  0.06666667  0.15555556  0.24444444
  0.33333333  0.42222222  0.51111111  0.6       ]
```
![download-5.png](https://qiita-image-store.s3.amazonaws.com/0/222849/00775306-cf08-f1d2-a59d-458a34f75f30.png)

`extent`を指定していない(a)ではこれまで説明した通り0始まり整数のピクセル座標を示すtick（目盛り）が各ピクセルの中心を表すように配置されています。(b)では`extent`をつかって物理量（ここではサンプルコード中の`x`と`y`）に変換できているように見えますが、実はこれはよくありがちな間違った`extent`の使い方の例です。グリッドの交点を見ると、ピクセル位置に対応しているつもりで設定したtickがピクセルの中心を通っていないことがわかります。一方、`extent`を正しく使っている(c)では意図した通りにピクセル中央にピクセル位置と対応した`x`と`y`の値を示すtickが配置されています。`extent=[left, right, bottom, top]`が__左右上下端のピクセルの座標ではなく表示領域の左右上下端の数字を指定するオプション__であることを考えるとこの違いは当然なのですが、実際のデータは上で使った乱数のようにピクセルの区別がつきやすい見た目をしているわけではなく、さらにわざわざピクセル位置にtickを置いたりグリッドを表示したりしないためなかなか間違いに気づきません[^4]。

ピクセル座標を物理量に変換するという目的を達成するには、実は「座標スケールは0始まり整数のままで`FuncFormatter`を使って表示する数字(tick label)だけを無理やり物理量を表す数字にしてしまう」という方法も使えます。ただし、この方法では軸のスケール自体は0始まり整数のままなので`ax.set_xlim`やデータのslicingを使って拡大表示したい時に常に整数のピクセル座標を使う必要が生じます。実際の作業を想像すればこれがあまり便利でないことはわかるでしょう。x軸の物理量が0.1から0.25に相当する部分を拡大するには何番目のピクセルを指定したらいいか調べないといけない方法を好む人なら別ですが。(d)に示した通り`extent`を正しく指定すれば、拡大表示に`x`や`y`の物理量をそのまま指定できます。

## `pyFAI`で一次元化する際も座標の定義に注意
二次元検出器の測定データの解析では、ある方向の強度のみを切り出す一次元化という処理を行うことが多いです。この際、ビームセンターと呼ばれる強度による重み付き重心を原点とした極座標を設定して一次元化を行います。`pyFAI`では、これまで述べてきたピクセル中央を基準にした座標ではなくピクセル左下を基準にした座標を使っているので、2D arrayのインデックスベースで算出したビームセンターの値をそのまま`pyFAI`に渡すと誤った結果になってしまいます。これは下の例のように、2D arrayのインデックスベースの重心(`[63.5, 63.5]`)に0.5を足した値(`[64, 64]`)を`pyFAI`に渡すことで解決します。

```python
# 一次元化に使うパラメータの例
pixel, wv, dist = 0.0075, 1.2, 18 # in m, nm, m
# 一次元化する方向と角度の範囲
az, daz = 0, 15
# 一次元化に必要なパラメータの設定
ai = pyFAI.AzimuthalIntegrator(dist=dist, pixel1=pixel, pixel2=pixel, wavelength=wv*1e-9)
# 動径方向、角度方向のbinning個数
rbin, azbin = 60, 360

fig, ax = plt.subplots(1, 3, figsize=(12, 4))

fig.suptitle('The definition of pixel positions in pyFAI')

for i, bc in enumerate(cen128):
    ai.setFit2D(dist*1000, *bc) # distance in milimeter
    q, I = ai.integrate1d(data128, rbin, azimuth_range=(az-daz, az+daz))
    q1, I1 = ai.integrate1d(data128, rbin, azimuth_range=(az+180-daz, az+180+daz))
    ax[i].set_title('bc set to '+str(bc))
    ax[i].set_xscale('log')
    ax[i].set_xlabel('q (nm$^{-1}$)')
    ax[i].tick_params(axis='x', which='minor')
    ax[i].plot(q, I, 'o', label=str(az)+'$\pm$'+str(daz))
    ax[i].plot(q1, I1, '.', label=str(az+180)+'$\pm$'+str(daz))
    ax[i].legend()
```

![download-3.png](https://qiita-image-store.s3.amazonaws.com/0/222849/59116992-879b-154d-daa5-f789b0b2383d.png)

ここでは以下の図の二つの白い部分の強度をそれぞれの範囲の中で合計して動径の関数としてプロットしています。`pyFAI`に正しいビームセンターが設定されている場合は左右の白い部分は同じ強度を持つはずなので二つのプロットは一致するはずで、確かに`[64, 64]`のときに一致しているのがわかります。

<img width="232" alt="スクリーンショット 2017-12-17 14.20.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/222849/b27ead91-f178-9d92-c64e-38b6b9cbe4f6.png">


`data128`を`imshow`で拡大した図を使って説明すると

 - 2D arrayのインデックスベースの座標で計算した重心`[63.5, 63.5]`はcで正しい位置に表示される
 - `pyFAI`はbのようにピクセル左下を基準にした座標で採用しているので、2D arrayインデックスベース座標の重心`[63.5, 63.5]`を0.5ずつずらした`[64, 64]`が重心になる

とも言えます。

[^1]: 転置をしてから[0,0]が左下にくるように表示すれば転置前のdata[i,j]が(x,y)座標表記で(i,j)にくるようにはできますが、転置をする方式を採用すると、転置を忘れた際に何が何だか分からなくなるので私はあまりオススメしません。
[^2]: 実際に間違ってみない限り、このように言葉で説明されてもピンとこないと思いますが、一度読んでおくと何かおかしいことに気づいた時の解決の助けになると思います。
[^3]: `imshow`は画像データのように等間隔に並んだ数字の可視化に使います。したがって、ここで縦軸と横軸に割り当てる物理量はピクセル座標と線形関係にあるものを想定しています。
[^4]: 実際のデータは最低でも128x128ピクセルはあるでしょうから、データ全体を表示している時は気づかない程度の差しかないとも言えますが、`ax.set_xlim`などで拡大すると意図した領域から少しずれた部分を拡大することになります。