Gradio ImageEditorチュートリアル

このノートブックでは、Gradioのパワフルな`ImageEditor`コンポーネントを使用して、画像編集アプリケーションを作成する方法を学びます。

## Gradioとは？

[Gradio](https://www.gradio.app/)は、機械学習モデルや画像処理アプリケーションなどを簡単にWebインターフェースとして公開できるPythonライブラリです。特に、AIデモの作成やプロトタイピングに最適です。

## 環境のセットアップ

まずは必要なライブラリをインストールしましょう。

```python
!pip install gradio pillow numpy
```

## 基本的なImageEditorアプリケーション

以下では、シンプルな画像編集アプリを構築します。このアプリでは、ユーザーは画像をアップロードして編集し、結果を保存することができます。

```python
import gradio as gr
import time
import numpy as np
from PIL import Image
import os

def save_image(im, filename="saved_image.png"):
    """画像をファイルに保存する関数"""
    if im is not None and "composite" in im:
        # 合成画像（最終結果）を取得
        composite = im["composite"]

        # numpy配列の場合はPIL Imageに変換
        if isinstance(composite, np.ndarray):
            composite = Image.fromarray(composite)

        # 画像を保存
        composite.save(filename)
        return f"画像が正常に {filename} として保存されました"
    else:
        return "保存する画像がないか、無効な画像形式です"

# Gradio Blocksアプリケーションを作成（テーマをOceanに設定）
with gr.Blocks(theme=gr.themes.Ocean()) as demo:
    gr.Markdown("# Image Editor Demo")

    with gr.Row():
        with gr.Column():
            # 1:1の縦横比でImageEditorコンポーネントを作成
            im = gr.ImageEditor(
                type="numpy",  # 画像はnumpy配列として処理される
                crop_size="1:1",  # 正方形のトリミング比率を設定
                label="画像エディタ"
            )

        with gr.Column():
            # プレビュー表示用のImageコンポーネントを作成
            im_preview = gr.Image(label="プレビュー")

    # 各種イベントを追跡するカウンター
    with gr.Row():
        n_upload = gr.Number(0, label="アップロードイベント数", step=1)
        n_change = gr.Number(0, label="変更イベント数", step=1)
        n_input = gr.Number(0, label="入力イベント数", step=1)

    # ファイル名入力と保存ボタンを追加
    with gr.Row():
        filename_input = gr.Textbox(value="my_drawing.png", label="ファイル名")
        save_btn = gr.Button("画像を保存", variant="primary")

    # 保存操作のステータスメッセージ
    save_status = gr.Textbox(label="保存ステータス", interactive=False)

    # イベントハンドラーを定義:
    # 画像がアップロードされたらアップロードカウンターを増加
    im.upload(lambda x: x + 1, outputs=n_upload, inputs=n_upload)

    # 画像が変更されたら変更カウンターを増加（ユーザー入力または関数更新による）
    im.change(lambda x: x + 1, outputs=n_change, inputs=n_change)

    # ユーザーが画像を変更したら入力カウンターを増加
    im.input(lambda x: x + 1, outputs=n_input, inputs=n_input)

    # 画像が変更されたらプレビューエリアに合成画像を表示
    im.change(lambda im: im["composite"] if im and "composite" in im else None,
              outputs=im_preview,
              inputs=im,
              show_progress="hidden")

    # 保存ボタンクリックイベントを設定
    save_btn.click(
        fn=save_image,
        inputs=[im, filename_input],
        outputs=save_status
    )

# このスクリプトが直接実行されたときにデモを起動
if __name__ == "__main__":
    demo.launch()
```

## コードの詳細解説

### インポートと設定

```python
import gradio as gr
import time
import numpy as np
from PIL import Image
import os
```

ここでは、必要なライブラリをインポートしています：
- `gradio`: UIコンポーネントの作成に使用
- `time`: デモンストレーション目的（必要に応じて）
- `numpy`: 画像データを配列として処理
- `PIL.Image`: 画像処理機能
- `os`: ファイル操作

### 画像保存関数

```python
def save_image(im, filename="saved_image.png"):
    """画像をファイルに保存する関数"""
    if im is not None and "composite" in im:
        # 合成画像（最終結果）を取得
        composite = im["composite"]

        # numpy配列の場合はPIL Imageに変換
        if isinstance(composite, np.ndarray):
            composite = Image.fromarray(composite)

        # 画像を保存
        composite.save(filename)
        return f"画像が正常に {filename} として保存されました"
    else:
        return "保存する画像がないか、無効な画像形式です"
```

この関数は以下のことを行います：
- `ImageEditor`からの出力を取得し、それが有効かチェックします
- `composite`キーにアクセスして最終的な合成画像を取得します
- 必要に応じてnumpy配列からPIL Imageに変換します
- 指定されたファイル名で画像を保存します
- 操作の結果に関するメッセージを返します

### Gradio UIの作成

```python
with gr.Blocks() as demo:
    with gr.Row():
        # 1:1の縦横比でImageEditorコンポーネントを作成
        im = gr.ImageEditor(
            type="numpy",  # 画像はnumpy配列として処理される
            crop_size="1:1",  # 正方形のトリミング比率を設定
            label="画像エディタ"
        )
        # プレビュー表示用のImageコンポーネントを作成
        im_preview = gr.Image(label="プレビュー")
```

ここでは：
- `gr.Blocks()`でUIの作成を開始
- `gr.Row()`で水平レイアウトを作成
- `gr.ImageEditor`を設定：
  - `type="numpy"`: 画像をnumpy配列として処理
  - `crop_size="1:1"`: 画像を正方形にトリミングするオプション
  - `label="画像エディタ"`: コンポーネントのラベル
- `gr.Image`でプレビュー表示用のコンポーネントを作成

### イベントカウンター

```python
# 各種イベントを追跡するカウンター
n_upload = gr.Number(0, label="アップロードイベント数", step=1)
n_change = gr.Number(0, label="変更イベント数", step=1)
n_input = gr.Number(0, label="入力イベント数", step=1)
```

<!-- #region -->
- 各種のユーザーイベントを追跡するための数値カウンターを作成します
- この例では、3種類のイベントを追跡しています：
  - アップロードイベント：新しい画像がアップロードされたとき
  - 変更イベント：画像が何らかの方法で変更されたとき
  - 入力イベント：ユーザーが直接画像を編集したとき

### 保存機能
```python
# ファイル名入力と保存ボタンを追加
with gr.Row():
    filename_input = gr.Textbox(value="my_drawing.png", label="ファイル名")
    save_btn = gr.Button("画像を保存")

# 保存操作のステータスメッセージ
save_status = gr.Textbox(label="保存ステータス", interactive=False)
```
<!-- #endregion -->

- 保存機能のためのUI要素を追加：
  - ファイル名を入力するためのテキストボックス（デフォルト値付き）
  - 保存アクションを開始するボタン
  - 保存操作の結果を表示するステータステキストボックス

### イベントハンドラ

```python
# イベントハンドラーを定義:
# 画像がアップロードされたらアップロードカウンターを増加
im.upload(lambda x: x + 1, outputs=n_upload, inputs=n_upload)

# 画像が変更されたら変更カウンターを増加（ユーザー入力または関数更新による）
im.change(lambda x: x + 1, outputs=n_change, inputs=n_change)

# ユーザーが画像を変更したら入力カウンターを増加
im.input(lambda x: x + 1, outputs=n_input, inputs=n_input)

# 画像が変更されたらプレビューエリアに合成画像を表示
im.change(lambda im: im["composite"] if im and "composite" in im else None,
          outputs=im_preview,
          inputs=im,
          show_progress="hidden")

# 保存ボタンクリックイベントを設定
save_btn.click(
    fn=save_image,
    inputs=[im, filename_input],
    outputs=save_status
)
```

この部分ではUIのインタラクティブな動作を定義しています：

1. **イベントカウンター更新**:
   - `.upload()`、`.change()`、`.input()`メソッドを使って各種イベントを検出
   - それぞれのイベントが発生するとカウンターを1増加

2. **プレビュー更新**:
   - 画像が変更されるたびに、`im.change()`イベントでプレビュー画像を更新
   - `composite`キーにアクセスして最終的な合成画像を取得

3. **保存機能**:
   - 保存ボタンがクリックされたときに`save_image`関数を呼び出す
   - 現在の画像とユーザーが指定したファイル名を関数に渡す
   - 保存操作の結果をステータステキストボックスに表示

### アプリケーションの起動

```python
# このスクリプトが直接実行されたときにデモを起動
if __name__ == "__main__":
    demo.launch()
```

- `if __name__ == "__main__":`は、このスクリプトが直接実行されたときだけ以下のコードを実行するPythonのイディオム
- `demo.launch()`でGradioアプリケーションを起動し、ローカルのWebサーバーを開始

## ImageEditorの主な特徴

### 編集ツール

`ImageEditor`コンポーネントには、以下のような編集ツールが含まれています：

- **ブラシ**: 自由に描画することができます
- **消しゴム**: 描画した内容を消去できます
- **トリミング**: 画像の一部を切り抜くことができます
- **レイヤー**: 複数のレイヤーで作業でき、それぞれを個別に編集できます

### データ構造

`ImageEditor`からの出力は`EditorValue`と呼ばれる辞書形式のオブジェクトです：

```python
{
    'background': [背景画像データ],
    'layers': [レイヤーのリスト],
    'composite': [最終的な合成画像]
}
```

- `background`: 編集の背景となる画像
- `layers`: 追加されたレイヤーのリスト
- `composite`: すべてのレイヤーを合成した最終画像

## まとめ

このチュートリアルでは、Gradioの`ImageEditor`コンポーネントを使用して、以下のことを学びました：

1. 基本的な画像編集アプリケーションの作成方法
2. イベントハンドラを使ったインタラクティブな機能の実装
3. 編集した画像の保存とファイル管理
4. `ImageEditor`コンポーネントの主な特徴と出力データ構造

Gradioを使うと、複雑な画像処理機能を持つウェブアプリケーションを、わずか数十行のPythonコードで実装できます。これは、実用的なツール開発だけでなく、AIモデルのデモンストレーションやプロトタイピングにも非常に便利です。

## さらなる学習リソース

- [Gradio公式ドキュメント](https://www.gradio.app/docs)
- [ImageEditorコンポーネントのドキュメント](https://www.gradio.app/docs/imageeditor)
- [Gradio GitHub](https://github.com/gradio-app/gradio)

## ノートブック

https://colab.research.google.com/drive/1iFLIvwn8H-SEmUihWpWsaKZiae2gMhLG?usp=sharing