Astridkraft commited on
Commit
9ad17b9
·
verified ·
1 Parent(s): faad7d4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +453 -325
app.py CHANGED
@@ -9,6 +9,9 @@ import time
9
  import os
10
  import tempfile
11
  import random
 
 
 
12
 
13
  # === OPTIMIERTE EINSTELLUNGEN ===
14
  device = "cuda" if torch.cuda.is_available() else "cpu"
@@ -41,8 +44,11 @@ MODEL_CONFIGS = {
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,28 +145,34 @@ def auto_detect_face_area(image):
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,16 +184,15 @@ def load_txt2img(model_id):
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,136 +203,179 @@ def load_txt2img(model_id):
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,9 +650,25 @@ def update_model_settings(model_id):
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,253 +763,311 @@ def main_ui():
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
  )
 
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
  # === 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
  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
  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
  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
  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
  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
+ # Tab-Wechsel Event-Handler
1018
+ def update_current_tab(selected_tab):
1019
+ # Konvertiere Tab-Objekt zu Namen
1020
+ tab_name = "Text zu Bild" if selected_tab == 0 else "Bild zu Bild"
1021
+ on_tab_change(tab_name)
1022
+ return tab_name
1023
+
1024
+ # Event für Tab-Wechsel
1025
+ demo.load(
1026
+ fn=lambda: "Text zu Bild",
1027
+ outputs=current_tab
1028
+ )
1029
+
1030
+ # Tab-Wechsel verfolgen
1031
+ txt_tab.select(
1032
+ fn=lambda: update_current_tab(0),
1033
+ outputs=current_tab,
1034
+ queue=False
1035
+ )
1036
+
1037
+ img_tab.select(
1038
+ fn=lambda: update_current_tab(1),
1039
+ outputs=current_tab,
1040
+ queue=False
1041
+ )
1042
+
1043
+ # Queue mit Load-Balancing konfigurieren
1044
+ demo.queue(
1045
+ max_size=2, # Reduziere max_size für besseres Load-Balancing
1046
+ default_concurrency_limit=1,
1047
+ api_open=False
1048
+ )
1049
+
1050
  return demo
1051
 
1052
  if __name__ == "__main__":
1053
+ import atexit
1054
+
1055
+ # Cleanup-Handler
1056
+ @atexit.register
1057
+ def cleanup():
1058
+ model_preloader.stop()
1059
+ torch.cuda.empty_cache() if torch.cuda.is_available() else None
1060
+ print("🧹 Cleanup durchgeführt")
1061
+
1062
  demo = main_ui()
 
1063
  demo.launch(
1064
  server_name="0.0.0.0",
1065
  server_port=7860,
1066
  max_file_size="10MB",
1067
  show_error=True,
1068
  share=False,
1069
+ # Wichtig für T4 Optimierungen
1070
+ enable_queue=True,
1071
+ prevent_thread_lock=True,
1072
+ ssl_verify=False
1073
  )