0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

今更 stable diffusion automatic 1111 webui の拡張機能を作ってみた

0
Posted at

概要

Stable diffusion Automatic 1111 とは、画像生成モデルの Stable Diffusion をローカルで動かす web GUI を提供するツール・ライブラリのことです。

ComfyUI など、Stable diffusion の GUI ツールは他にもありますが、私は automatic 1111 webui を愛用しており、SD 1.5 時代から、今に至るまでローカル画像生成を遊んでいます。
そんな Automatic 1111 webui (以下 webui) では、ユーザーがかなり自由度高く拡張できる、拡張機能の仕組みを提供しています。有名なものでいえば、controlnettagcomplete などでしょうか。そんな拡張機能の仕組みに少し興味を持ったので、本記事では拡張機能の開発の流れを紹介します。習作として、プロンプトをノードエディタで組み立てる拡張機能を作り、その中で拡張機能のエントリーポイントや、よく見る機能の実現方法について検討します。

前提

  • Stable diffusion automatic 1111 webui 自体の導入は済んでいる
  • Python / JavaScript の解説はしません

今回作るもの

タブを新規に追加して、https://github.com/jagenjo/litegraph.js のノードエディタを表示して、プロンプトを出力します。

Stable Difusion automatic 1111 拡張機能開発

エントリーポイントについて

python と javascript を動的に読み込む手段を提供しています。なお、筆者も詳しい仕組みは完全理解できていませんので、誤った説明があれば教えてください。

  1. UIなどで拡張機能をインストールをすると、extensions フォルダーの中に clone されます
    1. セットアップスクリプトなどが動いて、必要な依存モジュールなどをインストールしています
  2. webui リロード / 起動時に、拡張機能のスクリプトを動的に読み込みます
    1. ここの裏取りができてないですが、挙動的に多分そう
    2. 拡張機能のスクリプトは、webui 起動の各種タイミングで呼ばれるコールバックを追加しています
  3. UI が gradio の仕組みに乗って構成され、ブラウザで表示されます
  4. javascript も拡張機能毎にあれば読み込まれます
    1. タイミングは理解できていません

コールバックの種類は多岐に渡るようです。下記の関数定義辺りがコールバックの種類になっていて、 script_callbacks.on_app_started(callback)の形で呼び出します。

コールバックなしで、modules.scripts.Script を継承した class を定義をすることで読み込ませる流儀もあるみたいですが、あまりわかっていないので、ここではスコープ外とします。なんかこの辺で読み込んでいる気配:

ということで、extensions/ 配下に自分の拡張機能の名前で新規フォルダを作り、その中に scripts (pythonコード) と javascript (JSコード) のディレクトリを切れば、開発準備完了です。

今回作るものの概要

画像生成のプロンプトは、多くの場合次のように分類されるタグの結合で構成されるので、それをグラフィカルに扱えたら楽しいかな?と思いついたのでそれを形にします。
元ネタは sdweb-easy-prompt-selector です。

できたもの:
image.png

Python 側

次の二つの処理のみです:

  • UI 定義
  • API 定義

API は何の変哲もない fastapi の定義なので解説を省略。
UI 定義では gradio で script_callbacks.on_ui_tabs つまりタブを追加して新しいページを作る処理をしています。

今回、生成したプロンプトを txt2img / img2img タブにそのまま送りたかったので、webui 本体が提供している「送る」ボタンの仕組みをそのまま借用しています。これが modules.infotext_utils(コード中では parameters_copypaste という別名でimportしています)です。中身を見ると、以下のような処理になっています。

buttons = parameters_copypaste.create_buttons(["txt2img", "img2img"])
parameters_copypaste.bind_buttons(buttons, None, prompt_output)

create_buttons は渡したタブ名それぞれに対して Send to txt2img / Send to img2img という gr.Button を作るだけの単純な関数です。
bind_buttons(buttons, send_image, send_generate_info) の方は少し込み入っていて、内部では register_paste_params_buttonParamBinding(どのボタンが押されたらどのコンポーネントの値をどのタブに転送するか、を表す設定オブジェクト)を登録しているだけです。実際にボタンとタブ側のコンポーネントを繋ぐ処理(connect_paste_params_buttons)は webui 起動時に別途まとめて走るので、拡張機能側は「登録するだけ」でよい、という設計になっています。

今回は画像は送らないので第2引数 send_imageNone、生成したプロンプトを積んだテキストボックス(prompt_output)を send_generate_info に渡すことで、ボタンを押すと prompt_output の中身が txt2img / img2img の該当欄にコピーされるようになります。
なお bind_buttons はコメントに "old function for backwards compatibility; do not use this, use register_paste_params_button" とある通り後方互換のための関数のようで、本来は register_paste_params_button を直接使うのが推奨のようです。今回は手短に済ませたかったのと、他の拡張機能でもよく使われている書き方だったのでそのまま bind_buttons を使っています。

もう一つ拝借しているのが modules.ui_commoncreate_refresh_button です。タグ定義ファイルを選ぶ Dropdownfile_selector)の横に、よくある🔄アイコンのリフレッシュボタンを置きたかったので使いました。

create_refresh_button(
    file_selector,
    self.load_tag_files,
    lambda: {"choices": self.files},
    "pne-file-selector-refresh",
)

引数は「更新対象のコンポーネント」「クリック時に呼ぶ更新処理(ここでは self.files を再読込する load_tag_files)」「更新後に対象コンポーネントへ反映する gradio の update 用引数を返す callable」「ボタンの elem_id」の4つです。内部の実装はシンプルで、ボタンがクリックされたら refresh_method() を呼んでから refreshed_args() の中身(今回なら {"choices": self.files})を gr.update(**args) として返す、というだけです。この統一されたインターフェースのおかげで、model や VAE の選択欄などあちこちにある「選択肢を再読込するリフレッシュボタン」と全く同じ見た目・挙動のものを自分の拡張機能にも簡単に生やせます。

このように、少し hack ですが modules モジュールを利用すると、他の部分で利用されている UI をパクることができます。(まぁ突然壊れるリスクは上がりますが...)

また、フロントエンドで利用したい canvas 要素を予め定義しています。ついでに css も読み込みます。/file=extensions/sd-webui-prompt-node-editor/* のようにすることで、自分の拡張機能側のディレクトリにアクセスできます。

javascript 側

onUiLoaded にコールバックを渡すことで、他の UI の処理が終わった後に拡張機能の読み込みをしてくれます。
また、パーツは基本的には python 側で定義して、フロントエンド独自の処理を id 経由で渡す形で実装してみました。
分けるとなると、フロントエンド側で動的に要素を生成する必要がありますが、見た目を gradio に寄せたいのでこの方式にしています。
後は今回独自のノードを litegraph.js の流儀で実装します。

拡張機能独自なといか、hackなのですが、 gradio で実装されたコンポーネントにイベントを伝播させるために、dispatchEvent などが使われていることでしょうか。

まとめ

今更 Stable diffusion automatic 1111 webui の拡張機能を開発してみました。
地味にネット世界に解説がなさそうだったので、今回結構雑ですが投稿しておきました。ちなみに内部のコンポーネント周りを利用する実装は、Claude 君にほとんどやってもらました。本当に賢いね。
本記事を読んで、I/F だけ書けば後は Claude 君が全部あなたがやりたいことを実現してくれるでしょう。

余談:他の web UI に乗り換えてもいいんですが、推しが結構マイナーで LoRA 等は非常に少なく、安定して生成できる現状から、最新モデルに対応したものに乗り換えるモチベがないんですよねw

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?