Spaces:
Running
Running
| """ | |
| FLUX.2 Gradio App | |
| 画像とテキストプロンプトを入力して、FLUX.2で画像生成を行うWebアプリ | |
| """ | |
| import gc | |
| import gradio as gr | |
| import torch | |
| from diffusers import Flux2Pipeline | |
| from googletrans import LANGUAGES, Translator | |
| from PIL import Image | |
| class FLUX2App: | |
| def __init__(self): | |
| self.pipe = None | |
| self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| self.torch_dtype = torch.bfloat16 if torch.cuda.is_available() else torch.float32 | |
| self.repo_id = "diffusers/FLUX.2-dev-bnb-4bit" | |
| def load_model(self): | |
| """モデルを初回のみロード""" | |
| if self.pipe is None: | |
| print("モデルをロード中...") | |
| self.pipe = Flux2Pipeline.from_pretrained( | |
| self.repo_id, | |
| torch_dtype=self.torch_dtype | |
| ).to(self.device) | |
| # メモリ最適化 | |
| self.pipe.enable_model_cpu_offload() | |
| print("モデルのロード完了") | |
| return self.pipe | |
| def generate_image( | |
| self, | |
| prompt: str, | |
| input_image: Image.Image, | |
| num_steps: int = 28, | |
| guidance_scale: float = 4.0, | |
| seed: int = 42, | |
| width: int = 1024, | |
| height: int = 768, | |
| progress=gr.Progress() | |
| ): | |
| """ | |
| 画像を生成 | |
| Args: | |
| prompt: テキストプロンプト | |
| input_image: 入力画像(任意) | |
| num_steps: デノイジングステップ数 | |
| guidance_scale: ガイダンススケール | |
| seed: 乱数シード | |
| width: 出力画像の幅 | |
| height: 出力画像の高さ | |
| progress: Gradio Progress tracker | |
| Returns: | |
| 生成された画像、ステータスメッセージ | |
| """ | |
| try: | |
| # プロンプトチェック | |
| if not prompt or prompt.strip() == "": | |
| error_msg = "❌ エラー: プロンプトを入力してください" | |
| progress(0, desc=error_msg) | |
| return None, error_msg | |
| # モデルロード | |
| progress(0.1, desc="🔄 モデルをロード中...") | |
| pipe = self.load_model() | |
| # 生成パラメータ | |
| progress(0.2, desc="⚙️ パラメータを設定中...") | |
| generator = torch.Generator(device=self.device).manual_seed(seed) | |
| # 入力画像の処理 | |
| images_input = [input_image] if input_image is not None else None | |
| # 画像生成 | |
| progress(0.3, desc=f"🎨 画像生成中... (0/{num_steps} steps)") | |
| print(f"生成開始: prompt='{prompt[:50]}...', steps={num_steps}, guidance={guidance_scale}") | |
| # コールバック関数でプログレスを更新 | |
| def callback(pipe, step_index, timestep, callback_kwargs): | |
| progress_value = 0.3 + (0.6 * (step_index + 1) / num_steps) | |
| progress(progress_value, desc=f"🎨 画像生成中... ({step_index + 1}/{num_steps} steps)") | |
| return callback_kwargs | |
| result = pipe( | |
| prompt=prompt, | |
| image=images_input, | |
| generator=generator, | |
| num_inference_steps=num_steps, | |
| guidance_scale=guidance_scale, | |
| width=width, | |
| height=height, | |
| callback_on_step_end=callback, | |
| ) | |
| output_image = result.images[0] | |
| # メモリクリア | |
| progress(0.95, desc="🧹 メモリをクリア中...") | |
| if torch.cuda.is_available(): | |
| torch.cuda.empty_cache() | |
| gc.collect() | |
| success_msg = f"✅ 生成完了! (steps={num_steps}, guidance={guidance_scale}, seed={seed})" | |
| progress(1.0, desc=success_msg) | |
| return output_image, success_msg | |
| except torch.cuda.OutOfMemoryError as e: | |
| error_msg = f"❌ VRAM不足エラー: メモリが足りません。ステップ数や解像度を下げてください。\n詳細: {str(e)}" | |
| print(error_msg) | |
| progress(0, desc="❌ VRAM不足エラー") | |
| if torch.cuda.is_available(): | |
| torch.cuda.empty_cache() | |
| gc.collect() | |
| return None, error_msg | |
| except Exception as e: | |
| error_msg = f"❌ エラーが発生しました: {type(e).__name__}\n詳細: {str(e)}" | |
| print(error_msg) | |
| progress(0, desc=f"❌ {type(e).__name__}") | |
| return None, error_msg | |
| async def translate_to_english(prompt): | |
| """プロンプトを英語に翻訳""" | |
| if not prompt or prompt.strip() == "": | |
| return "", "⚠️ プロンプトが空です" | |
| try: | |
| translator = Translator() | |
| # awaitを使用してコルーチンを実行 | |
| translated = await translator.translate(prompt, src='ja', dest='en') | |
| lang_name = LANGUAGES.get('ja', 'Japanese') | |
| return translated.text, f"✅ 翻訳完了: {lang_name} → English\n原文: {prompt}\n翻訳: {translated.text}" | |
| except Exception as e: | |
| error_msg = f"❌ 翻訳エラー: {type(e).__name__}: {str(e)}" | |
| print(error_msg) | |
| return prompt, error_msg | |
| def create_ui(): | |
| """Gradio UIを作成""" | |
| app = FLUX2App() | |
| with gr.Blocks(title="FLUX.2 画像生成", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown( | |
| """ | |
| # 🎨 FLUX.2 画像生成アプリ | |
| テキストプロンプトと入力画像(任意)から、FLUX.2で新しい画像を生成します。 | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| # 入力エリア | |
| gr.Markdown("### 入力") | |
| prompt_input = gr.Textbox( | |
| label="プロンプト", | |
| placeholder="生成したい画像の説明を入力してください(例: a beautiful sunset over the ocean)", | |
| lines=3, | |
| value="a beautiful sunset over the ocean with vibrant colors" | |
| ) | |
| with gr.Row(): | |
| translate_btn = gr.Button("🌐 英語に翻訳", size="sm") | |
| translate_status = gr.Textbox( | |
| label="翻訳ステータス", | |
| interactive=False, | |
| visible=False | |
| ) | |
| image_input = gr.Image( | |
| label="入力画像(任意)", | |
| type="pil", | |
| sources=["upload", "clipboard"] | |
| ) | |
| with gr.Accordion("詳細設定", open=False): | |
| num_steps = gr.Slider( | |
| minimum=10, | |
| maximum=50, | |
| value=28, | |
| step=1, | |
| label="ステップ数(多いほど高品質だが時間がかかる)" | |
| ) | |
| guidance_scale = gr.Slider( | |
| minimum=1.0, | |
| maximum=10.0, | |
| value=4.0, | |
| step=0.5, | |
| label="ガイダンススケール(高いほどプロンプトに忠実)" | |
| ) | |
| seed = gr.Number( | |
| label="シード値(再現性確保)", | |
| value=42, | |
| precision=0 | |
| ) | |
| with gr.Row(): | |
| width = gr.Slider( | |
| minimum=512, | |
| maximum=2048, | |
| value=1024, | |
| step=64, | |
| label="幅" | |
| ) | |
| height = gr.Slider( | |
| minimum=512, | |
| maximum=2048, | |
| value=768, | |
| step=64, | |
| label="高さ" | |
| ) | |
| generate_btn = gr.Button("🎨 生成", variant="primary", size="lg") | |
| with gr.Column(scale=1): | |
| # 出力エリア | |
| gr.Markdown("### 出力") | |
| output_image = gr.Image( | |
| label="生成画像", | |
| type="pil" | |
| ) | |
| status_text = gr.Textbox( | |
| label="ステータス", | |
| interactive=False | |
| ) | |
| # サンプル例 | |
| gr.Markdown("### 📝 サンプルプロンプト例") | |
| gr.Examples( | |
| examples=[ | |
| ["a photo of a forest with mist swirling around the tree trunks"], | |
| ["a clean monochrome CAD-style technical line drawing"], | |
| ["a beautiful landscape with mountains and a lake at sunset"], | |
| ["an astronaut riding a horse on the moon"], | |
| ["a cute cat wearing sunglasses, digital art"], | |
| ], | |
| inputs=[prompt_input], | |
| label="クリックしてプロンプトをセット" | |
| ) | |
| # イベント設定 | |
| # 翻訳ボタン | |
| async def on_translate(prompt): | |
| translated, status = await translate_to_english(prompt) | |
| return translated, status, gr.update(visible=True) | |
| translate_btn.click( | |
| fn=on_translate, | |
| inputs=[prompt_input], | |
| outputs=[prompt_input, translate_status, translate_status] | |
| ) | |
| # 生成ボタン | |
| generate_btn.click( | |
| fn=app.generate_image, | |
| inputs=[ | |
| prompt_input, | |
| image_input, | |
| num_steps, | |
| guidance_scale, | |
| seed, | |
| width, | |
| height, | |
| ], | |
| outputs=[output_image, status_text] | |
| ) | |
| gr.Markdown( | |
| """ | |
| --- | |
| **使い方:** | |
| 1. プロンプトを入力(必須) | |
| 2. 入力画像をアップロード(任意、編集モードの場合) | |
| 3. 詳細設定を調整(任意) | |
| 4. 「生成」ボタンをクリック | |
| **注意:** | |
| - 初回実行時はモデルのロードに時間がかかります | |
| - VRAM不足の場合はステップ数や解像度を下げてください | |
| """ | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| demo = create_ui() | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| show_error=True | |
| ) | |