Update app.py
Browse files
app.py
CHANGED
|
@@ -319,6 +319,9 @@ pipe_txt2img = None
|
|
| 319 |
current_pipe_model_id = None
|
| 320 |
pipe_img2img = None
|
| 321 |
|
|
|
|
|
|
|
|
|
|
| 322 |
def load_txt2img(model_id):
|
| 323 |
"""Lädt das Text-to-Image Modell basierend auf der Auswahl"""
|
| 324 |
global pipe_txt2img, current_pipe_model_id
|
|
@@ -334,7 +337,7 @@ def load_txt2img(model_id):
|
|
| 334 |
print(f"📝 Beschreibung: {config['description']}")
|
| 335 |
|
| 336 |
try:
|
| 337 |
-
# VAE-Handling basierend auf Modellkonfiguration
|
| 338 |
vae = None
|
| 339 |
if config.get("requires_vae", False):
|
| 340 |
print(f"🔧 Lade externe VAE: {config['vae_model']}")
|
|
@@ -356,7 +359,8 @@ def load_txt2img(model_id):
|
|
| 356 |
"add_watermarker": False,
|
| 357 |
"allow_pickle": True,
|
| 358 |
}
|
| 359 |
-
|
|
|
|
| 360 |
if model_id in SAFETENSORS_MODELS:
|
| 361 |
model_params["use_safetensors"] = True
|
| 362 |
print(f"ℹ️ Verwende safetensors für {model_id}")
|
|
@@ -378,7 +382,10 @@ def load_txt2img(model_id):
|
|
| 378 |
model_id,
|
| 379 |
**model_params
|
| 380 |
).to(device)
|
| 381 |
-
|
|
|
|
|
|
|
|
|
|
| 382 |
print("⚙️ Konfiguriere Scheduler...")
|
| 383 |
|
| 384 |
if pipe_txt2img.scheduler is None:
|
|
@@ -415,7 +422,8 @@ def load_txt2img(model_id):
|
|
| 415 |
|
| 416 |
pipe_txt2img.enable_attention_slicing()
|
| 417 |
print("✅ Attention Slicing aktiviert")
|
| 418 |
-
|
|
|
|
| 419 |
if hasattr(pipe_txt2img, 'vae') and pipe_txt2img.vae is not None:
|
| 420 |
try:
|
| 421 |
pipe_txt2img.enable_vae_slicing()
|
|
@@ -487,7 +495,9 @@ def load_img2img():
|
|
| 487 |
print("✅ Inpainting-Modell geladen und optimiert")
|
| 488 |
|
| 489 |
return pipe_img2img
|
| 490 |
-
|
|
|
|
|
|
|
| 491 |
# === NEUE CALLBACK-FUNKTIONEN FÜR FORTSCHRITT ===
|
| 492 |
class TextToImageProgressCallback:
|
| 493 |
def __init__(self, progress, total_steps):
|
|
@@ -523,6 +533,7 @@ class ImageToImageProgressCallback:
|
|
| 523 |
progress_percent = (step / self.actual_total_steps) * 100
|
| 524 |
self.progress(progress_percent / 100, desc="Generierung läuft...")
|
| 525 |
return callback_kwargs
|
|
|
|
| 526 |
|
| 527 |
# === NEUE FUNKTIONEN FÜR DIE FEATURES (ANGEPASST FÜR 3 MODI) ===
|
| 528 |
def create_preview_image(image, bbox_coords, mode):
|
|
@@ -568,7 +579,7 @@ def create_preview_image(image, bbox_coords, mode):
|
|
| 568 |
box_color = (128, 128, 128, 200)
|
| 569 |
text_bg_color = (64, 64, 64, 160)
|
| 570 |
|
| 571 |
-
# Skaliere Rahmendicke basierend auf Bildgröße
|
| 572 |
border_width = max(8, image.width // 200) # Mindestens 8px, bei großen Bildern dicker
|
| 573 |
draw.rectangle([0, 0, preview.width-1, preview.height-1],
|
| 574 |
outline=border_color, width=border_width)
|
|
@@ -613,6 +624,7 @@ def update_live_preview(image, bbox_x1, bbox_y1, bbox_x2, bbox_y2, mode):
|
|
| 613 |
bbox_coords = sort_coordinates(bbox_x1, bbox_y1, bbox_x2, bbox_y2)
|
| 614 |
|
| 615 |
return create_preview_image(image, bbox_coords, mode)
|
|
|
|
| 616 |
|
| 617 |
def process_image_upload(image):
|
| 618 |
"""Verarbeitet Bild-Upload und gibt Bild + Koordinaten zurück"""
|
|
@@ -634,6 +646,7 @@ def process_image_upload(image):
|
|
| 634 |
print(f"Bild {width}x{height} -> Slider-Originalwerte: [{bbox_x1}, {bbox_y1}, {bbox_x2}, {bbox_y2}]")
|
| 635 |
|
| 636 |
return preview, bbox_x1, bbox_y1, bbox_x2, bbox_y2
|
|
|
|
| 637 |
|
| 638 |
# === FUNKTION FÜR SLIDER-UPDATE ===
|
| 639 |
def update_slider_for_image(image):
|
|
@@ -718,6 +731,7 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 718 |
print(f"ℹ️ Kein manueller Negativ-Prompt, verwende nur automatischen: {combined_negative_prompt}")
|
| 719 |
|
| 720 |
print(f"✅ Finaler kombinierter Negativ-Prompt: {combined_negative_prompt}")
|
|
|
|
| 721 |
|
| 722 |
# ===== GESICHTS-SPEZIFISCHE BOOSTER FÜR NUR-GESICHT MODUS =====
|
| 723 |
if mode == "face_only_change":
|
|
@@ -732,6 +746,7 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 732 |
print(f"👤 Benutzer hat bereits Gesichts-Booster im Prompt")
|
| 733 |
else:
|
| 734 |
enhanced_prompt = prompt
|
|
|
|
| 735 |
|
| 736 |
# ===== HINTERGRUND-BOOSTER FÜR UMGEBUNGS-ÄNDERUNG =====
|
| 737 |
if mode == "environment_change":
|
|
@@ -744,7 +759,10 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 744 |
|
| 745 |
print(f"🎯 Finaler Prompt für {mode}: {enhanced_prompt}")
|
| 746 |
|
|
|
|
|
|
|
| 747 |
progress(0, desc="Starte Generierung mit ControlNet...")
|
|
|
|
| 748 |
|
| 749 |
# ===== MODUS-SPEZIFISCHE EINSTELLUNGEN =====
|
| 750 |
adj_strength = min(0.85, strength * 1.25)
|
|
@@ -786,7 +804,11 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 786 |
original_mask, # Originalmaske
|
| 787 |
target_size=IMG_SIZE
|
| 788 |
)
|
| 789 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 790 |
controlnet_input = scaled_image # Verwende das skalierte Bild für ControlNet
|
| 791 |
print(f"✅ Gemeinsame Skalierung abgeschlossen")
|
| 792 |
print(f" Original: {image.size} → Skaliert: {scaled_image.size}")
|
|
@@ -1230,13 +1252,13 @@ def main_ui():
|
|
| 1230 |
)
|
| 1231 |
with gr.Column():
|
| 1232 |
img_steps = gr.Slider(
|
| 1233 |
-
minimum=10, maximum=
|
| 1234 |
label="⚙️ Inferenz-Schritte",
|
| 1235 |
info="Anzahl der Verarbeitungsschritte (25-45 für gute Ergebnisse)"
|
| 1236 |
)
|
| 1237 |
with gr.Column():
|
| 1238 |
img_guidance = gr.Slider(
|
| 1239 |
-
minimum=1.0, maximum=
|
| 1240 |
label="🎛️ Prompt-Stärke",
|
| 1241 |
info="Einfluss des Prompts auf das Ergebnis (6-10 für natürliche Ergebnisse)"
|
| 1242 |
)
|
|
|
|
| 319 |
current_pipe_model_id = None
|
| 320 |
pipe_img2img = None
|
| 321 |
|
| 322 |
+
|
| 323 |
+
#Das Laden des Modells bedeutet, die trainierten Gewichte (Parameter) von der Festplatte zu lesen und
|
| 324 |
+
#im Arbeitsspeicher (RAM) und idealerweise im Grafikspeicher (VRAM) zu halten, damit sie für Berechnungen schnell verfügbar sind.
|
| 325 |
def load_txt2img(model_id):
|
| 326 |
"""Lädt das Text-to-Image Modell basierend auf der Auswahl"""
|
| 327 |
global pipe_txt2img, current_pipe_model_id
|
|
|
|
| 337 |
print(f"📝 Beschreibung: {config['description']}")
|
| 338 |
|
| 339 |
try:
|
| 340 |
+
# VAE-Handling basierend auf Modellkonfiguration (Realistic Vision hat kein VAE-der Autoencoder ist ein CNN)
|
| 341 |
vae = None
|
| 342 |
if config.get("requires_vae", False):
|
| 343 |
print(f"🔧 Lade externe VAE: {config['vae_model']}")
|
|
|
|
| 359 |
"add_watermarker": False,
|
| 360 |
"allow_pickle": True,
|
| 361 |
}
|
| 362 |
+
|
| 363 |
+
# Jetzt wird nicht mehr erzwungen wo nach Gewichten gesucht werden soll sondern gezielt mitgeteilt welche Gewichte gewählt wurden.
|
| 364 |
if model_id in SAFETENSORS_MODELS:
|
| 365 |
model_params["use_safetensors"] = True
|
| 366 |
print(f"ℹ️ Verwende safetensors für {model_id}")
|
|
|
|
| 382 |
model_id,
|
| 383 |
**model_params
|
| 384 |
).to(device)
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
# Der Scheduler (z.B. DPM-Solver++ oder PNDM) ist der Algorithmus, der den Zeitplan für das schrittweise Entrauschen (Denoising)
|
| 388 |
+
# festlegt - er bestimmt, wie viele und welche Rauschschritte in welcher Reihenfolge abgearbeitet werden.
|
| 389 |
print("⚙️ Konfiguriere Scheduler...")
|
| 390 |
|
| 391 |
if pipe_txt2img.scheduler is None:
|
|
|
|
| 422 |
|
| 423 |
pipe_txt2img.enable_attention_slicing()
|
| 424 |
print("✅ Attention Slicing aktiviert")
|
| 425 |
+
|
| 426 |
+
# Attention Slicing ist Aufteilung der Attention-Matrix auf die Heads -> späteres concat
|
| 427 |
if hasattr(pipe_txt2img, 'vae') and pipe_txt2img.vae is not None:
|
| 428 |
try:
|
| 429 |
pipe_txt2img.enable_vae_slicing()
|
|
|
|
| 495 |
print("✅ Inpainting-Modell geladen und optimiert")
|
| 496 |
|
| 497 |
return pipe_img2img
|
| 498 |
+
|
| 499 |
+
#Die Callback-Funktion wird von der Pipeline nach jedem Verarbeitungsschritt aufgerufen und erhält Informationen
|
| 500 |
+
#wie den aktuellen step und timestep. Diese nutzt der Progressbalken-Callback, um den Fortschritt zu berechnen und anzuzeigen.
|
| 501 |
# === NEUE CALLBACK-FUNKTIONEN FÜR FORTSCHRITT ===
|
| 502 |
class TextToImageProgressCallback:
|
| 503 |
def __init__(self, progress, total_steps):
|
|
|
|
| 533 |
progress_percent = (step / self.actual_total_steps) * 100
|
| 534 |
self.progress(progress_percent / 100, desc="Generierung läuft...")
|
| 535 |
return callback_kwargs
|
| 536 |
+
|
| 537 |
|
| 538 |
# === NEUE FUNKTIONEN FÜR DIE FEATURES (ANGEPASST FÜR 3 MODI) ===
|
| 539 |
def create_preview_image(image, bbox_coords, mode):
|
|
|
|
| 579 |
box_color = (128, 128, 128, 200)
|
| 580 |
text_bg_color = (64, 64, 64, 160)
|
| 581 |
|
| 582 |
+
# Skaliere Rahmendicke basierend auf Bildgröße (sonst bei großen Bildern ganz dünne Rahmen!)
|
| 583 |
border_width = max(8, image.width // 200) # Mindestens 8px, bei großen Bildern dicker
|
| 584 |
draw.rectangle([0, 0, preview.width-1, preview.height-1],
|
| 585 |
outline=border_color, width=border_width)
|
|
|
|
| 624 |
bbox_coords = sort_coordinates(bbox_x1, bbox_y1, bbox_x2, bbox_y2)
|
| 625 |
|
| 626 |
return create_preview_image(image, bbox_coords, mode)
|
| 627 |
+
|
| 628 |
|
| 629 |
def process_image_upload(image):
|
| 630 |
"""Verarbeitet Bild-Upload und gibt Bild + Koordinaten zurück"""
|
|
|
|
| 646 |
print(f"Bild {width}x{height} -> Slider-Originalwerte: [{bbox_x1}, {bbox_y1}, {bbox_x2}, {bbox_y2}]")
|
| 647 |
|
| 648 |
return preview, bbox_x1, bbox_y1, bbox_x2, bbox_y2
|
| 649 |
+
|
| 650 |
|
| 651 |
# === FUNKTION FÜR SLIDER-UPDATE ===
|
| 652 |
def update_slider_for_image(image):
|
|
|
|
| 731 |
print(f"ℹ️ Kein manueller Negativ-Prompt, verwende nur automatischen: {combined_negative_prompt}")
|
| 732 |
|
| 733 |
print(f"✅ Finaler kombinierter Negativ-Prompt: {combined_negative_prompt}")
|
| 734 |
+
|
| 735 |
|
| 736 |
# ===== GESICHTS-SPEZIFISCHE BOOSTER FÜR NUR-GESICHT MODUS =====
|
| 737 |
if mode == "face_only_change":
|
|
|
|
| 746 |
print(f"👤 Benutzer hat bereits Gesichts-Booster im Prompt")
|
| 747 |
else:
|
| 748 |
enhanced_prompt = prompt
|
| 749 |
+
|
| 750 |
|
| 751 |
# ===== HINTERGRUND-BOOSTER FÜR UMGEBUNGS-ÄNDERUNG =====
|
| 752 |
if mode == "environment_change":
|
|
|
|
| 759 |
|
| 760 |
print(f"🎯 Finaler Prompt für {mode}: {enhanced_prompt}")
|
| 761 |
|
| 762 |
+
|
| 763 |
+
#Zur Überbrückung bis von der Pipelines Infos kommen!
|
| 764 |
progress(0, desc="Starte Generierung mit ControlNet...")
|
| 765 |
+
|
| 766 |
|
| 767 |
# ===== MODUS-SPEZIFISCHE EINSTELLUNGEN =====
|
| 768 |
adj_strength = min(0.85, strength * 1.25)
|
|
|
|
| 804 |
original_mask, # Originalmaske
|
| 805 |
target_size=IMG_SIZE
|
| 806 |
)
|
| 807 |
+
|
| 808 |
+
#ControlNet ist ein paralleles Modell (CNN), das unveränderte Control-Maps (z. B. Tiefenkarten)
|
| 809 |
+
#verarbeitet und konditionierende Signale an das frozen UNet weiterleitet, um die Gesamtgeneration zu steuern,
|
| 810 |
+
#ohne pixelgenaue Manipulationen vorzunehmen. Es beeinflusst den Diffusionsprozess global/lokal durch Addition zu den Features.
|
| 811 |
+
#ControlNet-Bildgröße und Inpaint-Bildgröße müssen übereinstimmen!
|
| 812 |
controlnet_input = scaled_image # Verwende das skalierte Bild für ControlNet
|
| 813 |
print(f"✅ Gemeinsame Skalierung abgeschlossen")
|
| 814 |
print(f" Original: {image.size} → Skaliert: {scaled_image.size}")
|
|
|
|
| 1252 |
)
|
| 1253 |
with gr.Column():
|
| 1254 |
img_steps = gr.Slider(
|
| 1255 |
+
minimum=10, maximum=45, value=35, step=1,
|
| 1256 |
label="⚙️ Inferenz-Schritte",
|
| 1257 |
info="Anzahl der Verarbeitungsschritte (25-45 für gute Ergebnisse)"
|
| 1258 |
)
|
| 1259 |
with gr.Column():
|
| 1260 |
img_guidance = gr.Slider(
|
| 1261 |
+
minimum=1.0, maximum=15.0, value=7.5, step=0.5,
|
| 1262 |
label="🎛️ Prompt-Stärke",
|
| 1263 |
info="Einfluss des Prompts auf das Ergebnis (6-10 für natürliche Ergebnisse)"
|
| 1264 |
)
|