Spectral Python データ表示

#Spectral Python
[Spectral Python(SPy)](https://github.com/spectralpython/spectral)とは純度100%のpythonのモジュールで、ハイパースペクトル(HS)データの処理ができる。HSデータの読み込み、表示、計算そして分類まで一括して行える。[MITライセンス](https://mit-license.org/)のため、自由に使うことができる。前回はライブラリの導入とデータの読み込みに関して述べた。本稿ではデータの表示に関して記す。翻訳に関しては許諾を得ている。

#Displaying Data
##Starting IPython
SPyは双方向的なpythonインタープリターを妨げることなくGUI画面を利用するためにIPythonを使用する。この機能を利用するためにIPythonは「pylab」モードで起動されなければならない。すでにmatplotlibrcファイルに「WX」か「WXAgg」のいずれかがmatplotlib backendとしてセットされているなら、IPythonは下記のように起動することが推奨される。

>ipython --pylab

またIPythonでバックエンドを明示的に指定することもできる。

>ipython --pylab=wx

**Note:**GUIの機能を利用しないのであれば（`save_rgb`関数はGUIの機能としてカウントされない）、IPythonを走らせる必要はないため、通常のpythonインタープリターを利用できる。
**Note:**使用しているシステム上でWXバックエンドが利用不可な場合、別のバックエンド（Qt4Agg, TkAggなど）が使える。ただしその場合、`view_cube`, `View_nd`関数が使用できなくなる。

##Raster Displays
SPyの`imshow`関数はmatplotlibに存在する同名関数のラッパーである。SPy版の関数では、マルチスペクトルやハイパースペクトルバンドや分類マップをより簡単に表示することができ、双方向を高める機能に対応している。

##Image Data Display
`imshow`関数はnp.ndarrayやSpyFileオブジェクトのデータからラスタ画像を生成する。

>
In [1]: from spectral import *
>
In [2]: img = open_image('92AV3C.lan')
>
In [3]: view = imshow(img, (29, 19, 9))

![displayed img](http://www.spectralpython.net/_images/imshow_92AV3C_rgb.png)

画像をインタラクティブに表示する際には、matplotlib button controlsが大きさの変更に使われる。「Z」を押すとズームウィンドウが開くため、そこから画像の拡大図を見れる。またコントロールキーを押しながら、左クリックをもとのウィンドウ内ですると、クリックした場所へズームウィンドウの画像が移動する。

*version 0.16.0での変更：*デフォルトでは`imshow`関数で表示されるRGBデータは輝度分布が偏りないよう[linear histogram strech](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_histograms/py_histogram_equalization/py_histogram_equalization.html)が適用されている。これによりコントラストがでる。この色伸長（コントラスト伸長？）は`imshow`関数の*strech*、*bounds*、 *strech_all*キーワード引数で調整できる（キーワードの意味は[*get_rgb*](http://www.spectralpython.net/class_func_ref.html#spectral.graphics.graphics.get_rgb)より）。表示画像の色伸長の調整には*ImageView*オブジェクトの`set_rgb_options`メソッドが呼び出される。

*ImageView*オブジェクトの*__str__*メソッドから表示画像のRGBデータの範囲がプリントされる。

>In [4]: print(view)
ImageView object:
  Display bands       :  (29, 19, 9)
  Interpolation       :  <default>
  RGB data limits     :
    R: [2054.0, 6317.0]
    G: [2775.0, 7307.0]
    B: [3560.0, 7928.0]

##Class Map Display
グランドトゥルース（リモートセンシングで得られる画像データから対象物を判読する場合に、その代表的な対象物の実際の地上のデータをいう）を表示する際は、*imshow*関数に引数*classes*をセットする。

>
In [5]: gt = open_image('92AV3GT.GIS').read_band(0)
>
In [6]: view = imshow(classes=gt)

![class map](http://www.spectralpython.net/_images/imshow_92AV3C_gt.png)

表示画像のバンドとクラスの色を入れ替えたり、クラスマップを重ね合わせて表示させることも可能で、`imshow`関数呼び出し時にデータとクラスの値を明示すればよい。

>
In [7]: view = imshow(img, (30, 20, 10), classes=gt)

デフォルトの表示モードでは画像のバンドを表示する。「d」、「c」、「C」（イメージウィンドウがフォーカスされている状態で）を押すことで表示をデータ、クラス、クラスオーバーレイと切り替えられる。表示設定はコードから設定することもできる。以下に一例を示す。透過度がα=0.5のクラスマスクが重畳した状態で表示させる場合は、`imshow`の呼出し後に下記のコマンドを入力する。

>
In [8]: view = imshow(img, (30, 20, 10), classes=gt)
>
In [9]: view.set_display_mode('overlay')
>
In [10]: view.class_alpha = 0.5

![example](http://www.spectralpython.net/_images/imshow_92AV3C_overlay.png)

##Interactive class labeling
`ImageView`ウィンドでは画像内から矩形領域を選択し、新しいクラスIDを入力することでピクセルのクラスを対話的に変更できる。矩形領域への新クラスIDの適用手順を下記に示す。

1. `imshow`関数を呼び出し、*classes*引数に初期配列を与える。この配列は非負値整数配列でなければならない。クラスIDが0の場合、未分類のピクセルであることを示すため完全に未分類の状態から始める場合はすべての要素がゼロの配列を代入しなければならない。
2. シフトキーを押しながら、左クリックすると矩形の左上の頂点として選択される。そのままマウスを動かしドラッグしはなした点が矩形の右下の頂点として選択される。マウスをはなす前にシフトボタンを離すと矩形選択操作がキャンセルされる。
3. `ImageView`ウィンドにフォーカスした状態で、選択領域に割り当てるクラスIDを数字で入力する。このクラスIDは複数のクラスを持つことが可能である。また入力した数字はコマンドラインには表示されない。エンターキーを押すことでクラスIDを代入する。この際に、コマンドラインではこの操作の確認が要求され、再びエンターキーを押すことでクラスIDが適用され、その他のいづれかのキーを押すことで操作のキャンセルができる。

クラスの割り当ては、ImageViewのメインウィンドウ、または関連するズームウィンドウからできる。選択ツールは矩形領域しか作成できないが、クラスIDをすべてのピクセルをカバーする大きな長方形に割り当てた後、小さな長方形をクラス0（または元のクラスID）に再割り当てすることで、長方形ではない領域にクラスを割り当てることができる。

##Additional Capabilities
`ImageView`のキーボード、マウス割り当ては"h"キーを押すと表示される。

>
Mouse Functions:
----------------
ctrl+left-click          ->   pan zoom window to pixel
shift+left-click&drag    ->   select rectangular image region
left-dblclick            ->   plot pixel spectrum

>
Keybinds:
---------
0-9     -> enter class ID for image pixel labeling
ENTER   -> apply specified class ID to selected rectangular region
a/A     -> decrease/increase class overlay alpha value
c       -> set display mode to "classes" (if classes set)
C       -> set display mode to "overlay" (if data and classes set)
d       -> set display mode to "data" (if data set)
h       -> print help message
i       -> toggle pixel interpolation between "nearest" and SPy default.
z       -> open zoom window
>
See matplotlib imshow documentation for addition key binds.

`imshow`関数で設定されている以外の画像表示動作の設定は、直接`ImageView`オブジェクトを作成し、そのオブジェクトの`show`メソッドを呼び出す前にカスタマイズできる。また`imshow`関数によって返される`ImageView`オブジェクトのaxis属性から
matplotlibのfigureとcanvas属性にアクセスできる。そのほか、`SpySettings`オブジェクトを設定することで画像表示のデフォルト動作をカスタマイズできる。

##Saving RGB Image Files
表示画像の保存には`save_rgb`関数を使う。この関数は`imshow`関数と同様の引数を使うが、保存ファイル名が第一引数となる。

>
In [11]: save_rgb('rgb.jpg', img, [29, 19, 9])

インデックスカラー画像の保存もRGB画像と似ているが、`save_rgb`関数では画像が単バンド画像（グレイスケール）かインデックスカラー画像か判別できない。そのためインデックスカラー画像を保存するには、キーワード引数としてカラーパレットを明示的に渡す必要がある。

>
In [12]: save_rgb('gt.jpg', gt, colors=spy_colors)

##Spectrum Plots
画像表示ウィンドはいくつかの機能をもっており、グラフのプロットも可能である。`imshow`関数（または`view`関数）で画像を表示させた後、任意の場所をダブルクリックすると、クリックしたピクセルの地点のスペクトルの2Dプロットが新しいウィンドに表示される。以下に表示プロットの例を示す。

![2d plot](http://www.spectralpython.net/_images/rgb_double_click.png)

ダブルクリックし選択したピクセルの 行/列　はコマンドプロンプトにプリントされる。今回使用しているサンプルイメージファイルにはスペクトルバンドのメタデータがないため、横軸は波長ではなくバンドナンバーとなる。横軸波長のグラフをプロットするには、イメージファイルにスペクトルバンドの情報を紐づけるなければならない。

>
In [13]: import spectral.io.aviris as aviris
>
In [14]: img.bands = aviris.read_aviris_bands('92AV3C.spc')

画像とグラフウィンドウを閉じ、前述のコードを走らせる。再度`view`関数で画像を表示し、数ピクセルを選択する。すると、横軸が各バンドと対応した波長になっていることが分かる。

![x wavelength](http://www.spectralpython.net/_images/rgb_with_bands_double_click.png)

このスペクトルのプロット機能は画像からスペクトルを迅速かつ簡易的にみることを意図している。同じデータから好みのグラフスタイルにしたい場合はSPｙでスペクトルデータを読み込み、matplotlibから直接カスタマイズされたグラフを作成できる。

##Hypercube Display
ハイパースペクトルイメージングのコンテクストにおいては、3Dハイパーキューブとはハイパースペクトル画像の3次元的な表現である。ハイパーキューブではx,y次元は画像中の空間的次元で、3番目の次元がスペクトル次元である。ハイパーキューブの解析的な有効性は議論の余地があるが見た目がかっこいい。

`view_cube`関数を呼び出すと、3Dハイパーキューブを表示する新しいウィンドウが開く。ウィンドウフォーカスを新しく開いたウィンドウに合わせ、キーボードの入力からハイパーキューブの見方を変えることができる。またキーワード引数でキューブ表面の画像やキューブ側面のカラースケールを変更できる。

>
In [15]: view_cube(img, bands=[29, 19, 9])
>
Mouse Functions:
----------------
left-click & drag        ->   Rotate cube
CTRL+left-click & drag   ->   Zoom in/out
SHIFT+left-click & drag  ->  Pan
>
Keybinds:
---------
l       -> toggle light
t/g     -> stretch/compress z-dimension
h       -> print help message
q       -> close window

![hypercube](http://www.spectralpython.net/_images/hypercube.jpg)

**Note:**`view_cube`で空のキャンバスが出力される（キューブが表示されない）場合、ディスプレイアダプターが32-bit depth bufferをサポートしていない可能性がある。その場合、`view_cube`と`view_nd`を利用することで次のコマンドのようにdepth bufferのサイズを小さい値（16とか）に減らすことができる。

>
In [16]: import spectral
In [17]: spectral.settings.WX_GL_DEPTH_SIZE = 16

##N-Dimentional Feature Display
ハイパースペクトル画像はそれぞれのバンドの幅が狭く、隣接しあっているため特に隣り合ったバンド同士での相関が強い。そのため、可視化できる特徴を増やすために、一般的に次元削減を行い、情報密度のより高い特徴群へ変換する(主成分変分析からの変換など)。しかしながら変換後のデータでも3次元以上の特徴を持つためことが常であるため、解析者はどういった観点（スペクトルクラスの分離度など）からデータを強調するかを決め、どの3つの次元が目的を満たすか選ぶ必要がある。また短時間で多くの特徴量の組み合わせを調べるために、表示する特徴量を素早く切り替えれることが望ましい。

ほとんどの場合では、次元削減をデータを表示する前に行うことでより意味のある解釈性の高い特徴の表示が可能となる。（例えば、いくつかの主成分を選択することで）

>
In [18]: data = open_image('92AV3C.lan').load()
>
In [19]: gt = open_image('92AV3GT.GIS').read_band(0)
>
In [20]: pc = principal_components(data)
Covariance.....done
>
In [21]: xdata = pc.transform(data)
>
In [22]: w = view_nd(xdata[:,:,:15], classes=gt)

次のようなNDウィンドウが表示される。

![initial nd](http://www.spectralpython.net/_images/ndwindow_orig.png)

プロンプトにNDウィンドウで受け付けているキーボード、マウスのコマンドが表示される。
>Mouse functions:
---------------
Left-click & drag       -->     Rotate viewing geometry (or pan)
CTRL+Left-click & drag  -->     Zoom viewing geometry
CTRL+SHIFT+Left-click   -->     Print image row/col and class of selected pixel
SHIFT+Left-click & drag -->     Define selection box in the window
Right-click             -->     Open GLUT menu for pixel reassignment
>
Keyboard functions:
-------------------
a       -->     Toggle axis display
c       -->     View dynamic raster image of class values
d       -->     Cycle display mode between single-quadrant, mirrored octants,
                and independent octants (display will not change until features
                are randomzed again)
f       -->     Randomize features displayed
h       -->     Print this help message
m       -->     Toggle mouse function between rotate/zoom and pan modes
p/P     -->     Increase/Decrease the size of displayed points
q       -->     Exit the application
r       -->     Reset viewing geometry
u       -->     Toggle display of unassigned points (points with class == 0)

次の画像は表示を回転させ、未分類のピクセルを排し、ピクセルサイズを大きくした後のウィンドウを表している。
![modified nd window](http://www.spectralpython.net/_images/ndwindow_mod.png)


##Display Modes
高次元画像の対話的なビジュアル解析を促進するために、`view_nd`関数は3次元表示ウィンドウで高次元データを表示するための種々のモードを持つ。

1. Single-Octant Mode

    このモードでは画像データから選ばれた三つの特徴量が3Dウィンドウでそれぞれx、y、z軸に割り当てられる。データは三次元空間のx/y/zの正の象限を埋めるような点に変換される。またユーザーは表示される３つの特徴量をランダムに選ぶことができる。このランダム性により、ユーザーは様々な組み合わせを素早く試すことができ、望ましい組み合わせを探すことができる。

2. Mirrored-Octant Mode

    このモードでは、Single-Octant Modeと同様に、3つの特徴がx、y、zの正軸にマッピングされる。また、Single-Octant Modeと同様に、3つの特徴がx、y、zの正軸にマッピングされるが、さらに3つの特徴が選択され、x、y、z軸の負の部分にマッピングされる。そして、画像ピクセルは、3Dディスプレイの8つの象限のそれぞれに、その象限の3つの半軸に関連付けられた特徴を用いて表示されます。このモードでは、2つの半軸で定義された各部分平面は鏡とみなすことができ、垂直な2つの半軸は異なる特徴を表す。例えば、x、y、z、-x、-y、-zの各半軸がそれぞれ特徴1から6に対応している場合、x/y/zおよびx/y/-zの各象限のデータポイントはx/y平面に共通して投影される。しかし、zと-zは異なる特徴量に対応しているため、データポイントはz軸と-z軸に異なる投影を持つことになる。シングルオクタントモードと同様に、ユーザーは表示される6つの特徴量をランダムに変更することができます。

3. Independent-Octant Mode

    このモードでは3次元空間の各象限は3つの独立した特徴量を表し、最大で24の独立した特徴量を表示できる。他の2つのモードと同様にすべての画像ピクセルが各象限を埋めるようにデータが変換される。また特定の特徴量が一つ以上の象限を使うことも不可能ではない。

ユーザーは上記のような3つの表示モードを切り替えれる。どの表示モードが適切かは使用データセットに関わるファクターの数やアプリケーションの動作するシステムに依る。またピクセル数の大きい画像では、mirrored-octant や independent-octant modeでのシステムディスプレイのㇾフレッシュレートが悪くなる可能性がある。なぜなら各ピクセルが8回レンダリングされているからである。パフォーマンスに問題がなければ、先の2つのモードは同時性の高いデータの投影でデータセットから知見をえる手助けとなるだろう。

##User Interaction
3Dビューの初期状態は決して理想的なものではないため、マウスでの入力でズームやパン、回転などの操作ができる。また未分類のピクセルを非表示にしたり、表示されているピクセルのサイズの大小を変更したりできる。

ピクセルはユーザーがピクセルクラスを判別できるように色付けされている一方、どの色がどのクラスと紐づけられているかわからない場合もある。特定のピクセルのクラスを判別できるように、ピクセルをクリックすると、そのピクセルの画像中での位置（縦と横の値）とピクセルクラスの番号（ID）がコマンドラインに表示される。ピクセル一つをクリックするのは難しいため、先にマウスでズームしてからクリックすることを勧める。

##Data Manipulation
解析者が教師なし分類の結果をレビューし、その後ピクセルクラスを組み合わせたり、新たなまたは異なったクラスを代入することはよくあることである。同様に、グラウンドトゥルースを評価し、それに含まれる誤ったクラスに関しても処理をしたいと思うだろう。こういった需要にこたえるために、N-Dウィンドウではクリックとドラッグの操作で選択した矩形領域に再代入することができる。この機能は選択した範囲に見えるピクセルだけでなく3次元空間に投影された範囲内すべてのピクセルに適用される。

`view_nd`が呼び出されたときNDディスプレイに紐づけられた現在のクラスの値の配列を示すクラスメンバーを持つコピーが返される。このコピーのオブジェクトは画像ピクセルがNDウィンドウを通して再代入されたあとクラス配列の更新分にアクセスするために使われる。またコピーも`set_features`メソッドをもち、3Dウィンドウに表示される特徴量を設定できる。

**Tip:**ピクセルクラスの値を再代入する前に、"C"キーを押して現在の画像のピクセルクラスの値のラスタービューを表示するウィンドウを開くこと。そしてNDウィンドウでピクセルクラスを再代入すると、クラスラスタービューは自動的に更新するためどのピクセルが更新されたかが分かる。