Astridkraft commited on
Commit
7d35e03
·
verified ·
1 Parent(s): f6176b5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +326 -433
app.py CHANGED
@@ -9,9 +9,6 @@ import time
9
  import os
10
  import tempfile
11
  import random
12
- import threading
13
- from queue import Queue, Empty
14
- import warnings
15
 
16
  # === OPTIMIERTE EINSTELLUNGEN ===
17
  device = "cuda" if torch.cuda.is_available() else "cpu"
@@ -44,11 +41,8 @@ MODEL_CONFIGS = {
44
  # === SAFETENSORS KONFIGURATION ===
45
  SAFETENSORS_MODELS = ["runwayml/stable-diffusion-v1-5"]
46
 
47
- # === GLOBALE CACHE FÜR MODELLE ===
48
- _model_cache = {}
49
- _model_cache_lock = threading.Lock()
50
- _current_loading_model = None
51
- _loading_lock = threading.Lock()
52
 
53
  # === AUTOMATISCHE NEGATIVE PROMPT GENERIERUNG ===
54
  def auto_negative_prompt(positive_prompt):
@@ -145,34 +139,28 @@ def auto_detect_face_area(image):
145
  print(f"Geschätzte Gesichtskoordinaten: [{x1}, {y1}, {x2}, {y2}]")
146
  return [x1, y1, x2, y2]
147
 
148
- # === MODELL-LADEN MIT CACHING UND LOAD-BALANCING ===
149
- def load_model_with_cache(model_id, force_reload=False):
150
- """Lädt Modelle mit Caching und Thread-Sicherheit"""
151
- global _model_cache, _current_loading_model
 
 
 
 
152
 
153
- # Prüfe Cache
154
- with _model_cache_lock:
155
- if model_id in _model_cache and not force_reload:
156
- print(f"✅ Modell {model_id} aus Cache geladen")
157
- return _model_cache[model_id]
158
 
159
- # Verhindere paralleles Laden desselben Modells
160
- with _loading_lock:
161
- if _current_loading_model == model_id:
162
- print(f" Modell {model_id} wird bereits geladen, warte...")
163
- while model_id not in _model_cache:
164
- time.sleep(0.1)
165
- return _model_cache.get(model_id)
166
-
167
- _current_loading_model = model_id
168
 
169
  try:
170
- print(f"🔄 Lade Modell: {model_id}")
171
-
172
- config = MODEL_CONFIGS.get(model_id, MODEL_CONFIGS["runwayml/stable-diffusion-v1-5"])
173
- print(f"📋 Modell-Konfiguration: {config['name']}")
174
-
175
- # VAE-Handling
176
  vae = None
177
  if config.get("requires_vae", False):
178
  print(f"🔧 Lade externe VAE: {config['vae_model']}")
@@ -184,15 +172,16 @@ def load_model_with_cache(model_id, force_reload=False):
184
  print("✅ VAE erfolgreich geladen")
185
  except Exception as vae_error:
186
  print(f"⚠️ Fehler beim Laden der VAE: {vae_error}")
 
187
  vae = None
188
 
189
- # Modellparameter
190
  model_params = {
191
  "torch_dtype": torch_dtype,
192
  "safety_checker": None,
193
  "requires_safety_checker": False,
194
  "add_watermarker": False,
195
- "allow_pickle": True,
196
  }
197
 
198
  # SAFETENSORS LOGIK
@@ -203,179 +192,136 @@ def load_model_with_cache(model_id, force_reload=False):
203
  model_params["use_safetensors"] = False
204
  print(f"ℹ️ Verwende .bin weights für {model_id}")
205
 
206
- # FP16 nur wenn unterstützt
207
  if config.get("supports_fp16", False) and torch_dtype == torch.float16:
208
  model_params["variant"] = "fp16"
209
  print("ℹ️ Verwende FP16 Variante")
 
 
210
 
211
- # VAE hinzufügen
212
  if vae is not None:
213
  model_params["vae"] = vae
214
 
215
- print(f"📥 Lade Hauptmodell...")
216
- pipe = StableDiffusionPipeline.from_pretrained(
217
  model_id,
218
  **model_params
219
  ).to(device)
220
 
221
- # Scheduler-Konfiguration
222
- if pipe.scheduler is None:
 
 
 
223
  print("⚠️ Scheduler ist None, setze Standard-Scheduler")
224
- pipe.scheduler = PNDMScheduler.from_pretrained(
225
  model_id,
226
  subfolder="scheduler"
227
  )
228
 
229
- # Optimierungen
230
  try:
231
- # Versuche DPM-Solver
232
- scheduler_config = pipe.scheduler.config if hasattr(pipe.scheduler, 'config') else {
233
- "beta_start": 0.00085,
234
- "beta_end": 0.012,
235
- "beta_schedule": "scaled_linear",
236
- "num_train_timesteps": 1000,
237
- "prediction_type": "epsilon",
238
- "steps_offset": 1
239
- }
 
 
 
 
 
240
 
241
- pipe.scheduler = DPMSolverMultistepScheduler.from_config(
 
242
  scheduler_config,
243
  use_karras_sigmas=True,
244
  algorithm_type="sde-dpmsolver++"
245
  )
246
  print("✅ DPM-Solver Multistep Scheduler konfiguriert")
247
- except Exception:
248
- print("ℹ️ Verwende Standard-Scheduler")
 
 
249
 
250
- pipe.enable_attention_slicing()
 
 
251
 
252
- if hasattr(pipe, 'vae') and pipe.vae is not None:
 
253
  try:
254
- pipe.enable_vae_slicing()
 
 
255
  print("✅ VAE Slicing aktiviert")
256
- except Exception:
257
- pass
258
-
259
- # In Cache speichern
260
- with _model_cache_lock:
261
- _model_cache[model_id] = pipe
262
 
263
- print(f"✅ {config['name']} erfolgreich geladen und gecached")
 
 
 
 
264
 
265
- return pipe
266
 
267
  except Exception as e:
268
- print(f"❌ Fehler beim Laden von {model_id}: {str(e)[:200]}")
269
  import traceback
270
  traceback.print_exc()
 
271
 
272
- # Fallback auf SD 1.5
273
  try:
274
- print("🔄 Fallback auf SD 1.5...")
275
- pipe = StableDiffusionPipeline.from_pretrained(
276
  "runwayml/stable-diffusion-v1-5",
277
  torch_dtype=torch_dtype,
278
- safety_checker=None,
279
  ).to(device)
280
- pipe.enable_attention_slicing()
281
-
282
- with _model_cache_lock:
283
- _model_cache["runwayml/stable-diffusion-v1-5"] = pipe
284
 
285
- return pipe
286
  except Exception as fallback_error:
287
  print(f"❌ Auch Fallback fehlgeschlagen: {fallback_error}")
288
  raise
289
- finally:
290
- with _loading_lock:
291
- _current_loading_model = None
292
-
293
- # === LAZY LOADING FÜR IMG2IMG ===
294
- _img2img_pipe = None
295
- _img2img_lock = threading.Lock()
296
 
297
- def get_img2img_pipe():
298
- """Lazy Loading für Img2Img Pipeline mit Thread-Sicherheit"""
299
- global _img2img_pipe
300
-
301
- if _img2img_pipe is not None:
302
- return _img2img_pipe
303
-
304
- with _img2img_lock:
305
- if _img2img_pipe is not None: # Double-check locking
306
- return _img2img_pipe
307
-
308
- print("🔄 Lade Inpainting-Modell...")
309
  try:
310
- _img2img_pipe = StableDiffusionInpaintPipeline.from_pretrained(
311
  "runwayml/stable-diffusion-inpainting",
312
  torch_dtype=torch_dtype,
 
313
  safety_checker=None,
314
  ).to(device)
315
-
316
- _img2img_pipe.enable_attention_slicing()
317
- _img2img_pipe.enable_vae_tiling()
318
-
319
- print("✅ Inpainting-Modell geladen")
320
  except Exception as e:
321
- print(f"Fehler beim Laden des Inpainting-Modells: {e}")
322
  raise
323
-
324
- return _img2img_pipe
325
-
326
- # === OPTIMIERTE PIPELINE FUNKTIONEN ===
327
- def load_txt2img(model_id):
328
- """Lädt das Text-to-Image Modell aus Cache oder neu"""
329
- return load_model_with_cache(model_id)
330
 
331
- def load_img2img():
332
- """Lädt Img2Img Pipeline mit Lazy Loading"""
333
- return get_img2img_pipe()
334
-
335
- # === ASYNCHRONE MODELL-VORLADUNG BEI TAB-WECHSEL ===
336
- class ModelPreloader:
337
- """Asynchrones Vorladen von Modellen bei Tab-Aktivierung"""
338
- def __init__(self):
339
- self.queue = Queue()
340
- self.worker_thread = None
341
- self.stop_flag = False
342
 
343
- def start(self):
344
- """Startet den Worker-Thread"""
345
- self.worker_thread = threading.Thread(target=self._worker, daemon=True)
346
- self.worker_thread.start()
347
- print("✅ ModelPreloader gestartet")
348
-
349
- def stop(self):
350
- """Stoppt den Worker-Thread"""
351
- self.stop_flag = True
352
- if self.worker_thread:
353
- self.worker_thread.join(timeout=1.0)
354
-
355
- def schedule_preload(self, model_id):
356
- """Plant das Vorladen eines Modells"""
357
- if model_id not in _model_cache:
358
- self.queue.put(model_id)
359
-
360
- def _worker(self):
361
- """Worker-Thread für asynchrones Laden"""
362
- while not self.stop_flag:
363
- try:
364
- model_id = self.queue.get(timeout=0.5)
365
- if model_id:
366
- try:
367
- print(f"⚡ Vorlade Modell: {model_id}")
368
- load_model_with_cache(model_id)
369
- except Exception as e:
370
- print(f"⚠️ Vorladen von {model_id} fehlgeschlagen: {e}")
371
- except Empty:
372
- continue
373
- except Exception as e:
374
- print(f"⚠️ Fehler im Preloader: {e}")
375
 
376
- # Preloader initialisieren
377
- model_preloader = ModelPreloader()
378
- model_preloader.start()
379
 
380
  # === NEUE CALLBACK-FUNKTIONEN FÜR FORTSCHRITT ===
381
  class TextToImageProgressCallback:
@@ -650,25 +596,9 @@ def update_model_settings(model_id):
650
  return (
651
  config["recommended_steps"], # steps
652
  config["recommended_cfg"], # guidance_scale
653
- f"📊 Empfohlene Einstellungen: {config['recommended_steps']} Steps, CFG {config['recommended_cfg']}"
654
  )
655
 
656
- # === TAB-WECHSEL HANDLER ===
657
- def on_tab_change(tab_name):
658
- """Wird aufgerufen wenn Tab gewechselt wird"""
659
- print(f"📌 Tab gewechselt zu: {tab_name}")
660
-
661
- if tab_name == "Text zu Bild":
662
- # Vorlade das aktuell ausgewählte Modell
663
- model_id = "runwayml/stable-diffusion-v1-5" # Standardmodell
664
- model_preloader.schedule_preload(model_id)
665
-
666
- elif tab_name == "Bild zu Bild":
667
- # Img2Img Modell im Hintergrund laden
668
- threading.Thread(target=get_img2img_pipe, daemon=True).start()
669
-
670
- return tab_name
671
-
672
  def main_ui():
673
  with gr.Blocks(
674
  title="AI Image Generator",
@@ -763,290 +693,253 @@ def main_ui():
763
  color: #721c24;
764
  border: 1px solid #f5c6cb;
765
  }
766
- .tab-nav {
767
- padding: 10px 0;
768
- }
769
- .tab-nav button {
770
- transition: all 0.3s ease;
771
- }
772
- .tab-nav button:hover {
773
- transform: translateY(-2px);
774
- }
775
  """
776
  ) as demo:
777
 
778
- # Tab-Status Tracking
779
- current_tab = gr.State(value="Text zu Bild")
780
-
781
- with gr.Tab("Text zu Bild") as txt_tab:
782
- gr.Markdown("## 🎨 Text zu Bild Generator")
783
-
784
- with gr.Row():
785
- with gr.Column(scale=2):
786
- # Modellauswahl Dropdown (NUR 2 MODELLE)
787
- model_dropdown = gr.Dropdown(
788
- choices=[
789
- (config["name"], model_id)
790
- for model_id, config in MODEL_CONFIGS.items()
791
- ],
792
- value="runwayml/stable-diffusion-v1-5",
793
- label="📁 Modellauswahl",
794
- info="🏠 Universal vs 👤 Portraits"
795
- )
796
-
797
- # Modellinformationen Box
798
- model_info_box = gr.Markdown(
799
- value="<div class='model-info-box'>"
800
- "**🏠 Stable Diffusion 1.5 (Universal)**<br>"
801
- "Universal model, good all-rounder, reliable results<br>"
802
- "Empfohlene Einstellungen: 35 Steps, CFG 7.5"
803
- "</div>",
804
- label="Modellinformationen"
805
- )
806
-
807
- with gr.Column(scale=3):
808
- txt_input = gr.Textbox(
809
- placeholder="z.B. ultra realistic mountain landscape at sunrise, soft mist over the valley, detailed foliage, crisp textures, depth of field, sunlight rays through clouds, shot on medium format camera, 8k, HDR, hyper-detailed, natural lighting, masterpiece",
810
- lines=3,
811
- label="🎯 Prompt (Englisch)",
812
- info="Beschreibe detailliert, was du sehen möchtest. Negative Prompts werden automatisch generiert."
813
- )
814
-
815
- with gr.Row():
816
- with gr.Column():
817
- txt_steps = gr.Slider(
818
- minimum=10, maximum=100, value=35, step=1,
819
- label="⚙️ Inferenz-Schritte",
820
- info="Mehr Schritte = bessere Qualität, aber langsamer (20-50 empfohlen)"
821
- )
822
- with gr.Column():
823
- txt_guidance = gr.Slider(
824
- minimum=1.0, maximum=20.0, value=7.5, step=0.5,
825
- label="🎛️ Prompt-Stärke (CFG Scale)",
826
- info="Wie stark der Prompt befolgt wird (7-12 für gute Balance)"
827
- )
828
-
829
- # Status-Nachricht
830
- status_output = gr.Markdown(
831
- value="",
832
- elem_classes="status-message"
833
- )
834
-
835
- generate_btn = gr.Button("🚀 Bild generieren", variant="primary", elem_id="generate-button")
836
-
837
- with gr.Row():
838
- txt_output = gr.Image(
839
- label="🖼️ Generiertes Bild",
840
- show_download_button=True,
841
- type="pil",
842
- height=400
843
  )
844
-
845
- # Event-Handler für Modelländerung mit Vorladen
846
- def on_model_select(model_id):
847
- # Vorlade das ausgewählte Modell im Hintergrund
848
- model_preloader.schedule_preload(model_id)
849
- config = MODEL_CONFIGS.get(model_id, MODEL_CONFIGS["runwayml/stable-diffusion-v1-5"])
850
- info_html = f"""
851
- <div class='model-info-box'>
852
- <strong>{config['name']}</strong><br>
853
- {config['description']}<br>
854
- <em>Empfohlene Einstellungen: {config['recommended_steps']} Steps, CFG {config['recommended_cfg']}</em>
855
- </div>
856
- """
857
- return info_html, config["recommended_steps"], config["recommended_cfg"]
858
-
859
- model_dropdown.change(
860
- fn=on_model_select,
861
- inputs=[model_dropdown],
862
- outputs=[model_info_box, txt_steps, txt_guidance],
863
- queue=False # Wichtig: Keine Warteschlange für dieses Event
864
- )
865
-
866
- generate_btn.click(
867
- fn=text_to_image,
868
- inputs=[txt_input, model_dropdown, txt_steps, txt_guidance],
869
- outputs=[txt_output, status_output],
870
- concurrency_limit=1
871
- )
872
-
873
- with gr.Tab("Bild zu Bild") as img_tab:
874
- gr.Markdown("## 🖼️ Bild zu Bild Transformation")
875
-
876
- with gr.Row():
877
- with gr.Column():
878
- img_input = gr.Image(
879
- type="pil",
880
- label="📤 Eingabebild",
881
- height=300,
882
- sources=["upload"],
883
- elem_id="image-upload"
884
  )
885
- with gr.Column():
886
- preview_output = gr.Image(
887
- label="🎯 Live-Vorschau mit Maske",
888
- height=300,
889
- interactive=False,
890
- show_download_button=False
891
- )
892
-
893
- with gr.Row():
894
- face_preserve = gr.Checkbox(
895
- label="🛡️ Schutzmodus",
896
- value=True,
897
- info="🟢 AN: Alles AUSSERHALB des gelben Rahmens verändern | 🔴 AUS: Nur INNERHALB des gelben Rahmens verändern"
 
 
 
 
898
  )
899
-
900
- with gr.Row():
901
- gr.Markdown("### 📐 Bildelementbereich anpassen")
902
-
903
- with gr.Row():
904
- with gr.Column():
905
- bbox_x1 = gr.Slider(
906
- label="← Links (x1)",
907
- minimum=0, maximum=512, value=100, step=1,
908
- info="Linke Kante des Bildelementbereichs"
909
- )
910
- with gr.Column():
911
- bbox_y1 = gr.Slider(
912
- label="↑ Oben (y1)",
913
- minimum=0, maximum=512, value=100, step=1,
914
- info="Obere Kante des Bildelementbereichs"
915
- )
916
- with gr.Row():
917
- with gr.Column():
918
- bbox_x2 = gr.Slider(
919
- label="→ Rechts (x2)",
920
- minimum=0, maximum=512, value=300, step=1,
921
- info="Rechte Kante des Bildelementbereichs"
922
- )
923
- with gr.Column():
924
- bbox_y2 = gr.Slider(
925
- label="↓ Unten (y2)",
926
- minimum=0, maximum=512, value=300, step=1,
927
- info="Untere Kante des Bildelementbereichs"
928
- )
929
-
930
- with gr.Row():
931
- with gr.Column():
932
- img_prompt = gr.Textbox(
933
- placeholder="change background to beach with palm trees, keep person unchanged, sunny day",
934
- lines=2,
935
- label="🎯 Transformations-Prompt (Englisch)",
936
- info="Was soll verändert werden? Sei spezifisch."
937
- )
938
- with gr.Column():
939
- img_neg_prompt = gr.Textbox(
940
- placeholder="blurry, deformed, ugly, bad anatomy, extra limbs, poorly drawn hands",
941
- lines=2,
942
- label="🚫 Negativ-Prompt (Englisch)",
943
- info="Was soll vermieden werden? Unerwünschte Elemente auflisten."
944
- )
945
-
946
- with gr.Row():
947
- with gr.Column():
948
- strength_slider = gr.Slider(
949
- minimum=0.1, maximum=0.9, value=0.4, step=0.05,
950
- label="💪 Veränderungs-Stärke",
951
- info="0.1-0.3: Leichte Anpassungen, 0.4-0.6: Mittlere Veränderungen, 0.7-0.9: Starke Umgestaltung"
952
  )
953
- with gr.Column():
954
- img_steps = gr.Slider(
955
- minimum=10, maximum=100, value=35, step=1,
956
- label="⚙️ Inferenz-Schritte",
957
- info="Anzahl der Verarbeitungsschritte (25-45 für gute Ergebnisse)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
958
  )
959
- with gr.Column():
960
- img_guidance = gr.Slider(
961
- minimum=1.0, maximum=20.0, value=7.5, step=0.5,
962
- label="🎛️ Prompt-Stärke",
963
- info="Einfluss des Prompts auf das Ergebnis (6-10 für natürliche Ergebnisse)"
 
 
 
 
964
  )
965
-
966
- with gr.Row():
967
- gr.Markdown(
968
- "### 📋 Hinweise:\n"
969
- "• **🆕 Automatische Bildelementerkennung** setzt Koordinaten beim Upload\n"
970
- "• **🆕 Live-Vorschau** zeigt farbige Rahmen je nach Modus (🔴 Rot / 🟢 Grün)\n"
971
- "• **🆕 Koordinaten-Schieberegler** für präzise Anpassung mit Live-Update\n"
972
- "• **Koordinaten nur bei erkennbaren Verzerrungen anpassen** (Bereiche leicht verschieben)"
973
- )
974
-
975
- transform_btn = gr.Button("🔄 Bild transformieren", variant="primary")
976
-
977
- with gr.Row():
978
- img_output = gr.Image(
979
- label="✨ Transformiertes Bild",
980
- show_download_button=True,
981
- type="pil",
982
- height=400
983
  )
984
-
985
- img_input.change(
986
- fn=process_image_upload,
987
- inputs=[img_input],
988
- outputs=[preview_output, bbox_x1, bbox_y1, bbox_x2, bbox_y2]
989
- )
990
-
991
- coordinate_inputs = [img_input, bbox_x1, bbox_y1, bbox_x2, bbox_y2, face_preserve]
992
-
993
- for slider in [bbox_x1, bbox_y1, bbox_x2, bbox_y2]:
994
- slider.change(
995
  fn=update_live_preview,
996
  inputs=coordinate_inputs,
997
  outputs=preview_output
998
  )
999
-
1000
- face_preserve.change(
1001
- fn=update_live_preview,
1002
- inputs=coordinate_inputs,
1003
- outputs=preview_output
1004
- )
1005
-
1006
- transform_btn.click(
1007
- fn=img_to_image,
1008
- inputs=[
1009
- img_input, img_prompt, img_neg_prompt,
1010
- strength_slider, img_steps, img_guidance,
1011
- face_preserve, bbox_x1, bbox_y1, bbox_x2, bbox_y2
1012
- ],
1013
- outputs=img_output,
1014
- concurrency_limit=1
1015
- )
1016
-
1017
 
1018
- def handle_tab_switch():
1019
- """Leere Funktion nur um Tab-Wechsel zu registrieren"""
1020
- return
1021
-
1022
- # Füge diese Event-Handler hinzu:
1023
- txt_tab.select(fn=handle_tab_switch, queue=False)
1024
- img_tab.select(fn=handle_tab_switch, queue=False)
1025
-
1026
- # === ENDE TAB WECHSEL OPTIMIERUNG ===
1027
-
1028
- # Queue mit Load-Balancing konfigurieren
1029
- demo.queue(max_size=2, default_concurrency_limit=1, api_open=False)
1030
-
1031
- return demo
1032
-
1033
 
1034
  if __name__ == "__main__":
1035
- import atexit
1036
-
1037
- # Cleanup-Handler
1038
- @atexit.register
1039
- def cleanup():
1040
- model_preloader.stop()
1041
- torch.cuda.empty_cache() if torch.cuda.is_available() else None
1042
- print("🧹 Cleanup durchgeführt")
1043
-
1044
  demo = main_ui()
 
1045
  demo.launch(
1046
  server_name="0.0.0.0",
1047
  server_port=7860,
1048
  max_file_size="10MB",
1049
  show_error=True,
1050
  share=False,
1051
- ssl_verify=False
1052
  )
 
9
  import os
10
  import tempfile
11
  import random
 
 
 
12
 
13
  # === OPTIMIERTE EINSTELLUNGEN ===
14
  device = "cuda" if torch.cuda.is_available() else "cpu"
 
41
  # === SAFETENSORS KONFIGURATION ===
42
  SAFETENSORS_MODELS = ["runwayml/stable-diffusion-v1-5"]
43
 
44
+ # Aktuell ausgewähltes Modell (wird vom User gesetzt)
45
+ current_model_id = "runwayml/stable-diffusion-v1-5"
 
 
 
46
 
47
  # === AUTOMATISCHE NEGATIVE PROMPT GENERIERUNG ===
48
  def auto_negative_prompt(positive_prompt):
 
139
  print(f"Geschätzte Gesichtskoordinaten: [{x1}, {y1}, {x2}, {y2}]")
140
  return [x1, y1, x2, y2]
141
 
142
+ # === PIPELINES ===
143
+ pipe_txt2img = None
144
+ current_pipe_model_id = None
145
+ pipe_img2img = None
146
+
147
+ def load_txt2img(model_id):
148
+ """Lädt das Text-to-Image Modell basierend auf der Auswahl"""
149
+ global pipe_txt2img, current_pipe_model_id
150
 
151
+ # Wenn bereits das richtige Modell geladen ist, nichts tun
152
+ if pipe_txt2img is not None and current_pipe_model_id == model_id:
153
+ print(f"✅ Modell {model_id} bereits geladen")
154
+ return pipe_txt2img
 
155
 
156
+ print(f"🔄 Lade Modell: {model_id}")
157
+
158
+ config = MODEL_CONFIGS.get(model_id, MODEL_CONFIGS["runwayml/stable-diffusion-v1-5"])
159
+ print(f"📋 Modell-Konfiguration: {config['name']}")
160
+ print(f"📝 Beschreibung: {config['description']}")
 
 
 
 
161
 
162
  try:
163
+ # VAE-Handling basierend auf Modellkonfiguration
 
 
 
 
 
164
  vae = None
165
  if config.get("requires_vae", False):
166
  print(f"🔧 Lade externe VAE: {config['vae_model']}")
 
172
  print("✅ VAE erfolgreich geladen")
173
  except Exception as vae_error:
174
  print(f"⚠️ Fehler beim Laden der VAE: {vae_error}")
175
+ print("ℹ️ Versuche ohne VAE weiter...")
176
  vae = None
177
 
178
+ # Modellparameter basierend auf Modelltyp
179
  model_params = {
180
  "torch_dtype": torch_dtype,
181
  "safety_checker": None,
182
  "requires_safety_checker": False,
183
  "add_watermarker": False,
184
+ "allow_pickle": True, # Für .bin Modelle wichtig
185
  }
186
 
187
  # SAFETENSORS LOGIK
 
192
  model_params["use_safetensors"] = False
193
  print(f"ℹ️ Verwende .bin weights für {model_id}")
194
 
195
+ # FP16 Variante nur wenn Modell sie unterstützt UND wir auf GPU sind
196
  if config.get("supports_fp16", False) and torch_dtype == torch.float16:
197
  model_params["variant"] = "fp16"
198
  print("ℹ️ Verwende FP16 Variante")
199
+ else:
200
+ print("ℹ️ Verwende Standard Variante (kein FP16)")
201
 
202
+ # VAE nur wenn nicht None
203
  if vae is not None:
204
  model_params["vae"] = vae
205
 
206
+ print(f"📥 Lade Hauptmodell von Hugging Face...")
207
+ pipe_txt2img = StableDiffusionPipeline.from_pretrained(
208
  model_id,
209
  **model_params
210
  ).to(device)
211
 
212
+ # SICHERER SCHEDULER-HANDLING
213
+ print("⚙️ Konfiguriere Scheduler...")
214
+
215
+ # Prüfe ob Scheduler existiert
216
+ if pipe_txt2img.scheduler is None:
217
  print("⚠️ Scheduler ist None, setze Standard-Scheduler")
218
+ pipe_txt2img.scheduler = PNDMScheduler.from_pretrained(
219
  model_id,
220
  subfolder="scheduler"
221
  )
222
 
223
+ # Versuche DPM-Solver zu verwenden (bessere Ergebnisse)
224
  try:
225
+ # Hole die Scheduler-Konfiguration
226
+ if hasattr(pipe_txt2img.scheduler, 'config'):
227
+ scheduler_config = pipe_txt2img.scheduler.config
228
+ else:
229
+ # Fallback-Konfiguration für Scheduler
230
+ scheduler_config = {
231
+ "beta_start": 0.00085,
232
+ "beta_end": 0.012,
233
+ "beta_schedule": "scaled_linear",
234
+ "num_train_timesteps": 1000,
235
+ "prediction_type": "epsilon",
236
+ "steps_offset": 1
237
+ }
238
+ print("⚠️ Keine Scheduler-Konfig gefunden, verwende Standard")
239
 
240
+ # Setze DPM-Solver Scheduler
241
+ pipe_txt2img.scheduler = DPMSolverMultistepScheduler.from_config(
242
  scheduler_config,
243
  use_karras_sigmas=True,
244
  algorithm_type="sde-dpmsolver++"
245
  )
246
  print("✅ DPM-Solver Multistep Scheduler konfiguriert")
247
+
248
+ except Exception as scheduler_error:
249
+ print(f"⚠️ Konnte DPM-Scheduler nicht setzen: {scheduler_error}")
250
+ print("ℹ️ Verwende Standard-Scheduler weiter")
251
 
252
+ # Optimierungen
253
+ pipe_txt2img.enable_attention_slicing()
254
+ print("✅ Attention Slicing aktiviert")
255
 
256
+ # VAE Slicing nur wenn VAE existiert
257
+ if hasattr(pipe_txt2img, 'vae') and pipe_txt2img.vae is not None:
258
  try:
259
+ pipe_txt2img.enable_vae_slicing()
260
+ if hasattr(pipe_txt2img.vae, 'enable_slicing'):
261
+ pipe_txt2img.vae.enable_slicing()
262
  print("✅ VAE Slicing aktiviert")
263
+ except Exception as vae_slice_error:
264
+ print(f"⚠️ VAE Slicing nicht möglich: {vae_slice_error}")
 
 
 
 
265
 
266
+ current_pipe_model_id = model_id
267
+ print(f"✅ {config['name']} erfolgreich geladen")
268
+ print(f"📊 Modell-Dtype: {pipe_txt2img.dtype}")
269
+ print(f"📊 Scheduler: {type(pipe_txt2img.scheduler).__name__}")
270
+ print(f"⚙️ Empfohlene Einstellungen: Steps={config['recommended_steps']}, CFG={config['recommended_cfg']}")
271
 
272
+ return pipe_txt2img
273
 
274
  except Exception as e:
275
+ print(f"❌ Fehler beim Laden von {model_id}: {str(e)[:200]}...")
276
  import traceback
277
  traceback.print_exc()
278
+ print("🔄 Fallback auf SD 1.5...")
279
 
280
+ # Fallback auf Standard SD 1.5
281
  try:
282
+ pipe_txt2img = StableDiffusionPipeline.from_pretrained(
 
283
  "runwayml/stable-diffusion-v1-5",
284
  torch_dtype=torch_dtype,
285
+ use_safetensors=True,
286
  ).to(device)
287
+ pipe_txt2img.enable_attention_slicing()
288
+ current_pipe_model_id = "runwayml/stable-diffusion-v1-5"
289
+ print("✅ Fallback auf SD 1.5 erfolgreich")
 
290
 
291
+ return pipe_txt2img
292
  except Exception as fallback_error:
293
  print(f"❌ Auch Fallback fehlgeschlagen: {fallback_error}")
294
  raise
 
 
 
 
 
 
 
295
 
296
+ def load_img2img():
297
+ global pipe_img2img
298
+ if pipe_img2img is None:
299
+ print("Loading Inpainting model...")
 
 
 
 
 
 
 
 
300
  try:
301
+ pipe_img2img = StableDiffusionInpaintPipeline.from_pretrained(
302
  "runwayml/stable-diffusion-inpainting",
303
  torch_dtype=torch_dtype,
304
+ allow_pickle=False,
305
  safety_checker=None,
306
  ).to(device)
 
 
 
 
 
307
  except Exception as e:
308
+ print(f"Fehler beim Laden des Inpainting-Modells: {e}")
309
  raise
 
 
 
 
 
 
 
310
 
311
+ from diffusers import DPMSolverMultistepScheduler
312
+ pipe_img2img.scheduler = DPMSolverMultistepScheduler.from_config(
313
+ pipe_img2img.scheduler.config,
314
+ algorithm_type="sde-dpmsolver++",
315
+ use_karras_sigmas=True,
316
+ timestep_spacing="trailing"
317
+ )
 
 
 
 
318
 
319
+ pipe_img2img.enable_attention_slicing()
320
+ pipe_img2img.enable_vae_tiling()
321
+ if hasattr(pipe_img2img, 'vae_slicing'):
322
+ pipe_img2img.vae_slicing = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
+ return pipe_img2img
 
 
325
 
326
  # === NEUE CALLBACK-FUNKTIONEN FÜR FORTSCHRITT ===
327
  class TextToImageProgressCallback:
 
596
  return (
597
  config["recommended_steps"], # steps
598
  config["recommended_cfg"], # guidance_scale
599
+ f"📊 Empfohlene Einstellungen: {config['steps']} Steps, CFG {config['cfg']}"
600
  )
601
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  def main_ui():
603
  with gr.Blocks(
604
  title="AI Image Generator",
 
693
  color: #721c24;
694
  border: 1px solid #f5c6cb;
695
  }
 
 
 
 
 
 
 
 
 
696
  """
697
  ) as demo:
