Spectral Python Tutorial   導入からデータの読み込みまで

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

##Introduction
このユーザーガイドでは、下記の論文のデータを参照しチュートリアルを進める。またチュートリアルでは利便性のため`from spectral import *`としているが、モジュール内ネームスペースのコンテンツをすべてインポートすることは非推奨である。データは[ここ](https://github.com/spectralpython/sample-data)から。

| File Name | Description |
|:---------:|:-----------:|
|92AV3C.lan |ERDAS/LanフォーマットのHS画像。AVRISでの撮影で得られた145*145ピクセル、220バンドのデータ。|
|92AV3GT.GIS|上のデータの土地利用図。|
|92AV3C.spc| 上のデータのAVIRISフォーマットのバンドカリブレーションファイル|

Landgrebe, D. Multispectral data analysis: A signal theory perspective. School of Electr. Comput. Eng., Purdue Univ., West Lafayette, IN (1998).

##Installing Spy
基本的にはPythonの組み込みのものとNumpyがあればHSデータの処理は可能である。グラフィカルな機能を使用して解析を行いたい場合には別途モジュールが必要となる。下表にそれぞれの機能の要件を示す。

|Dependency         |Notes|
|:--------:|:---:|
|Python 2.6+ or 3.3+| (1) |
|Numpy | Required |
|Pillow or Python Imaging Library(PIL)|Required if displaying or saving images|
|wxPython|(2)|
|matplotlib|Required if rendering raster displays or spectral plots|
|IPython|Required for interactive, non-blocking GUI windows|
|PyOpenGL|(2)|

Notes:
(1): `view_cube`、`view_nd`以外はPython 3.3+で動作
(2): `view_cube`、`view_nd`を呼び出す際に必要

pip でのインストールは下記のコマンドで。
その他の方法は[ここから](http://www.spectralpython.net/installation.html#installing-from-a-distribution-package)

```
pip install spectral
```

基本的にHSデータは容量が大きいため、特定のディレクトリに保存されている。SPyでデータを開こうとするたびに絶対パスを入力しなくて済むように`SPECTRAL_DATA`という環境変数を設定できる。SPyはこの環境変数に記されているディレクトリからイメージファイルを探す。またディレクトリパスのリストは`:`区切りで記載されなければならない。

##Reading HSI Data Files
通常、SPyではHSイメージファイルにアクセスし開くために`image`関数を使用する。この関数は`SpyFile`オブジェクトのインスタンスを返す。

###The SpyFile Interface
`SpyFile`はHSデータを読み込むオブジェクトの基本的なクラスである。`SpyFile`オブジェクトの作成時、対応するファイルからデータを読み込むインターフェースが与えられる。画像を開いたとき、返される実際のオブジェクトは`SpyFile`のサブクラス(BipFile, BilFile, BsqFile)で、このサブクラスはデータの格納方法(interleaves)にそれぞれ対応している。

下記のコードではサンプル画像を開いている。
>
In [1]: from spectral import *
>
In [2]: img = open_image('92AV3C.lan')
>
In [3]: img.__class__
Out[3]: spectral.io.bilfile.BilFile
>
In [4]: print(img)
	Data Source:   '/home/thomas/spectral_data/92AV3C.lan'
	# Rows:            145
	# Samples:         145
	# Bands:           220
	Interleave:        BIL
	Quantization:  16 bits
	Data format:     int16

HS画像はワーキングディレクトリには存在しないが、*SPECTRAL＿DATA*環境変数のディレクトリから探している。また画像のピクセルデータがlineごとにinterleaveされているため、`image`関数は*BilFile*インスタンスを返す。([BIL](https://desktop.arcgis.com/ja/arcmap/10.3/manage-data/raster-and-images/bil-bip-and-bsq-raster-files.htm)はデータの格納方式)

HSデータファイルはサイズが大きいため、`SpyFile`オブジェクトが作られるときメタデータだけが読み込まれる。実際のイメージデータの値は`SpyFile`メソッドによってリクエストされた時のみ読み込まれる。Numpyのように配列に対して添字演算子(たぶんインデックスとスライシングとか)を適用することができる。`SpyFile`オブジェクトは*M×N×B*の配列で*M*が画像の行数、*N*が画像の列数、*B*がバンド数にそれぞれなる。thenumberになってる。

>
In [5]: img.shape
Out[5]: (145, 145, 220)
>
In [6]: pixel = img[50,100]
>
In [7]: pixel.shape
Out[7]: (220,)
>
In [8]: band6 = img[:,:,5]
>
In [9]: band6.shape
Out[9]: (145, 145, 1)

画像データの各ピクセルの値は添字演算子が適用されるまで(インデクシングやスライシングされるまで)読み込まれない。またPythonではインデックスは0から始まるため、`img[50, 100]`のような場合、51行、101列目のピクセルデータが読み込まれる。同様に、`img[:,:,6]`では画像全体での6バンド目の情報が読み込まれる。


`SpyFile`にはサブクラスのインスタンスが存在し、特定のイメージファイルを返すメソッドが実装されている。

|Method|Description|
|:----:|:---------:|
|read_band|Reads a single band into an *M×N* array|
|read_bands|Reads multiple bands into an *M×N×C* array|
|read_pixel|Reads a single pixel into a length *B* array|
|read_subregion|Reads multiple bands from a rectangular sub-reagion of the image|
|read_subimage|Reads specified rows, columns, and bands|

`SpyFile`オブジェクトは`bands`メンバーを持っており、このメンバーはイメージスペクトルバンドについての補足的な情報を持つ`BandInfo`オブジェクトのインスタンスである。

###Loading Entire Images
繰り返しにはなるが、画像データは`SpyFile`オブジェクトによって必要となったときに呼び出され、データはキャッシュされない。添字演算子やメソッドの呼び出しごとに対応する画像データファイルが読み込まれる。これは事前に同じデータが読み込まれていたとしても関係はない。都度読み込みを行うことで、巨大な画像データに対して処理を行う際に激しいメモリの消費を防ぐ。また画像データ中の一部だけを利用する際には無駄を省くことができる(画像表示のためにRGBだけ読み込むなど)。
しかし、データ全体に対して適用するようなアルゴリズムを走らせた場合には処理に時間がかかる。さらに何度も繰り返しデータにアクセスする必要があるアルゴリズムではよりひどい。

前述のような欠点を解消するために、`load`メソッドを利用しデータ全体をメモリにロードすることが望ましい。このメソッドでは`ImageArray`オブジェクトが返されるが、`SpyFile`インターフェースと同様に`numpy.ndarray`のインターフェース（インデクシングとかスライシングとか）をもつ。

>
In [1]: arr = img.load()
>
In [2]: arr.__class__
Out[2]: spectral.image.ImageArray
>
In [3]: print(arr.info())
	# Rows:            145
	# Samples:         145
	# Bands:           220
	Data format:   float32
>
In [4]: arr.shape
Out[4]: (145, 145, 220)

SPyは主にスペクトル分野での処理用途にデザインされているため、メモリ中の`spectral.ImageArray`オブジェクトはもとのデータファイルのインターリーブスに関係なく常にピクセルごとのインターリーブスをもつ。言い換えるならば、`numpy.ndarray`の`shape`は`(numRows, numCols, numBands)`である。また配列の要素は常に32-bit floatsのデータ型である。

**Note:**
`load`メソッドを呼び出す前に、出力されうる`ImageArray`オブジェクトの消費メモリ量を考慮すべきである。`spectral.ImageArray`オブジェクトは32-bit 浮動小数点の要素を持つため、メモリ消費量は`4*numRows*numCols*numBands`バイトと概算される。

### Numpy `memmap` Interface
ファイル全体を読み込んでおく方法は他にもあり、遅いもののメモリ効率のよいnumpy memmapオブジェクトを利用した方法がある。ここではSpyFileオブジェクトを`open_memmap`メソッドで開く。このmemmapオブジェクトはdate（dataの間違い？）をイメージファイルに書き込むことにも使われる。

##File Formats Supported
###ENVI Headers
ENVI*は地理空間画像情報を処理し解析するための商用ソフトウェアパッケージのひとつである。SPyはENVIヘッダーでまとめられた画像を読み込み、ENVIヘッダーでスペクトルライブラリの読み込みと書き込みができる。ENVIファイルはSPyの`image`関数で自動的に開かれるが、明示的にENVIファイルと指定して開くことも可能である。ヘッダーとデータのディレクトリが異なる場合や、データがSPyで特定できない拡張子を持つ場合、ENVIファイルと明示的に指定しデータをひらく必要がある。

*ENVI is a registered trademark of Exelis Visual Information Solutions.

>
In [5]: import spectral.io.envi as envi
>
In [6]: img = envi.open('cup95eff.int.hdr', 'cup95eff.int')
>
In [7]: import spectral.io.envi as envi
>
In [8]: lib = envi.open('spectra.hdr')
>
In [9]: lib.names[:5]
Out[9]: 
['construction asphalt',
 'construction  concrete',
 'red smooth-faced brick',
 'weathered red brick',
 'bare red brick']

**See also:** ファイルに画像データを書き込むための関数:

`create_image`: ディスク上の割り当てられたストレージに新しい画像ファイルを作成

`save_image`: ENVIヘッダーで既存の画像、またはndarrayを保存


###AVIRIS
SPyはAirborne Visible/Infrared Imaging Spectrometer (AVIRIS)**によって生成されたデータファイルをサポートしている。AVIRISファイルは`open_image`関数によって自動的に認識されるが、スペクトルバンドのカリブレーションファイルは自動的には認識されない。そのため、それぞれのファイルを明示的に開くことになるかもしれない。

>
In [10]: img = aviris.open('f970619t01p02_r02_sc01.a.rfl', 'f970619t01p02_r02.a.spc')


バンドカリブレーションファイルを別々に読み込むこともできる(AVIRISフォーマット中にバンドカリブレーションファイルはあるが、イメージファイルがない場合などに必要となる。)

>
In [11]: img = open_image('92AV3C.lan')
>
In [12]: img.bands = aviris.read_aviris_bands('92AV3C.spc')

**[AVIRIS](http://aviris.jpl.nasa.gov/) 

###ERDAS/Lan
ERDAS/Lanファイルフォーマットは`image`関数により自動的に認識される。ファイルをLanファイルとして明示的に開く必要があるとは考えにくいが、以下のようにして開くことができる。

>
In [13]: import spectral.io.erdas as erdas
>
In [14]: img = erdas.open('92AV3C.lan')


次回に続く。