本日は
ちょっとタイトルが悪いですが,Bokehの対話機能,つまり,スライダーを用いてパラメータを動かせるようなアプリケーションの練習として,画像処理の様子を可視化しましょう.
題材は scipy レクチャーノートから拝借します.
例えば
において, 画像をシャープにするコードがあります.
#一部抜粋
f = scipy.misc.face(gray=True).astype(float)
blurred_f = ndimage.gaussian_filter(f, 3)
filter_blurred_f = ndimage.gaussian_filter(blurred_f, 1)
alpha = 30
sharpened = blurred_f + alpha * (blurred_f - filter_blurred_f)
上記のコードにあるalpha
と blurred_f = ndimage.gaussian_filter(f, 3)
の3に当たる値をsigma
とし,これらを動かした時の画像の変化の様子を可視化しましょう.
実装例
上でロジックは何をするべきかを決めましたので, 可視化部分をbokehを用いて実装してみましょう.個人的にも実務でも再利用していきたいというのもあって,真面目に可視化用のclassを実装しています.
(bokehもmatplotlibでもそうなんですが,サンプルがベタがきで書いてあってオブジェクト指向で実装をしていないのが若干不満です.そんな人たちは自分で実装せいやという事なのだろうか?)
from scipy import ndimage as ndi
import numpy as np
import scipy
from bokeh.io import curdoc
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Slider, TextInput
from bokeh.layouts import row, widgetbox
from bokeh.layouts import gridplot
from bokeh.client import push_session
class ImageViewer():
def __init__(self, target):
self.target = target[::-1]
self.source1 = ColumnDataSource(data=dict(image=[self.target]))
self.alpha = Slider(title="alpha", value=30,
start=10, end=50, step=1)
self.sigma = Slider(title="sigma", value=3,
start=1, end=20, step=1)
self.fig1 = self.define_figure('image')
self.regist_image(self.fig1,self.source1)
blurred = ndi.gaussian_filter(self.target, sigma=self.sigma.value)
self.source2 = ColumnDataSource(data=dict(image=[blurred]))
self.fig2 = self.define_figure('blurred')
self.regist_image(self.fig2,self.source2)
filtered = ndi.gaussian_filter(blurred, sigma=1)
sharped = blurred+self.alpha.value*(blurred-filtered)
sharped = sharped.astype(np.uint8)
self.source3 = ColumnDataSource(data=dict(image=[sharped]))
self.fig3 = self.define_figure('sharped')
self.regist_image(self.fig3,self.source3)
widget_list = [self.alpha, self.sigma]
for widget in widget_list:
widget.on_change('value', self.update_data)
inputs = widgetbox(*[widget_list])
self.plot = row(inputs, gridplot(
[[self.fig1, self.fig2, self.fig3]]), width=600)
def define_figure(self, title):
return figure(title=title,
plot_width=self.target.shape[1]//2,
x_range=[0, self.target.shape[1]],
plot_height=self.target.shape[0]//2,
y_range=[0, self.target.shape[0]])
def regist_image(self, fig, source):
fig.image('image',
x=0, y=0,
dh=self.target.shape[0], dw=self.target.shape[1],
source=source, palette='Greys256')
def update_data(self, attr, old, new):
blurred = ndi.gaussian_filter(self.target, sigma=int(self.sigma.value))
filtered = ndi.gaussian_filter(blurred, sigma=1)
sharped = blurred+self.alpha.value*(blurred-filtered)
sharped = sharped.astype(np.uint8)
self.source2.data = dict(image=[blurred])
self.source3.data = dict(image=[sharped])
self.fig1.title.text = '{} {}'.format(
self.sigma.value, self.alpha.value)
def main():
target = scipy.misc.face(gray=True)
viewer = ImageViewer(target)
document = curdoc()
document.add_root(viewer.plot)
main()
実装の方針としては次の通り,
- 画像処理対象画像オブジェクトをインスタンスの引数とするクラスを作ります.
- そして
__init__
内部でクリクリ動かすスライダー, 画像を表示する為の領域の確保のためにfigureオブジェクトを作ります(define_figure
メソッド). -
update_data
メソッドでスライダーをクリクリ動かすごとにどういう動作をさせるかを定義しています. - matplotlibで言う所の
ax.imshow()
に相当するのがregist_image
メソッドです. -
main
関数内でdocument
を定義してそこにplot
を add する.
適宜メソッドの内部を書き換える事で対話機能を用いた可視化ツールを作る事ができるでしょう.
実行例
$ bokeh serve --show sharpe.py
ブラウザが立ち上がり下図のようなものが出ます.
結果がレクチャーノートと微妙に異なるのは
f = scipy.misc.face(gray=True).astype(float)
としていないからです.
修正は読者の練習問題とします.
ともかく,ここでは左上にあるスライダーを動かして変化の様子をみてみましょう.グリグリ動かすと画像が変化していきます.
matplotlib+ipywidgetsと比べて
これぐらいのツールであれば jupyter notebook を起動して ipywidgets と matplotlib の描画機能を利用してできるのですが, スライダーを動かした時の挙動が結構カクカクします.
体感的としては今回のbokehでの実装の方が相対的にスムーズに動きます.とはいえ超絶ヌルヌル動くかといえばそうでもないです.
jupyter notebook で物足りない方はbokehにトライしてみてくださいまし.