698
 
699
+ with gr.Column(visible=True) as content_area:
700
+ with gr.Tab("Text zu Bild"):
701
+ gr.Markdown("## 🎨 Text zu Bild Generator")
702
+
703
+ with gr.Row():
704
+ with gr.Column(scale=2):
705
+ # Modellauswahl Dropdown (NUR 2 MODELLE)
706
+ model_dropdown = gr.Dropdown(
707
+ choices=[
708
+ (config["name"], model_id)
709
+ for model_id, config in MODEL_CONFIGS.items()
710
+ ],
711
+ value="runwayml/stable-diffusion-v1-5",
712
+ label="📁 Modellauswahl",
713
+ info="🏠 Universal vs 👤 Portraits"
714
+ )
715
+
716
+ # Modellinformationen Box
717
+ model_info_box = gr.Markdown(
718
+ value="<div class='model-info-box'>"
719
+ "**🏠 Stable Diffusion 1.5 (Universal)**<br>"
720
+ "Universal model, good all-rounder, reliable results<br>"
721
+ "Empfohlene Einstellungen: 35 Steps, CFG 7.5"
722
+ "</div>",
723
+ label="Modellinformationen"
724
+ )
725
+
726
+ with gr.Column(scale=3):
727
+ txt_input = gr.Textbox(
728
+ placeholder="z.B. ultra realistic mountain landscape at sunrise, soft mist over the valley, detailed foliage, crisp textures, depth of field, sunlight rays through clouds, shot on medium format camera, 8k, HDR, hyper-detailed, natural lighting, masterpiece",
729
+ lines=3,
730
+ label="🎯 Prompt (Englisch)",
731
+ info="Beschreibe detailliert, was du sehen möchtest. Negative Prompts werden automatisch generiert."
732
+ )
733
+
734
+ with gr.Row():
735
+ with gr.Column():
736
+ txt_steps = gr.Slider(
737
+ minimum=10, maximum=100, value=35, step=1,
738
+ label="⚙️ Inferenz-Schritte",
739
+ info="Mehr Schritte = bessere Qualität, aber langsamer (20-50 empfohlen)"
740
+ )
741
+ with gr.Column():
742
+ txt_guidance = gr.Slider(
743
+ minimum=1.0, maximum=20.0, value=7.5, step=0.5,
744
+ label="🎛️ Prompt-Stärke (CFG Scale)",
745
+ info="Wie stark der Prompt befolgt wird (7-12 für gute Balance)"
746
+ )
747
+
748
+ # Status-Nachricht
749
+ status_output = gr.Markdown(
750
+ value="",
751
+ elem_classes="status-message"
 
 
 
 
 
 
 
 
 
 
 
 
752
  )
