Streamlitを用いた音響信号処理ダッシュボードの開発(Tokyo BISH Bash #03発表資料)
Streamlitを用いた音響信号処理ダッシュボードの開発(Tokyo BISH Bash #03発表資料)¶
本記事はTokyo BISH Bash #03の発表資料です。
In [1]:
from IPython.display import IFrame
IFrame("https://www.hiromasa.info/slide/22.slides.html", "100%", "450px")
Out[1]:
自己紹介およびPyData Osakaの紹介¶
- 別スライドで説明します
本日の概要¶
- Streamlitの紹介
- 実例を踏まえた使用方法の紹介
- Herokuへのデプロイ方法の紹介
- その他
実行環境¶
-
https://hub.docker.com/r/wrist/jupyterlab-custom
docker pull wrist/jupyterlab-custom
-
22.ipynb
の動作確認に使用- ただしファイルアップロード時に書き込みエラーが生じる場合有
Streamlitとは¶
- Webブラウザで動作するダッシュボードを作成するためのライブラリ
- 複数のコンポーネントが連携して動作するもの
- Pythonのみで記述が可能
-
plotly/dash
と比較されることが多い
-
どんな場面で必要か¶
- 実装したアルゴリズムを他者に試してもらいたい
- Webフロントエンドがあると便利
- パラメータ変更による試行錯誤が必要な場面
- 調整ツールとして使用
実例¶
- 今回作成したダッシュボードを簡単に紹介
Dashとの違い¶
-
Towards Data Scienceの記事
- Dash
- プロダクション/エンタープライズ環境での実行に主眼
- Streamlit
- ラピッドプロトタイピングに主眼
- Dash
-
plotly.comの比較記事
- Rのshinyも含めて比較
Streamlitの魅力¶
- 手軽にフロントエンドが構築可能
-
javascript
不要、スクリプトライクな記述 - 各種pythonライブラリにGUIを付与
-
- 豊富な描画ライブラリ
-
matplotlib
,plotly
,altair
, ... - Dashはplotlyを前提
-
Streamlitの情報¶
本発表におけるバージョン¶
- 0.68.0
- 10/8にリリースされたばかり
- 1月あたり1〜2回マイナーバージョンが更新
- まだ不安定
- 0.66.0も一部で使用
基本機能の紹介¶
導入方法¶
pip install streamlit
実行方法¶
In [2]:
%%writefile app.py
import streamlit as st
st.write("# Hello, Streamlit!")
In [3]:
!streamlit run app.py
- ノートブックが固まるため以降はターミナル上から
streamlit
コマンドを実行
jupyterlabが8888番ポートのみ稼働している環境上でのアクセス¶
-
jupyter-server-proxy
の導入によるproxyアクセス-
jupyterのURL/proxy/streamlitのport/
でアクセス可能 - ex. 127.0.0.1:8888/proxy/8501/
-
In [4]:
%%writefile app.py
import streamlit as st
st.write("# Hello, Streamlit!")
st.write("# Where is Rerun button?")
スクリプトエラーもブラウザ上に表示¶
- デバッグも容易
In [5]:
%%writefile app.py
import streamlit as st
st.write("# Hello, Streamlit!")
raise ValueError
st.write
¶
- 引数に与えたオブジェクトを画面に表示
- markdownはHTMLに変換して表示
- listやdictは階層表示
- ndarrayやpandasのDataFrameはテーブルとして表示
In [6]:
%%writefile app.py
import numpy as np
import pandas as pd
import streamlit as st
st.write([1, 2, 3])
st.write({"hello": "world!"})
st.write(np.arange(10).reshape(1,10))
st.write(pd.DataFrame(np.random.randn(10, 4), columns=["1", "2", "3", "4"]))
本日紹介するダッシュボード¶
-
https://github.com/wrist/streamlit-dsp
- 窓関数ビューワ
- デジタルフィルタデザイナ
- pyroomacousticsを用いたシミュレータ
- espnet2のフロントエンド
ブランチ¶
master
-
heroku
- Herokuへのアップロードに必要なファイルを格納
-
espnet2
- 3.6.x系で動作させるため別ブランチ
-
Steramlit
のバージョンは0.66.0
github上のstreamlitアプリ¶
- 一般的には直接実行可能
streamlit run https://github.com/user/repos.git/master/app.py
- 今回のアプリはimportの都合でエラーが生じる
窓関数ビューワの構築¶
-
scipy.signal
を使用 -
Streamlit
によるアプリ構築をライブコーディング的に実施
- 現状のダッシュボード
窓関数の選択インタフェース¶
- サイドバーに
selectbox
で表示 - サイドバー上への配置
st.sidebar.xxx
-
st.xxx
と本体に配置できるウィジェットは何でも配置可能
In [7]:
%%writefile app.py
import streamlit as st
windows = ["boxcar", "triang", "blackman", "hamming", "hann", "bartlett", "flattop",
"parzen", "bohman", "blackmanharris", "nuttall", "barthann"]
-
%%writefile -a app.py
でapp.py
に追記
In [8]:
%%writefile -a app.py
win_name = st.sidebar.selectbox("window", windows, 4)
st.write(win_name)
- この時点の見た目
窓関数の窓長およびFFT長の取得¶
- 同様に
st.selectbox
で2の累乗の値を取得
In [9]:
%%writefile -a app.py
two_powers = [2**i for i in range(16)]
Nx = st.sidebar.selectbox("Window Length", two_powers, 8)
nfft = st.sidebar.selectbox("FFT Length", two_powers, 10)
st.write(win_name, Nx, nfft)
- この時点での見た目
窓関数の取得および描画¶
- 窓関数の取得
scipy.signal.get_window
-
scipy.fft.fft
で周波数分析
- 描画
- matplotlibをひとまず使用
- 必要なライブラリをimport
In [10]:
%%writefile -a app.py
import matplotlib.pyplot as plt
import numpy as np
import scipy.signal as sg
import scipy.fft as fft
- 窓関数を取得し周波数特性を計算
In [11]:
%%writefile -a app.py
eps = 1.e-12
win = sg.get_window(win_name, Nx)
W = 20.0 * np.log10(np.abs(fft.fft(win, nfft)) + eps)
W = fft.fftshift(W)
-
matplotlib
を用いて描画
In [12]:
%%writefile -a app.py
fig, axes = plt.subplots(2, 1)
axes[0].plot(win)
axes[1].plot(W)
st.pyplot(fig)
st.pyplot
¶
-
matplotlib
のfigureを描画st.pyplot(fig)
- 使い慣れたmatplotlibを使える
- が、javascriptの恩恵が得られない
- この時点での見た目
streamlit
の描画ライブラリ¶
-
st.line_chart
,st.area_chart
,st.bar_chart
-
st.altair_chart
のsyntax sugar - 手軽に使える反面細かい設定は不可能
-
- その他
vega
,plotly
,bokeh
なども使用可能
In [13]:
%%writefile -a app.py
st.line_chart(win)
st.line_chart(W)
-
st.line_chart
の見た目
この時点でのファイルの内容¶
- 通常の実行スクリプトに近い
- 手軽にフロントエンドの作成が可能
In [14]:
!cat app.py | pygmentize
複数の窓関数を同時に描画したい¶
-
st.multiselect(ラベル名, 選択肢のリスト, デフォルト項目の要素のリスト)
- 複数の選択項目をリストで返す
- 第三引数は選択肢中に存在する要素のリスト
- 先程と同じ
In [15]:
%%writefile app.py
import streamlit as st
windows = ["boxcar", "triang", "blackman", "hamming", "hann", "bartlett", "flattop",
"parzen", "bohman", "blackmanharris", "nuttall", "barthann"]
- 先程と同じ
In [16]:
%%writefile -a app.py
import matplotlib.pyplot as plt
import numpy as np
import scipy.signal as sg
import scipy.fft as fft
-
st.multiselect
を使用
In [17]:
%%writefile -a app.py
# ここから先が異なる
win_names = st.sidebar.multiselect("window", windows, [windows[4]])
st.write(win_names)
-
win_names
をst.write
する以外は同じ
In [18]:
%%writefile -a app.py
two_powers = [2**i for i in range(16)]
Nx = st.sidebar.selectbox("Window Length", two_powers, 8)
nfft = st.sidebar.selectbox("FFT Length", two_powers, 10)
st.write(win_names, Nx, nfft)
-
win_names
をループで処理
In [19]:
%%writefile -a app.py
eps = 1.e-12
for win_name in win_names: # ループとして処理
win = sg.get_window(win_name, Nx)
W = 20.0 * np.log10(np.abs(fft.fft(win, nfft)) + eps)
W = fft.fftshift(W)
In [20]:
%%writefile -a app.py
# ループの続き
st.write(win_name)
st.line_chart(win)
st.line_chart(W)
- 複数の窓が繰り返し描画される
In [21]:
!cat app.py | pygmentize
複数の窓をまとめて描画したい¶
- pandasのDataFrameの複数カラムにデータを押し込む
-
st.line_chart
に渡す
- 先程と同じ
In [22]:
%%writefile app.py
import streamlit as st
windows = ["boxcar", "triang", "blackman", "hamming", "hann", "bartlett", "flattop",
"parzen", "bohman", "blackmanharris", "nuttall", "barthann"]
- pandasを追加
In [23]:
%%writefile -a app.py
import matplotlib.pyplot as plt
import numpy as np
import scipy.signal as sg
import scipy.fft as fft
import pandas as pd # 追加
- 先程と同じ
In [24]:
%%writefile -a app.py
win_names = st.sidebar.multiselect("window", windows, [windows[4]])
st.write(win_names)
- 先程と同じ
In [25]:
%%writefile -a app.py
two_powers = [2**i for i in range(16)]
Nx = st.sidebar.selectbox("Window Length", two_powers, 8)
nfft = st.sidebar.selectbox("FFT Length", two_powers, 10)
st.write(win_names, Nx, nfft)
- 窓と周波数特性をlistに格納
In [26]:
%%writefile -a app.py
eps = 1.e-12
win_ary = []; W_ary = []
for win_name in win_names: # ループとして処理
win = sg.get_window(win_name, Nx)
W = 20.0 * np.log10(np.abs(fft.fft(win, nfft)) + eps)
W = fft.fftshift(W)
win_ary.append(win)
W_ary.append(W)
- DataFrameに格納
In [27]:
%%writefile -a app.py
fs = 16000.0
t = np.arange(Nx)
f = (fs / nfft) * np.arange(-nfft/2, nfft/2)
df_win = pd.DataFrame(np.array(win_ary).T, index=t, columns=win_names)
df_W = pd.DataFrame(np.array(W_ary).T, index=f, columns=win_names)
In [28]:
%%writefile -a app.py
st.line_chart(df_win)
st.line_chart(df_W)