Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -120,10 +120,12 @@ async def nano_banana_completion_api(base_image: Image.Image, prompt: str) -> Im
|
|
| 120 |
await asyncio.sleep(delay)
|
| 121 |
delay *= 2 # 指数バックオフ
|
| 122 |
else:
|
|
|
|
| 123 |
raise gr.Error(f"API呼び出しエラー: Client error '{e.response.status_code} {e.response.reason_phrase}' for url '{e.request.url}'")
|
| 124 |
except httpx.RequestError as e:
|
| 125 |
raise gr.Error(f"APIリクエストエラー: ネットワークまたはタイムアウトエラーが発生しました。詳細: {e}")
|
| 126 |
except Exception as e:
|
|
|
|
| 127 |
raise gr.Error(f"予期せぬエラーが発生しました: {e}")
|
| 128 |
|
| 129 |
raise gr.Error("APIリクエストが最大リトライ回数を超えて失敗しました。")
|
|
@@ -135,30 +137,43 @@ async def segment_and_inpaint(original_image_np: np.ndarray, sketchpad_image_np:
|
|
| 135 |
"""
|
| 136 |
アップロードされた一枚絵とブラシで描かれたマスクを受け取り、
|
| 137 |
マスクされた領域を分割し、欠損部分をAI補完する関数。
|
| 138 |
-
(出力を6つにし、エラーメッセージを分離してorjsonエラーを回避)
|
| 139 |
"""
|
| 140 |
# 失敗時の統一リターン(6つの出力に対応)
|
| 141 |
def fail(message):
|
| 142 |
-
|
|
|
|
| 143 |
|
| 144 |
if original_image_np is None:
|
| 145 |
return fail("元の画像がアップロードされていません。")
|
| 146 |
|
| 147 |
-
# Sketchpad
|
| 148 |
-
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
|
| 151 |
# PIL Image (元の画像) を取得
|
| 152 |
original_image = Image.fromarray(original_image_np).convert("RGBA")
|
| 153 |
|
| 154 |
# Sketchpadからの入力は、元の画像の上にブラシで描画されたRGBA画像
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
sketch_mask_alpha = sketchpad_image_np[:, :, 3]
|
| 158 |
-
except IndexError:
|
| 159 |
-
return fail("描画データ形式が不正です。RGBA (4チャンネル) の画像として処理できませんでした。")
|
| 160 |
|
| 161 |
-
# ユーザーが何も描画しなかった場合のチェック
|
|
|
|
| 162 |
if np.all(sketch_mask_alpha == 0):
|
| 163 |
return fail("分割したいパーツをブラシで描画してください。(描画が検出されません)")
|
| 164 |
|
|
@@ -182,7 +197,6 @@ async def segment_and_inpaint(original_image_np: np.ndarray, sketchpad_image_np:
|
|
| 182 |
try:
|
| 183 |
completed_body_part = await nano_banana_completion_api(body_with_hole, inpaint_prompt or "マスクされた領域のキャラクターの顔と身体の肌、及び下に着ている服を、元のイラストのテイストに合わせて自然に補完してください。")
|
| 184 |
except gr.Error as e:
|
| 185 |
-
# APIエラーはここで捕まえる
|
| 186 |
return fail(f"AI補完APIエラー: {e}")
|
| 187 |
except Exception as e:
|
| 188 |
return fail(f"AI補完処理中に予期せぬエラー: {e}")
|
|
@@ -265,12 +279,12 @@ with gr.Blocks(theme=theme, title="Live2D素材自動分割・補完アプリ")
|
|
| 265 |
type="numpy",
|
| 266 |
height=200,
|
| 267 |
)
|
| 268 |
-
# 描画専用のSketchpadコンポーネント (type="numpy"
|
| 269 |
input_sketchpad = gr.Sketchpad(
|
| 270 |
label="② 分割したいパーツをブラシで塗る (例: 前髪)",
|
| 271 |
height=400,
|
| 272 |
brush={"color": "#FF0000", "size": 20},
|
| 273 |
-
type="numpy"
|
| 274 |
)
|
| 275 |
|
| 276 |
inpaint_prompt = gr.Textbox(
|
|
@@ -283,8 +297,7 @@ with gr.Blocks(theme=theme, title="Live2D素材自動分割・補完アプリ")
|
|
| 283 |
gr.Markdown(
|
| 284 |
"""
|
| 285 |
### 📌 開発のポイント
|
| 286 |
-
* **安定性の向上:**
|
| 287 |
-
* **エラー表示の改善:** 処理ステータスを専用のテキストボックスに出力するように変更し、JSONデコードエラーを防いでいます。
|
| 288 |
"""
|
| 289 |
)
|
| 290 |
|
|
@@ -318,9 +331,9 @@ with gr.Blocks(theme=theme, title="Live2D素材自動分割・補完アプリ")
|
|
| 318 |
completed_body_output,
|
| 319 |
hair_part_output,
|
| 320 |
body_with_hole_output,
|
| 321 |
-
output_json_text,
|
| 322 |
download_zip,
|
| 323 |
-
status_message
|
| 324 |
]
|
| 325 |
)
|
| 326 |
|
|
|
|
| 120 |
await asyncio.sleep(delay)
|
| 121 |
delay *= 2 # 指数バックオフ
|
| 122 |
else:
|
| 123 |
+
# ユーザーへの表示用にgr.Errorを再raise
|
| 124 |
raise gr.Error(f"API呼び出しエラー: Client error '{e.response.status_code} {e.response.reason_phrase}' for url '{e.request.url}'")
|
| 125 |
except httpx.RequestError as e:
|
| 126 |
raise gr.Error(f"APIリクエストエラー: ネットワークまたはタイムアウトエラーが発生しました。詳細: {e}")
|
| 127 |
except Exception as e:
|
| 128 |
+
# その他の予期せぬエラー
|
| 129 |
raise gr.Error(f"予期せぬエラーが発生しました: {e}")
|
| 130 |
|
| 131 |
raise gr.Error("APIリクエストが最大リトライ回数を超えて失敗しました。")
|
|
|
|
| 137 |
"""
|
| 138 |
アップロードされた一枚絵とブラシで描かれたマスクを受け取り、
|
| 139 |
マスクされた領域を分割し、欠損部分をAI補完する関数。
|
|
|
|
| 140 |
"""
|
| 141 |
# 失敗時の統一リターン(6つの出力に対応)
|
| 142 |
def fail(message):
|
| 143 |
+
# 画像出力は全てNone, JSONとZIPは空文字列/None, ステータスにエラーメッセージ
|
| 144 |
+
return None, None, None, "{}", None, f"❌ 処理失敗: {message}"
|
| 145 |
|
| 146 |
if original_image_np is None:
|
| 147 |
return fail("元の画像がアップロードされていません。")
|
| 148 |
|
| 149 |
+
# ★★★ 修正: Sketchpad入力の型チェックと次元調整の強化 ★★★
|
| 150 |
+
|
| 151 |
+
if not isinstance(sketchpad_image_np, np.ndarray):
|
| 152 |
+
# NumPy配列ではない(NoneやDictなど)場合は、描画がないと判断
|
| 153 |
+
return fail("分割したいパーツをブラシで描画してください。(描画データが検出されません)")
|
| 154 |
+
|
| 155 |
+
if sketchpad_image_np.ndim == 3 and sketchpad_image_np.shape[2] == 3:
|
| 156 |
+
# 3次元配列(RGB)の場合、透明なアルファチャンネルを追加して4次元(RGBA)に変換
|
| 157 |
+
print("--- SketchpadデータがRGB (3次元) で渡されました。RGBA (4次元) に変換します。 ---")
|
| 158 |
+
h, w, _ = sketchpad_image_np.shape
|
| 159 |
+
# 全て透明(0)のアルファチャンネルを作成
|
| 160 |
+
alpha_channel = np.zeros((h, w, 1), dtype=np.uint8)
|
| 161 |
+
# RGB配列とアルファチャンネルを結合
|
| 162 |
+
sketchpad_image_np = np.concatenate((sketchpad_image_np, alpha_channel), axis=2)
|
| 163 |
+
|
| 164 |
+
elif sketchpad_image_np.ndim != 4 or sketchpad_image_np.shape[2] != 4:
|
| 165 |
+
# 4次元配列ではない、または4チャンネルではない場合は不正と判断
|
| 166 |
+
return fail(f"描画データ形式が不正です。(次元: {sketchpad_image_np.ndim}, チャンネル: {sketchpad_image_np.shape[2] if sketchpad_image_np.ndim >= 3 else 'N/A'})。")
|
| 167 |
|
| 168 |
# PIL Image (元の画像) を取得
|
| 169 |
original_image = Image.fromarray(original_image_np).convert("RGBA")
|
| 170 |
|
| 171 |
# Sketchpadからの入力は、元の画像の上にブラシで描画されたRGBA画像
|
| 172 |
+
# アルファチャンネルからマスクを抽出
|
| 173 |
+
sketch_mask_alpha = sketchpad_image_np[:, :, 3]
|
|
|
|
|
|
|
|
|
|
| 174 |
|
| 175 |
+
# ユーザーが何も描画しなかった場合のチェック (アルファチャンネルが全てゼロ)
|
| 176 |
+
# ブラシで描画した場合、アルファチャンネルは255の領域を持つはず
|
| 177 |
if np.all(sketch_mask_alpha == 0):
|
| 178 |
return fail("分割したいパーツをブラシで描画してください。(描画が検出されません)")
|
| 179 |
|
|
|
|
| 197 |
try:
|
| 198 |
completed_body_part = await nano_banana_completion_api(body_with_hole, inpaint_prompt or "マスクされた領域のキャラクターの顔と身体の肌、及び下に着ている服を、元のイラストのテイストに合わせて自然に補完してください。")
|
| 199 |
except gr.Error as e:
|
|
|
|
| 200 |
return fail(f"AI補完APIエラー: {e}")
|
| 201 |
except Exception as e:
|
| 202 |
return fail(f"AI補完処理中に予期せぬエラー: {e}")
|
|
|
|
| 279 |
type="numpy",
|
| 280 |
height=200,
|
| 281 |
)
|
| 282 |
+
# 描画専用のSketchpadコンポーネント (type="numpy")
|
| 283 |
input_sketchpad = gr.Sketchpad(
|
| 284 |
label="② 分割したいパーツをブラシで塗る (例: 前髪)",
|
| 285 |
height=400,
|
| 286 |
brush={"color": "#FF0000", "size": 20},
|
| 287 |
+
type="numpy"
|
| 288 |
)
|
| 289 |
|
| 290 |
inpaint_prompt = gr.Textbox(
|
|
|
|
| 297 |
gr.Markdown(
|
| 298 |
"""
|
| 299 |
### 📌 開発のポイント
|
| 300 |
+
* **安定性の向上:** Gradioの入力データが予期せず3次元配列で渡された場合にも、アルファチャンネルを追加して処理を継続できるようにロジックを強化しました。
|
|
|
|
| 301 |
"""
|
| 302 |
)
|
| 303 |
|
|
|
|
| 331 |
completed_body_output,
|
| 332 |
hair_part_output,
|
| 333 |
body_with_hole_output,
|
| 334 |
+
output_json_text,
|
| 335 |
download_zip,
|
| 336 |
+
status_message
|
| 337 |
]
|
| 338 |
)
|
| 339 |
|