753
+
754
+ generate_btn = gr.Button("🚀 Bild generieren", variant="primary", elem_id="generate-button")
755
+
756
+ with gr.Row():
757
+ txt_output = gr.Image(
758
+ label="🖼️ Generiertes Bild",
759
+ show_download_button=True,
760
+ type="pil",
761
+ height=400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
762
  )
763
+
764
+ # Event-Handler für Modelländerung
765
+ def update_model_info(model_id):
766
+ config = MODEL_CONFIGS.get(model_id, MODEL_CONFIGS["runwayml/stable-diffusion-v1-5"])
767
+ info_html = f"""
768
+ <div class='model-info-box'>
769
+ <strong>{config['name']}</strong><br>
770
+ {config['description']}<br>
771
+ <em>Empfohlene Einstellungen: {config['recommended_steps']} Steps, CFG {config['recommended_cfg']}</em>
772
+ </div>
773
+ """
774
+ return info_html, config["recommended_steps"], config["recommended_cfg"]
775
+
776
+ model_dropdown.change(
777
+ fn=update_model_info,
778
+ inputs=[model_dropdown],
779
+ outputs=[model_info_box, txt_steps, txt_guidance]
780
  )
781
+
782
+ generate_btn.click(
783
+ fn=text_to_image,
784
+ inputs=[txt_input, model_dropdown, txt_steps, txt_guidance],
785
+ outputs=[txt_output, status_output],
786
+ concurrency_limit=1
787
+ )
788
+
789
+ with gr.Tab("Bild zu Bild"):
790
+ gr.Markdown("## 🖼️ Bild zu Bild Transformation")
791
+
792
+ with gr.Row():
793
+ with gr.Column():
794
+ img_input = gr.Image(
795
+ type="pil",
796
+ label="📤 Eingabebild",
797
+ height=300,
798
+ sources=["upload"],
799
+ elem_id="image-upload"
800
+ )
801
+ with gr.Column():
802
+ preview_output = gr.Image(
803
+ label="🎯 Live-Vorschau mit Maske",
804
+ height=300,
805
+ interactive=False,
806
+ show_download_button=False
807
+ )
808
+
809
+ with gr.Row():
810
+ face_preserve = gr.Checkbox(
811
+ label="🛡️ Schutzmodus",
812
+ value=True,
813
+ info="🟢 AN: Alles AUSSERHALB des gelben Rahmens verändern | 🔴 AUS: Nur INNERHALB des gelben Rahmens verändern"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
814
  )
