Streamlit - Drawable Canvas の最小動作確認 (with GoogleColab)

# はじめに

Streamlit - Drawable Canvas の動作の確認方法をご紹介します。

実行環境は GoogleColab です。

# Streamlit - Drawable Canvas

Streamlit の環境でインタラクティブに画像に線を引いたりできるライブラリです。

https://github.com/andfanilo/streamlit-drawable-canvas

# 最小構成の動作確認

GoogleColab の環境は準備済みとします。

## 必要モジュールのインストール

`pip`を利用して必要モジュールをインストール

```
!pip install streamlit
!pip install streamlit-drawable-canvas
```

## アプリのPythonコードを作成

下記コマンドを実行して、アプリのPythonコードを作成します。
`%%writefile ファイル名`でファイルを作成できます。

```python
%%writefile app.py
from streamlit_drawable_canvas import st_canvas
canvas_result = st_canvas() # 書き込めるスペースを作っています
```

## アプリを実行する

### ローカルで実行してる場合

下記のコマンドでアプリを実行できます。

```
!streamlit run app.py
```

### GoogleColabの場合

GoogleColabでは次のようなコマンドを実行します。

```
!streamlit run app.py & sleep 3 && npx localtunnel --port 8501
```

次のような結果が表示されるので、
「External URL」のIPアドレスをコピーして、「your url is」の後のアドレスにアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575176/5e166a32-35c4-cd46-0f52-2d2e3a6a390b.png)

次のような画面が表示されるので、「Endpoint IP.」に先ほどコピーしたIPアドレスをペーストします。ポート番号は不要です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575176/2b914ab2-5569-f2f1-2e3b-3c85fec751d4.png)

次のようなアプリ画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575176/831b6908-8cec-f4af-487d-f3d0bd67662b.png)

## 書き込む

アプリの書き込みエリアをマウスのクリック&ドラッグすると線を書き込むことができます。

![画面収録 2023-10-30 19.10.44.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575176/2161dab3-7a8d-14ad-0628-65f6fc7f16dd.gif)

# 取得できる情報について

Streamlit - Drawable Canvas で取得できる情報についてです。

* `canvas_result = st_canvas()`: アプリ上で入力した情報が格納されます。
* `canvas_result.image_data`: アプリ上で入力した情報の画像データが格納されます。
    * 画像データは`numpy.ndarray`形式で保存
    * `st.image(canvas_result.image_data)`で画像データを表示可能
* `canvas_result.json_data`: アプリ上で入力した情報がHTMLのCanvas形式で格納されます。
    * 例えば、`path`の情報から入力した線の始点と終点を取得できます。
    * ```
      {'background': '',
      'objects': [{'angle': 0,
                  'backgroundColor': '',
                  'fill': None,
                  'fillRule': 'nonzero',
                  'flipX': False,
                  'flipY': False,
                  'globalCompositeOperation': 'source-over',
                  'height': 139.02,
                  'left': 216,
                  'opacity': 1,
                  'originX': 'left',
                  'originY': 'top',
                  'paintFirst': 'fill',
                  'path': [['M', 226, 80.98],
                           ['Q', 226, 81, 226, 81.5],
                           ['Q', 226, 82, 226, 84.5],
                           ['Q', 226, 87, 227, 92.5],
                           ['Q', 228, 98, 228, 103.5],
                           ['Q', 228, 109, 229.5, 126.5],
                           ['Q', 231, 144, 231, 149.5],
                           ['Q', 231, 155, 231, 169],
                           ['Q', 231, 183, 231, 187.5],
                           ['Q', 231, 192, 231, 197.5],
                           ['Q', 231, 203, 231, 207],
                           ['Q', 231, 211, 231, 212],
                           ['Q', 231, 213, 233, 213.5],
                           ['Q', 235, 214, 239, 214.5],
                           ['Q', 243, 215, 257.5, 216.5],
                           ['Q', 272, 218, 286, 219],
                           ['Q', 300, 220, 308.5, 220],
                           ['Q', 317, 220, 321.5, 220],
                           ['Q', 326, 220, 334.5, 219.5],
                           ['Q', 343, 219, 345.5, 218.5],
                           ['L', 348.02, 217.98]],
                  'scaleX': 1,
                  'scaleY': 1,
                  'shadow': None,
                  'skewX': 0,
                  'skewY': 0,
                  'stroke': 'black',
                  'strokeDashArray': None,
                  'strokeDashOffset': 0,
                  'strokeLineCap': 'round',
                  'strokeLineJoin': 'round',
                  'strokeMiterLimit': 10,
                  'strokeUniform': False,
                  'strokeWidth': 20,
                  'top': 70.98,
                  'type': 'path',
                  'version': '4.4.0',
                  'visible': True,
                  'width': 122.02}],
      'version': '4.4.0'}
      ```

## 実際に動作させて確認

アプリのスクリプトを次のように変換して、どのような結果が取得されているか確認できます。

```python
%%writefile app.py
import streamlit as st
from streamlit_drawable_canvas import st_canvas
import pprint

canvas_result = st_canvas()
st_canvas(initial_drawing=canvas_result.json_data)

print("type(canvas_result.image_data) :", type(canvas_result.image_data))
print("canvas_result.image_data.shape :", canvas_result.image_data.shape)
pprint.pprint(canvas_result.json_data)

st.image(canvas_result.image_data)
```

先ほどと同様にアプリを起動します。

```
!streamlit run app.py & sleep 3 && npx localtunnel --port 8501
```

アプリが起動します。

先ほどと違い３種類の画像が表示されます。

* １枚目は、`canvas_result = st_canvas()`の部分
    * 先ほど同様書き込むことができます
* ２枚目は、`st_canvas(initial_drawing=canvas_result.json_data)`の部分
    * 書き込むことはできますが、`canvas_result`は更新されません
* 3枚目は、　`st.image(canvas_result.image_data)`の部分
    * 書き込むことはできません

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575176/0e8d40a6-299a-45c7-3e96-252653acb41b.png)

GoogleColab上で次の部分で表示させている出力を確認できます。
```
print("type(canvas_result.image_data) :", type(canvas_result.image_data))
print("canvas_result.image_data.shape :", canvas_result.image_data.shape)
pprint.pprint(canvas_result.json_data)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575176/50e7adba-e718-a454-95cb-409f5d860171.png)