jupyter notebook で ipywidgets を使ってインタラクティブにパラメータを操作してついでに画像処理もしてみる

matplotlib とかで jupyter notebook に関数のグラフをプロットするのはいいけど値を変更して試行錯誤するたびにいちいち Ctrl + Enter とかするのがちとめんどくさい。
最近流行りのリアクティブ（もう古い？）的なノリで値変更を動的に結果に反映させたいのは世の常ですよね。
探してみたところ、 ipywidgets というライブラリが jupyter notebook 向けに開発されているらしいので使ってみることに。

https://github.com/ipython/ipywidgets

いわゆるスライダーやテキストボックスなどの UI コンポーネントを通してのインタラクティブな挙動も可能になるようです。

# 環境

以下の環境で試しました。

- OS X El Capitan
- Python3.6 with venv
- fish shell

# 準備

まずは venv で仮想環境を構築しておく。
virtualenv な人だったり bash な人は適宜読み替えて下さい。

```
$ python3 -m venv ipywidgets_env
$ source ipywidgets_env/bin/activate.fish # fish 用
```

# インストール

普通に jupyter をインストール。

```
(ipywidgets_env) $ pip install jupyter
```

依存関係として一緒に ipywidgets が入ってきたことを軽く確認。
なければないで `pip install` で入れてあげればいいだけな気がします。

```
(ipywidgets_env) $ pip list | grep ipywidgets
ipywidgets (5.2.2)
```

さらに jupyter に対し下記のコマンドを実行し、描画用拡張機能を有効にしておく。

```
(ipywidgets_env) $ jupyter nbextension enable --py widgetsnbextension --sys-prefix
```

# 挙動確認

一応 ipywidgets を試すだけの環境は整ったので早速 jupyter notebook 上で試してみます。

```
(ipywidgets_env) $ jupyter notebook
```

起動後、開いたブラウザで新規にノートブックを作成し、新規セルに `a * x + b` という計算結果を返す関数を書いてみる。

```python
def f(x, a, b):
    return a * x + b
```

この関数においてのパラメータをスライダーの UI コンポーネントで操作するには下記のように上記の関数 `f` およびパラメータとなる `x`, `a`, `b` のそれぞれの値域を `interact` 関数に渡してあげる。

```python
from ipywidgets import interact
interact(f, x=(-10,10, 1), a=(-10,10, 1), b=(-10,10, 1))
```

ブラウザ上の実行結果は以下のようになる。

<img width="534" alt="スクリーンショット 2017-01-04 0.52.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/34880/c2113075-6ae8-dd31-f4b4-4c41ec51bbca.png">

画面上にスライダーが現れた。
ツマミを適当にちょっといじってみる。

<img width="534" alt="スクリーンショット 2017-01-04 0.54.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/34880/b8141e7c-777e-421a-ccb9-ff55f9d1bbc3.png">

最初 `x = 0, a = 0, b = 0` で結果が `0` だったのが `x = 10, a = 4, b = -10` にすると結果にすぐに反映され `30` と表示される。
こうして ipywigdets による値の動的反映が確認できた。

# 画像の2値化しきい値をスライダーで調節しつつ結果に反映する

ちょっとした応用として、画像処理の定番である2値化のしきい値を自前で操作しつつ結果に反映する、といったことを試してみようと思います。
使うのは自分のアイコン画像。

![my_github.jpeg](https://qiita-image-store.s3.amazonaws.com/0/34880/d1865667-c348-0028-2b18-ebc5f41ac2fe.jpeg)

とりあえずですが Python での画像処理ツールである Pillow を使います。
既に入れてあれば下記のコマンドを実行する必要はないです。

```bash
(ipywidgets_env) $ pip install Pillow
```

まずは Pillow をインポートしてグローバル変数 `img` にグレスケの255階調画像をロードしておく。

```py3
from PIL import Image
img = Image.open('picture.jpeg').convert('L')
```

![my_github_gray.png](https://qiita-image-store.s3.amazonaws.com/0/34880/2407c8eb-bc14-b453-155d-bc8de28b27ba.png)

```py3
@interact
def binarize(th: (0, 255, 1)):
    return img.point(lambda p: 255 if p > th else 0)
```

ipywidgets で動的操作する関数 `binarize` を定義し、しきい値変数 `th` によって2値化後の画像を返す関数を作り、 `interact` デコレータを付与してあげることでも同様に ipywidgets を利用することができます。
上記セルを実行すると、

<img width="435" alt="スクリーンショット 2017-01-08 22.23.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/34880/112c0892-e65c-d217-56e6-05b2362f2fda.png">

<img width="436" alt="スクリーンショット 2017-01-08 22.24.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/34880/4a642bdd-e903-8896-1d99-a2905aab1c2b.png">

このように jupyter notebook 上でしきい値スライダーを動かしつつ動的に2値化操作が行えるようになります。

# ついでに GitHub の画像 diff もどきもやってみる

スライダーの使い方はわかったので有名な GitHub の画像差分による確認方法の真似事もやってみます。
Pillow にまだそこまで詳しくなく、部分画像の彩度を調節する方法がわからなかったので画像の上に画像を重ねる無駄な方法でやってしまいました。

```py3
orig = Image.open('picture.jpeg')

@interact
def github_diff(percentage: (0, 100, 1)):
    orig_copied = orig.copy()
    box = (int(img.width / 100 * percentage), 0, img.width, img.height)
    img_cropped = img.crop(box)
    orig_copied.paste(img_cropped, box)
    return orig_copied
```

`orig` にオリジナル画像をロード後、スライダーで指定された割合に基づいて左側にオリジナル画像を、右側にグレスケ画像を描画した画像を動的に表示します。
`paste` が破壊的なメソッドらしく、オリジナル画像のコピーを値変更ごとにとってます。
もっといいやり方あったら教えてほしいです。

<img width="436" alt="スクリーンショット 2017-01-09 14.14.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/34880/569ccad9-75f1-27dc-a8eb-5c9760c4ed28.png">

<img width="436" alt="スクリーンショット 2017-01-09 14.15.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/34880/a83e5ec7-0b00-5dfc-ea5e-9c9e92f085bc.png">

# まとめ

ただでさえ実験・検証に向いている jupyter notebook が更に使いやすくなりますね！