815
+
816
+ with gr.Row():
817
+ gr.Markdown("### 📐 Bildelementbereich anpassen")
818
+
819
+ with gr.Row():
820
+ with gr.Column():
821
+ bbox_x1 = gr.Slider(
822
+ label="← Links (x1)",
823
+ minimum=0, maximum=512, value=100, step=1,
824
+ info="Linke Kante des Bildelementbereichs"
825
+ )
826
+ with gr.Column():
827
+ bbox_y1 = gr.Slider(
828
+ label="↑ Oben (y1)",
829
+ minimum=0, maximum=512, value=100, step=1,
830
+ info="Obere Kante des Bildelementbereichs"
831
+ )
832
+ with gr.Row():
833
+ with gr.Column():
834
+ bbox_x2 = gr.Slider(
835
+ label="→ Rechts (x2)",
836
+ minimum=0, maximum=512, value=300, step=1,
837
+ info="Rechte Kante des Bildelementbereichs"
838
+ )
839
+ with gr.Column():
840
+ bbox_y2 = gr.Slider(
841
+ label="↓ Unten (y2)",
842
+ minimum=0, maximum=512, value=300, step=1,
843
+ info="Untere Kante des Bildelementbereichs"
844
+ )
845
+
846
+ with gr.Row():
847
+ with gr.Column():
848
+ img_prompt = gr.Textbox(
849
+ placeholder="change background to beach with palm trees, keep person unchanged, sunny day",
850
+ lines=2,
851
+ label="🎯 Transformations-Prompt (Englisch)",
852
+ info="Was soll verändert werden? Sei spezifisch."
853
+ )
854
+ with gr.Column():
855
+ img_neg_prompt = gr.Textbox(
856
+ placeholder="blurry, deformed, ugly, bad anatomy, extra limbs, poorly drawn hands",
857
+ lines=2,
858
+ label="🚫 Negativ-Prompt (Englisch)",
859
+ info="Was soll vermieden werden? Unerwünschte Elemente auflisten."
860
+ )
861
+
862
+ with gr.Row():
863
+ with gr.Column():
864
+ strength_slider = gr.Slider(
865
+ minimum=0.1, maximum=0.9, value=0.4, step=0.05,
866
+ label="💪 Veränderungs-Stärke",
867
+ info="0.1-0.3: Leichte Anpassungen, 0.4-0.6: Mittlere Veränderungen, 0.7-0.9: Starke Umgestaltung"
868
+ )
869
+ with gr.Column():
870
+ img_steps = gr.Slider(
871
+ minimum=10, maximum=100, value=35, step=1,
872
+ label="⚙️ Inferenz-Schritte",
873
+ info="Anzahl der Verarbeitungsschritte (25-45 für gute Ergebnisse)"
874
+ )
875
+ with gr.Column():
876
+ img_guidance = gr.Slider(
877
+ minimum=1.0, maximum=20.0, value=7.5, step=0.5,
878
+ label="🎛️ Prompt-Stärke",
879
+ info="Einfluss des Prompts auf das Ergebnis (6-10 für natürliche Ergebnisse)"
880
+ )
881
+
882
+ with gr.Row():
883
+ gr.Markdown(
884
+ "### 📋 Hinweise:\n"
885
+ "• **🆕 Automatische Bildelementerkennung** setzt Koordinaten beim Upload\n"
886
+ "• **🆕 Live-Vorschau** zeigt farbige Rahmen je nach Modus (🔴 Rot / 🟢 Grün)\n"
887
+ "• **🆕 Koordinaten-Schieberegler** für präzise Anpassung mit Live-Update\n"
888
+ "• **Koordinaten nur bei erkennbaren Verzerrungen anpassen** (Bereiche leicht verschieben)"
889
  )
