修行の場

知人が言っていました。これは修行だと。

Jupyterを使ってリモート上に手軽にインタラクティブなUIを作る

背景

EC2などリモート上でJupyterがすでに動いている前提です。 Reactなどのフレームワークを使ってGUIを作っても良いのですが、 Jupyterではそれよりも手軽でPythonのみでかけるため手軽にGUIを作るのにおすすめです。

初期設定

初期設定をします。

Jupyterのwidget用のextensionをオンにする。 これがないとwidgetが表示されません。

jupyter nbextension enable --py widgetsnbextension

スクロールをオフにする。(セルを作るたびに実行します)

Cell -> Current Outputs -> Toggle Scrolling

Output領域が狭いと表示されるUIが小さくなるので、Jupyterの幅を広くします。テーマ変更ツールをインストールします。

pip install jupyterthemes

# kernelのバージョンが変わるよう(用調査)
pip install --upgrade ipykernel

jt -t grade3 -fs 95 -altp -tfs 11 -nfs 115 -cellw 88% -T -f roboto -fs 9

ipywidget

ipywidgetを使ってGUIの構築を行います。 ユーザガイドを見れば十分ですが、下記に基本知識をまとめます。

  • ipywidget: widgetが定義されているライブラリ
  • IPython.display: IPythonの表示領域の制御

ipywidget以下にあるWidgetオブジェクトを作成して、 Ipython.display.displayメソッドで表示する流れと、 eventに関数などをsubscribeする方法を知っていればいろいろと作れそうです。

例:簡単なラベル付ツールの作成

データを可視化してアノテーションすることにもJupyterが役立ちます。 例えば、可視化をmatplotlibで実現して、その内容にラベル付をするようなものを作りたいと思います。

画像が表示されて、ボタンを押して次々にラベル付していくようなUIを考えます。とりあえず、次のようなコードでできそうです。

import ipywidgets
from IPython.display import display, clear_output
import functools as ft

class SimpleLabelingUI(object):
    DEFAULT_GRAPH_SIZE = 300
    
    def __init__(self, data_iter, labels, visualizer):
        self.data_iter = data_iter
        self.labels = labels
        self.visualizer = visualizer

        self.btns = []
        for label in self.labels:
            btn = ipywidgets.Button(description=label)
            btn.on_click(ft.partial(self.annotated, label))
            self.btns.append(btn)
        
        self.btn_box = ipywidgets.HBox(self.btns)
        self.max_slider = ipywidgets.IntSlider(self.DEFAULT_GRAPH_SIZE, 0, 600, continuous_update=False)
        self.max_slider.observe(self.graph_size_changed, names='value')
        self.enc = ipywidgets.VBox([self.btn_box, self.max_slider])

        self.current_data = next(data_iter)
        
        self.__draw()

    def graph_size_changed(self, change):
        self.__draw()
        
    def annotated(self, label, b):
        self.current_data = next(self.data_iter)
        self.__draw()

        ## Save annotation here

    def __draw(self):
        clear_output()
        display(self.enc)
        self.visualizer(self.current_data, size=self.max_slider.value)

使い方は、

SimpleLabelingUI(pil_images, ['cute', 'sexy', 'normal'], pil_image_visualizer)

といった感じで使えます。