890
+
891
+ transform_btn = gr.Button("🔄 Bild transformieren", variant="primary")
892
+
893
+ with gr.Row():
894
+ img_output = gr.Image(
895
+ label="✨ Transformiertes Bild",
896
+ show_download_button=True,
897
+ type="pil",
898
+ height=400
899
  )
900
+
901
+ img_input.change(
902
+ fn=process_image_upload,
903
+ inputs=[img_input],
904
+ outputs=[preview_output, bbox_x1, bbox_y1, bbox_x2, bbox_y2]
 
 
 
 
 
 
 
 
 
 
 
 
 
905
  )
906
+
907
+ coordinate_inputs = [img_input, bbox_x1, bbox_y1, bbox_x2, bbox_y2, face_preserve]
908
+
909
+ for slider in [bbox_x1, bbox_y1, bbox_x2, bbox_y2]:
910
+ slider.change(
911
+ fn=update_live_preview,
912
+ inputs=coordinate_inputs,
913
+ outputs=preview_output
914
+ )
915
+
916
+ face_preserve.change(
917
  fn=update_live_preview,
918
  inputs=coordinate_inputs,
919
  outputs=preview_output
920
  )
921
+
922
+ transform_btn.click(
923
+ fn=img_to_image,
924
+ inputs=[
925
+ img_input, img_prompt, img_neg_prompt,
926
+ strength_slider, img_steps, img_guidance,
927
+ face_preserve, bbox_x1, bbox_y1, bbox_x2, bbox_y2
928
+ ],
929
+ outputs=img_output,
930
+ concurrency_limit=1
931
+ )
 
 
 
 
 
 
 
932
 
933
+ return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
934
 
935
  if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
936
  demo = main_ui()
937
+ demo.queue(max_size=3)
938
  demo.launch(
939
  server_name="0.0.0.0",
940
  server_port=7860,
941
  max_file_size="10MB",
942
  show_error=True,
943
  share=False,
944
+ ssr_mode=False # SSR deaktivieren für Stabilität
945
  )