Files changed (2) hide show
  1. app_local.py +110 -129
  2. presets.py +7 -134
app_local.py CHANGED
@@ -15,8 +15,8 @@ import math
15
  import json # Added json import
16
  from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
17
  import logging
18
- import copy
19
  from copy import deepcopy
 
20
  #############################
21
  os.environ.setdefault('GRADIO_ANALYTICS_ENABLED', 'False')
22
  os.environ.setdefault('HF_HUB_DISABLE_TELEMETRY', '1')
@@ -47,21 +47,17 @@ rewriter_model = AutoModelForCausalLM.from_pretrained(
47
  quantization_config=bnb_config,
48
  )
49
 
50
- # Store original presets for reference
51
- ORIGINAL_PRESETS = deepcopy(PRESETS)
52
-
53
  def get_fresh_presets():
54
- return ORIGINAL_PRESETS
 
55
 
56
- preset_state = gr.State(value=get_fresh_presets())
 
57
 
58
- def reset_presets():
59
- return get_fresh_presets()
60
-
61
  # Preload enhancement model at startup
62
- logger.info("🔄 Loading prompt enhancement model...")
63
  rewriter_tokenizer = AutoTokenizer.from_pretrained(REWRITER_MODEL)
64
- logger.info("✅ Enhancement model loaded and ready!")
65
 
66
  SYSTEM_PROMPT_EDIT = '''
67
  # Edit Instruction Rewriter
@@ -117,7 +113,7 @@ def extract_json_response(model_output: str) -> str:
117
  end_idx = model_output.rfind('}')
118
  # Fix the condition - check if brackets were found
119
  if start_idx == -1 or end_idx == -1 or start_idx >= end_idx:
120
- logger.warning(f"No valid JSON structure found in output. Start: {start_idx}, End: {end_idx}")
121
  return None
122
  # Expand to the full object including outer braces
123
  end_idx += 1 # Include the closing brace
@@ -157,8 +153,8 @@ def extract_json_response(model_output: str) -> str:
157
  if str_values:
158
  return str_values[0].strip()
159
  except Exception as e:
160
- logger.warning(f"JSON parse error: {str(e)}")
161
- logger.warning(f"Model output was: {model_output}")
162
  return None
163
 
164
  def polish_prompt(original_prompt: str) -> str:
@@ -177,10 +173,10 @@ def polish_prompt(original_prompt: str) -> str:
177
  with torch.no_grad():
178
  generated_ids = rewriter_model.generate(
179
  **model_inputs,
180
- max_new_tokens=512,
181
  do_sample=True,
182
- temperature=0.75,
183
- top_p=0.85,
184
  repetition_penalty=1.1,
185
  no_repeat_ngram_size=3,
186
  pad_token_id=rewriter_tokenizer.eos_token_id
@@ -190,8 +186,8 @@ def polish_prompt(original_prompt: str) -> str:
190
  generated_ids[0][model_inputs.input_ids.shape[1]:],
191
  skip_special_tokens=True
192
  ).strip()
193
- logger.info(f"Original Prompt: {original_prompt}")
194
- logger.info(f"Model raw output: {enhanced}") # Debug logging
195
  # Try to extract JSON content
196
  rewritten_prompt = extract_json_response(enhanced)
197
  if rewritten_prompt:
@@ -249,7 +245,7 @@ pipe = QwenImageEditPipeline.from_pretrained(
249
  pipe.load_lora_weights(
250
  "lightx2v/Qwen-Image-Lightning",
251
  # weight_name="Qwen-Image-Lightning-8steps-V1.1.safetensors"
252
- weight_name="Qwen-Image-Edit-Lightning-4steps-V1.0.safetensors"
253
  )
254
  pipe.fuse_lora()
255
 
@@ -260,7 +256,7 @@ pipe.fuse_lora()
260
  try:
261
  pipe.enable_vae_slicing()
262
  except Exception as e:
263
- logger.info(f"VAE Slicing Failed: {e}")
264
 
265
 
266
  def toggle_output_count(preset_type):
@@ -301,30 +297,39 @@ def update_prompt_preview(preset_type, base_prompt):
301
  return preview_text
302
  else:
303
  return "Select a preset above to see how your base prompt will be modified for batch generation."
304
-
305
- def update_preset_prompt_textbox(preset_type, p1, p2, p3, p4):
306
- if preset_type and preset_type in preset_state.value:
307
- # Build new preset instead of mutating in place
308
- new_preset = {
309
- **preset_state.value[preset_type],
310
- "prompts": [p1, p2, p3, p4]
311
- }
312
- preset_state.value[preset_type] = new_preset
313
- return update_prompt_preview_with_presets(preset_type, prompt.value, preset_state.value)
314
- return "Select a preset first."
315
-
 
 
 
 
 
316
  def update_prompt_preview_with_presets(preset_type, base_prompt, custom_presets):
 
317
  if preset_type and preset_type in custom_presets:
318
  preset = custom_presets[preset_type]
319
  non_empty_prompts = [p for p in preset["prompts"] if p.strip()]
320
  if not non_empty_prompts:
321
  return "No prompts defined. Please enter at least one prompt in the editor."
322
- preview = f"**Preset: {preset_type}**\n\n{preset['description']}\n\n"
323
- preview += f"**Generating {len(non_empty_prompts)} image{'s' if len(non_empty_prompts)>1 else ''}:**\n"
324
- for i, pp in enumerate(non_empty_prompts, 1):
325
- preview += f"{i}. {base_prompt}, {pp}\n"
326
- return preview
327
- return "Select a preset to see the preview."
 
 
 
328
 
329
  @spaces.GPU()
330
  def infer(
@@ -333,7 +338,7 @@ def infer(
333
  seed=42,
334
  randomize_seed=False,
335
  true_guidance_scale=4.0,
336
- num_inference_steps=3,
337
  rewrite_prompt=True,
338
  num_images_per_prompt=1,
339
  preset_type=None,
@@ -341,8 +346,6 @@ def infer(
341
  ):
342
  """Image editing endpoint with optimized prompt handling - now uses fresh presets"""
343
  # Resize image to max 1024px on longest side
344
- session_presets = preset_state.value
345
-
346
  def resize_image(pil_image, max_size=1024):
347
  """Resize image to maximum dimension of 1024px while maintaining aspect ratio"""
348
  try:
@@ -358,10 +361,10 @@ def infer(
358
  new_height = int(height * scale)
359
  # Resize image
360
  resized_image = pil_image.resize((new_width, new_height), Image.LANCZOS)
361
- logger.info(f"📝 Image resized from {width}x{height} to {new_width}x{new_height}")
362
  return resized_image
363
  except Exception as e:
364
- logger.warning(f"⚠️ Image resize failed: {e}")
365
  return pil_image # Return original if resize fails
366
 
367
  # Add noise function for batch variation
@@ -379,11 +382,11 @@ def infer(
379
  noisy_array = (noisy_array * 255).astype(np.uint8)
380
  return Image.fromarray(noisy_array)
381
  except Exception as e:
382
- logger.warning(f"Warning: Could not add noise to image: {e}")
383
  return pil_image # Return original if noise addition fails
384
 
385
  # Get fresh presets for this session
386
-
387
 
388
  # Resize input image first
389
  image = resize_image(image, max_size=1024)
@@ -399,19 +402,19 @@ def infer(
399
  batch_prompts = [f"{original_prompt}, {preset_prompt}" for preset_prompt in non_empty_preset_prompts]
400
  num_images_per_prompt = len(non_empty_preset_prompts) # Use actual count of non-empty prompts
401
  prompt_info = (
402
- f"<div style='margin:10px; padding:15px; border-radius:8px; border-left:4px solid #2196F3;>"
403
  f"<h4 style='margin-top: 0;'>🎨 Preset: {preset_type}</h4>"
404
  f"<p>{preset['description']}</p>"
405
  f"<p><strong>Base Prompt:</strong> {original_prompt}</p>"
406
  f"<p>Generating {len(non_empty_preset_prompts)} image{'s' if len(non_empty_preset_prompts) > 1 else ''}</p>"
407
  f"</div>"
408
  )
409
- logger.info(f"Using preset: {preset_type} with {len(batch_prompts)} variations")
410
  else:
411
  # Fallback to manual if no valid prompts
412
  batch_prompts = [prompt]
413
  prompt_info = (
414
- f"<div style='margin:10px; padding:15px; border-radius:8px; border-left:4px solid #FF9800;>"
415
  f"<h4 style='margin-top: 0;'>⚠️ Invalid Preset</h4>"
416
  f"<p>No valid prompts found. Using manual prompt.</p>"
417
  f"<p><strong>Prompt:</strong> {original_prompt}</p>"
@@ -420,13 +423,12 @@ def infer(
420
  else:
421
  batch_prompts = [prompt] # Single prompt in list
422
  # Handle regular prompt rewriting
423
-
424
  if rewrite_prompt:
425
  try:
426
  enhanced_instruction = polish_prompt(original_prompt)
427
  if enhanced_instruction and enhanced_instruction != original_prompt:
428
  prompt_info = (
429
- f"<div style='margin:10px; padding:15px; border-radius:8px; border-left:4px solid #4CAF50;>"
430
  f"<h4 style='margin-top: 0;'>🚀 Prompt Enhancement</h4>"
431
  f"<p><strong>Original:</strong> {original_prompt}</p>"
432
  f"<p><strong style='color:#2E7D32;'>Enhanced:</strong> {enhanced_instruction}</p>"
@@ -435,23 +437,23 @@ def infer(
435
  batch_prompts = [enhanced_instruction]
436
  else:
437
  prompt_info = (
438
- f"<div style='margin:10px; padding:15px; border-radius:8px; border-left:4px solid #FF9800;>"
439
  f"<h4 style='margin-top: 0;'>📝 Prompt Enhancement</h4>"
440
  f"<p>No enhancement applied or enhancement failed</p>"
441
  f"</div>"
442
  )
443
  except Exception as e:
444
- logger.warning(f"Prompt enhancement error: {str(e)}") # Debug logging
445
  gr.Warning(f"Prompt enhancement failed: {str(e)}")
446
  prompt_info = (
447
- f"<div style='margin:10px; padding:15px; border-radius:8px; border-left:4px solid #FF5252;>"
448
  f"<h4 style='margin-top: 0;'>⚠️ Enhancement Not Applied</h4>"
449
  f"<p>Using original prompt. Error: {str(e)[:100]}</p>"
450
  f"</div>"
451
  )
452
  else:
453
  prompt_info = (
454
- f"<div style='margin:10px; padding:10px; border-radius:8px;>"
455
  f"<h4 style='margin-top: 0;'>📝 Original Prompt</h4>"
456
  f"<p>{original_prompt}</p>"
457
  f"</div>"
@@ -481,10 +483,10 @@ def infer(
481
  num_inference_steps=num_inference_steps,
482
  generator=generator,
483
  true_cfg_scale=varied_guidance,
484
- num_images_per_prompt=2
485
  ).images
486
  edited_images.extend(result)
487
- logger.info(f"Generated image {i+1}/{len(batch_prompts)} with prompt: {current_prompt}...")
488
  # Clear cache after generation
489
  # if device == "cuda":
490
  # torch.cuda.empty_cache()
@@ -497,17 +499,23 @@ def infer(
497
  gc.collect()
498
  gr.Error(f"Image generation failed: {str(e)}")
499
  return [], base_seed, (
500
- f"<div style='margin:10px; padding:15px; border-radius:8px; border-left:4px solid #dd2c00;>"
501
  f"<h4 style='margin-top: 0;'>⚠️ Processing Error</h4>"
502
  f"<p>{str(e)[:200]}</p>"
503
  f"</div>"
504
  )
505
 
506
- with gr.Blocks(title="'Qwen Image Edit' Model Playground & Showcase [4-Step Lightning Mode]") as demo:
507
  preset_prompts_state = gr.State(value=[])
508
  # preset_prompts_state = gr.State(value=["", "", "", ""])
509
- preset_state = gr.State(value=ORIGINAL_PRESETS)
510
- gr.Markdown("## ⚡️ Qwen-Image-Edit Lightning Presets")
 
 
 
 
 
 
511
 
512
  with gr.Row(equal_height=True):
513
  # Input Column
@@ -516,15 +524,8 @@ with gr.Blocks(title="'Qwen Image Edit' Model Playground & Showcase [4-Step Ligh
516
  label="Source Image",
517
  type="pil",
518
  height=300
519
- )
520
- with gr.Column(scale=2):
521
- result = gr.Gallery(
522
- label="Edited Images",
523
- columns=2,
524
- container=True
525
- )
526
- with gr.Row():
527
- with gr.Column(scale=1):
528
  prompt = gr.Textbox(
529
  label="Edit Instructions / Base Prompt",
530
  placeholder="e.g. Replace the background with a beach sunset... When a preset is selected, use as the base prompt, e.g. the lamborghini",
@@ -547,11 +548,13 @@ with gr.Blocks(title="'Qwen Image Edit' Model Playground & Showcase [4-Step Ligh
547
  preset_prompt_2 = gr.Textbox(label="Prompt 2", lines=1, value="")
548
  preset_prompt_3 = gr.Textbox(label="Prompt 3", lines=1, value="")
549
  preset_prompt_4 = gr.Textbox(label="Prompt 4", lines=1, value="")
550
-
551
- update_preset_button = gr.Button("Update Preset", variant="secondary", visible=False)
552
- reset_button = gr.Button("Reset Presets", variant="stop", visible=False)
553
-
554
-
 
 
555
 
556
  # Add prompt preview component
557
  prompt_preview = gr.Textbox(
@@ -562,14 +565,6 @@ with gr.Blocks(title="'Qwen Image Edit' Model Playground & Showcase [4-Step Ligh
562
  value="Enter a base prompt and select a preset above to see how your prompt will be modified for batch generation.",
563
  placeholder="Prompt preview will appear here..."
564
  )
565
-
566
- rewrite_toggle = gr.Checkbox(
567
- label="Additional Prompt Enhancement",
568
- info="Setting this to true will pass the basic prompt(s) generated via the static preset template to a secondary LLM tasked with improving the overall cohesiveness and details of the final generation prompt.",
569
- value=True,
570
- interactive=True
571
- )
572
-
573
  run_button = gr.Button(
574
  "Generate Edit(s)",
575
  variant="primary"
@@ -593,14 +588,14 @@ with gr.Blocks(title="'Qwen Image Edit' Model Playground & Showcase [4-Step Ligh
593
  minimum=1.0,
594
  maximum=10.0,
595
  step=0.1,
596
- value=1.1
597
  )
598
  num_inference_steps = gr.Slider(
599
  label="Inference Steps",
600
- minimum=1,
601
  maximum=16,
602
  step=1,
603
- value=3
604
  )
605
 
606
  num_images_per_prompt = gr.Slider(
@@ -612,33 +607,45 @@ with gr.Blocks(title="'Qwen Image Edit' Model Playground & Showcase [4-Step Ligh
612
  interactive=True
613
  )
614
 
 
615
  with gr.Column(scale=2):
616
- prompt_info = gr.Markdown(
 
 
 
 
 
 
 
617
  value="<div style='padding:15px; margin-top:15px'>"
618
- "Hint: depending on the original image, prompt quality, and complexity, you can often get away with 3 steps, even 2 steps without much loss in quality. </div>"
619
  )
620
-
621
-
622
  def show_preset_editor(preset_type):
623
- if preset_type and preset_type in preset_state.value:
624
- preset = preset_state.value[preset_type]
625
- prompts = preset["prompts"] + [""] * (4 - len(preset["prompts"]))
626
- return gr.Group(visible=True), *prompts[:4]
 
 
 
627
  return gr.Group(visible=False), "", "", "", ""
628
-
629
- def update_preset_count(preset_type, p1, p2, p3, p4):
630
- if preset_type and preset_type in preset_state.value:
631
- count = len([p for p in (p1,p2,p3,p4) if p.strip()])
632
- return gr.Slider(value=max(1, min(4, count)), interactive=False)
 
 
633
  return gr.Slider(interactive=True)
634
 
635
  # Update the preset_dropdown.change handlers to use ORIGINAL_PRESETS
636
  preset_dropdown.change(
637
- fn=show_preset_editor,
638
- inputs=[preset_dropdown],
639
- outputs=[preset_editor, preset_prompt_1, preset_prompt_2, preset_prompt_3, preset_prompt_4]
640
  )
641
-
642
 
643
  preset_dropdown.change(
644
  fn=update_prompt_preview,
@@ -646,28 +653,6 @@ with gr.Blocks(title="'Qwen Image Edit' Model Playground & Showcase [4-Step Ligh
646
  outputs=prompt_preview
647
  )
648
 
649
- preset_prompt_1.change(
650
- fn=update_preset_prompt_textbox,
651
- inputs=[preset_dropdown, preset_prompt_1, preset_prompt_2, preset_prompt_3, preset_prompt_4],
652
- outputs=prompt_preview
653
- )
654
-
655
- preset_prompt_2.change(
656
- fn=update_preset_prompt_textbox,
657
- inputs=[preset_dropdown, preset_prompt_1, preset_prompt_2, preset_prompt_3, preset_prompt_4],
658
- outputs=prompt_preview
659
- )
660
- preset_prompt_3.change(
661
- fn=update_preset_prompt_textbox,
662
- inputs=[preset_dropdown, preset_prompt_1, preset_prompt_2, preset_prompt_3, preset_prompt_4],
663
- outputs=prompt_preview
664
- )
665
- preset_prompt_4.change(
666
- fn=update_preset_prompt_textbox,
667
- inputs=[preset_dropdown, preset_prompt_1, preset_prompt_2, preset_prompt_3, preset_prompt_4],
668
- outputs=prompt_preview
669
- )
670
-
671
  preset_prompt_1.change(
672
  fn=update_preset_count,
673
  inputs=[preset_dropdown, preset_prompt_1, preset_prompt_2, preset_prompt_3, preset_prompt_4],
@@ -720,14 +705,10 @@ with gr.Blocks(title="'Qwen Image Edit' Model Playground & Showcase [4-Step Ligh
720
  inputs=inputs,
721
  outputs=outputs
722
  )
723
- # .then(
724
- # fn=reset_presets, outputs=preset_state
725
- # )
726
  prompt.submit(
727
  fn=infer,
728
  inputs=inputs,
729
  outputs=outputs
730
  )
731
- reset_button.click(fn=reset_presets, outputs=preset_state)
732
 
733
  demo.queue(max_size=5).launch()
 
15
  import json # Added json import
16
  from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
17
  import logging
 
18
  from copy import deepcopy
19
+
20
  #############################
21
  os.environ.setdefault('GRADIO_ANALYTICS_ENABLED', 'False')
22
  os.environ.setdefault('HF_HUB_DISABLE_TELEMETRY', '1')
 
47
  quantization_config=bnb_config,
48
  )
49
 
 
 
 
50
  def get_fresh_presets():
51
+ """Return a fresh copy of presets to avoid persistence across users"""
52
+ return deepcopy(PRESETS)
53
 
54
+ # Store original presets for reference
55
+ ORIGINAL_PRESETS = deepcopy(PRESETS)
56
 
 
 
 
57
  # Preload enhancement model at startup
58
+ print("🔄 Loading prompt enhancement model...")
59
  rewriter_tokenizer = AutoTokenizer.from_pretrained(REWRITER_MODEL)
60
+ print("✅ Enhancement model loaded and ready!")
61
 
62
  SYSTEM_PROMPT_EDIT = '''
63
  # Edit Instruction Rewriter
 
113
  end_idx = model_output.rfind('}')
114
  # Fix the condition - check if brackets were found
115
  if start_idx == -1 or end_idx == -1 or start_idx >= end_idx:
116
+ print(f"No valid JSON structure found in output. Start: {start_idx}, End: {end_idx}")
117
  return None
118
  # Expand to the full object including outer braces
119
  end_idx += 1 # Include the closing brace
 
153
  if str_values:
154
  return str_values[0].strip()
155
  except Exception as e:
156
+ print(f"JSON parse error: {str(e)}")
157
+ print(f"Model output was: {model_output}")
158
  return None
159
 
160
  def polish_prompt(original_prompt: str) -> str:
 
173
  with torch.no_grad():
174
  generated_ids = rewriter_model.generate(
175
  **model_inputs,
176
+ max_new_tokens=256,
177
  do_sample=True,
178
+ temperature=0.7,
179
+ top_p=0.8,
180
  repetition_penalty=1.1,
181
  no_repeat_ngram_size=3,
182
  pad_token_id=rewriter_tokenizer.eos_token_id
 
186
  generated_ids[0][model_inputs.input_ids.shape[1]:],
187
  skip_special_tokens=True
188
  ).strip()
189
+ print(f"Original Prompt: {original_prompt}")
190
+ print(f"Model raw output: {enhanced}") # Debug logging
191
  # Try to extract JSON content
192
  rewritten_prompt = extract_json_response(enhanced)
193
  if rewritten_prompt:
 
245
  pipe.load_lora_weights(
246
  "lightx2v/Qwen-Image-Lightning",
247
  # weight_name="Qwen-Image-Lightning-8steps-V1.1.safetensors"
248
+ weight_name="Qwen-Image-Lightning-4steps-V1.0.safetensors"
249
  )
250
  pipe.fuse_lora()
251
 
 
256
  try:
257
  pipe.enable_vae_slicing()
258
  except Exception as e:
259
+ print(f"VAE Slicing Failed: {e}")
260
 
261
 
262
  def toggle_output_count(preset_type):
 
297
  return preview_text
298
  else:
299
  return "Select a preset above to see how your base prompt will be modified for batch generation."
300
+
301
+ def update_preset_prompt_textbox(preset_type, prompt_1, prompt_2, prompt_3, prompt_4):
302
+ """Update preset prompts based on user input - now works with session copy"""
303
+ if preset_type and preset_type in ORIGINAL_PRESETS:
304
+ # Update each prompt in the preset copy (this won't persist globally)
305
+ new_prompts = [prompt_1, prompt_2, prompt_3, prompt_4]
306
+ # Create a working copy for preview purposes
307
+ working_presets = get_fresh_presets()
308
+ for i, new_prompt in enumerate(new_prompts):
309
+ if i < len(working_presets[preset_type]["prompts"]):
310
+ working_presets[preset_type]["prompts"][i] = new_prompt.strip()
311
+ else:
312
+ working_presets[preset_type]["prompts"].append(new_prompt.strip())
313
+ # Return updated preset info for preview
314
+ return update_prompt_preview_with_presets(preset_type, "your subject", working_presets)
315
+ return "Select a preset first to edit its prompts."
316
+
317
  def update_prompt_preview_with_presets(preset_type, base_prompt, custom_presets):
318
+ """Update the prompt preview display with custom presets"""
319
  if preset_type and preset_type in custom_presets:
320
  preset = custom_presets[preset_type]
321
  non_empty_prompts = [p for p in preset["prompts"] if p.strip()]
322
  if not non_empty_prompts:
323
  return "No prompts defined. Please enter at least one prompt in the editor."
324
+ preview_text = f"**Preset: {preset_type}**\n\n"
325
+ preview_text += f"*{preset['description']}*\n\n"
326
+ preview_text += f"**Generating {len(non_empty_prompts)} image{'s' if len(non_empty_prompts) > 1 else ''}:**\n"
327
+ for i, preset_prompt in enumerate(non_empty_prompts, 1):
328
+ full_prompt = f"{base_prompt}, {preset_prompt}"
329
+ preview_text += f"{i}. {full_prompt}\n"
330
+ return preview_text
331
+ else:
332
+ return "Select a preset above to see how your base prompt will be modified for batch generation."
333
 
334
  @spaces.GPU()
335
  def infer(
 
338
  seed=42,
339
  randomize_seed=False,
340
  true_guidance_scale=4.0,
341
+ num_inference_steps=4,
342
  rewrite_prompt=True,
343
  num_images_per_prompt=1,
344
  preset_type=None,
 
346
  ):
347
  """Image editing endpoint with optimized prompt handling - now uses fresh presets"""
348
  # Resize image to max 1024px on longest side
 
 
349
  def resize_image(pil_image, max_size=1024):
350
  """Resize image to maximum dimension of 1024px while maintaining aspect ratio"""
351
  try:
 
361
  new_height = int(height * scale)
362
  # Resize image
363
  resized_image = pil_image.resize((new_width, new_height), Image.LANCZOS)
364
+ print(f"📝 Image resized from {width}x{height} to {new_width}x{new_height}")
365
  return resized_image
366
  except Exception as e:
367
+ print(f"⚠️ Image resize failed: {e}")
368
  return pil_image # Return original if resize fails
369
 
370
  # Add noise function for batch variation
 
382
  noisy_array = (noisy_array * 255).astype(np.uint8)
383
  return Image.fromarray(noisy_array)
384
  except Exception as e:
385
+ print(f"Warning: Could not add noise to image: {e}")
386
  return pil_image # Return original if noise addition fails
387
 
388
  # Get fresh presets for this session
389
+ session_presets = get_fresh_presets()
390
 
391
  # Resize input image first
392
  image = resize_image(image, max_size=1024)
 
402
  batch_prompts = [f"{original_prompt}, {preset_prompt}" for preset_prompt in non_empty_preset_prompts]
403
  num_images_per_prompt = len(non_empty_preset_prompts) # Use actual count of non-empty prompts
404
  prompt_info = (
405
+ f"<div style='margin:10px; padding:15px; border-radius:8px; border-left:4px solid #2196F3; background: #f0f8ff'>"
406
  f"<h4 style='margin-top: 0;'>🎨 Preset: {preset_type}</h4>"
407
  f"<p>{preset['description']}</p>"
408
  f"<p><strong>Base Prompt:</strong> {original_prompt}</p>"
409
  f"<p>Generating {len(non_empty_preset_prompts)} image{'s' if len(non_empty_preset_prompts) > 1 else ''}</p>"
410
  f"</div>"
411
  )
412
+ print(f"Using preset: {preset_type} with {len(batch_prompts)} variations")
413
  else:
414
  # Fallback to manual if no valid prompts
415
  batch_prompts = [prompt]
416
  prompt_info = (
417
+ f"<div style='margin:10px; padding:15px; border-radius:8px; border-left:4px solid #FF9800; background: #fff8f0'>"
418
  f"<h4 style='margin-top: 0;'>⚠️ Invalid Preset</h4>"
419
  f"<p>No valid prompts found. Using manual prompt.</p>"
420
  f"<p><strong>Prompt:</strong> {original_prompt}</p>"
 
423
  else:
424
  batch_prompts = [prompt] # Single prompt in list
425
  # Handle regular prompt rewriting
 
426
  if rewrite_prompt:
427
  try:
428
  enhanced_instruction = polish_prompt(original_prompt)
429
  if enhanced_instruction and enhanced_instruction != original_prompt:
430
  prompt_info = (
431
+ f"<div style='margin:10px; padding:15px; border-radius:8px; border-left:4px solid #4CAF50; background: #f5f9fe'>"
432
  f"<h4 style='margin-top: 0;'>🚀 Prompt Enhancement</h4>"
433
  f"<p><strong>Original:</strong> {original_prompt}</p>"
434
  f"<p><strong style='color:#2E7D32;'>Enhanced:</strong> {enhanced_instruction}</p>"
 
437
  batch_prompts = [enhanced_instruction]
438
  else:
439
  prompt_info = (
440
+ f"<div style='margin:10px; padding:15px; border-radius:8px; border-left:4px solid #FF9800; background: #fff8f0'>"
441
  f"<h4 style='margin-top: 0;'>📝 Prompt Enhancement</h4>"
442
  f"<p>No enhancement applied or enhancement failed</p>"
443
  f"</div>"
444
  )
445
  except Exception as e:
446
+ print(f"Prompt enhancement error: {str(e)}") # Debug logging
447
  gr.Warning(f"Prompt enhancement failed: {str(e)}")
448
  prompt_info = (
449
+ f"<div style='margin:10px; padding:15px; border-radius:8px; border-left:4px solid #FF5252; background: #fef5f5'>"
450
  f"<h4 style='margin-top: 0;'>⚠️ Enhancement Not Applied</h4>"
451
  f"<p>Using original prompt. Error: {str(e)[:100]}</p>"
452
  f"</div>"
453
  )
454
  else:
455
  prompt_info = (
456
+ f"<div style='margin:10px; padding:10px; border-radius:8px; background: #f8f9fa'>"
457
  f"<h4 style='margin-top: 0;'>📝 Original Prompt</h4>"
458
  f"<p>{original_prompt}</p>"
459
  f"</div>"
 
483
  num_inference_steps=num_inference_steps,
484
  generator=generator,
485
  true_cfg_scale=varied_guidance,
486
+ num_images_per_prompt=1
487
  ).images
488
  edited_images.extend(result)
489
+ print(f"Generated image {i+1}/{len(batch_prompts)} with prompt: {current_prompt[:75]}...")
490
  # Clear cache after generation
491
  # if device == "cuda":
492
  # torch.cuda.empty_cache()
 
499
  gc.collect()
500
  gr.Error(f"Image generation failed: {str(e)}")
501
  return [], base_seed, (
502
+ f"<div style='margin:10px; padding:15px; border-radius:8px; border-left:4px solid #dd2c00; background: #fef5f5'>"
503
  f"<h4 style='margin-top: 0;'>⚠️ Processing Error</h4>"
504
  f"<p>{str(e)[:200]}</p>"
505
  f"</div>"
506
  )
507
 
508
+ with gr.Blocks(title="Qwen Image Edit - Fast Lightning Mode w/ Batch") as demo:
509
  preset_prompts_state = gr.State(value=[])
510
  # preset_prompts_state = gr.State(value=["", "", "", ""])
511
+
512
+ gr.Markdown("""
513
+ <div style="text-align: center; background: linear-gradient(to right, #3a7bd5, #00d2ff); color: white; padding: 20px; border-radius: 8px;">
514
+ <h1 style="margin-bottom: 5px;">⚡️ Qwen-Image-Edit Lightning</h1>
515
+ <p>✨ 4-step inferencing with lightx2v's LoRA.</p>
516
+ <p>📝 Local Prompt Enhancement, Batched Multi-image Generation, 🎨 Preset Batches</p>
517
+ </div>
518
+ """)
519
 
520
  with gr.Row(equal_height=True):
521
  # Input Column
 
524
  label="Source Image",
525
  type="pil",
526
  height=300
527
+ )
528
+
 
 
 
 
 
 
 
529
  prompt = gr.Textbox(
530
  label="Edit Instructions / Base Prompt",
531
  placeholder="e.g. Replace the background with a beach sunset... When a preset is selected, use as the base prompt, e.g. the lamborghini",
 
548
  preset_prompt_2 = gr.Textbox(label="Prompt 2", lines=1, value="")
549
  preset_prompt_3 = gr.Textbox(label="Prompt 3", lines=1, value="")
550
  preset_prompt_4 = gr.Textbox(label="Prompt 4", lines=1, value="")
551
+ update_preset_button = gr.Button("Update Preset", variant="secondary")
552
+
553
+ rewrite_toggle = gr.Checkbox(
554
+ label="Enable Prompt Enhancement",
555
+ value=True,
556
+ interactive=True
557
+ )
558
 
559
  # Add prompt preview component
560
  prompt_preview = gr.Textbox(
 
565
  value="Enter a base prompt and select a preset above to see how your prompt will be modified for batch generation.",
566
  placeholder="Prompt preview will appear here..."
567
  )
 
 
 
 
 
 
 
 
568
  run_button = gr.Button(
569
  "Generate Edit(s)",
570
  variant="primary"
 
588
  minimum=1.0,
589
  maximum=10.0,
590
  step=0.1,
591
+ value=1.0
592
  )
593
  num_inference_steps = gr.Slider(
594
  label="Inference Steps",
595
+ minimum=2,
596
  maximum=16,
597
  step=1,
598
+ value=4
599
  )
600
 
601
  num_images_per_prompt = gr.Slider(
 
607
  interactive=True
608
  )
609
 
610
+ # Output Column
611
  with gr.Column(scale=2):
612
+ result = gr.Gallery(
613
+ label="Edited Images",
614
+ columns=lambda x: min(x, 2),
615
+ height=500,
616
+ object_fit="cover",
617
+ preview=True
618
+ )
619
+ prompt_info = gr.HTML(
620
  value="<div style='padding:15px; margin-top:15px'>"
621
+ "Prompt details will appear after generation. Ability to edit Preset Prompts on the fly will be implemented shortly.</div>"
622
  )
623
+
624
+ # Fix the show_preset_editor function to use ORIGINAL_PRESETS:
625
  def show_preset_editor(preset_type):
626
+ if preset_type and preset_type in ORIGINAL_PRESETS: # Changed from PRESETS to ORIGINAL_PRESETS
627
+ preset = ORIGINAL_PRESETS[preset_type]
628
+ prompts = preset["prompts"]
629
+ # Pad prompts to 4 items if needed
630
+ while len(prompts) < 4:
631
+ prompts.append("")
632
+ return gr.Group(visible=True), prompts[0], prompts[1], prompts[2], prompts[3]
633
  return gr.Group(visible=False), "", "", "", ""
634
+
635
+ # Fix the update_preset_count function to use ORIGINAL_PRESETS:
636
+ def update_preset_count(preset_type, prompt_1, prompt_2, prompt_3, prompt_4):
637
+ """Update the output count slider based on non-empty preset prompts"""
638
+ if preset_type and preset_type in ORIGINAL_PRESETS: # Changed from PRESETS to ORIGINAL_PRESETS
639
+ non_empty_count = len([p for p in [prompt_1, prompt_2, prompt_3, prompt_4] if p.strip()])
640
+ return gr.Slider(value=max(1, min(4, non_empty_count)), interactive=False)
641
  return gr.Slider(interactive=True)
642
 
643
  # Update the preset_dropdown.change handlers to use ORIGINAL_PRESETS
644
  preset_dropdown.change(
645
+ fn=toggle_output_count,
646
+ inputs=preset_dropdown,
647
+ outputs=[preset_editor, num_images_per_prompt, preset_prompt_1, preset_prompt_2, preset_prompt_3, preset_prompt_4]
648
  )
 
649
 
650
  preset_dropdown.change(
651
  fn=update_prompt_preview,
 
653
  outputs=prompt_preview
654
  )
655
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
  preset_prompt_1.change(
657
  fn=update_preset_count,
658
  inputs=[preset_dropdown, preset_prompt_1, preset_prompt_2, preset_prompt_3, preset_prompt_4],
 
705
  inputs=inputs,
706
  outputs=outputs
707
  )
 
 
 
708
  prompt.submit(
709
  fn=infer,
710
  inputs=inputs,
711
  outputs=outputs
712
  )
 
713
 
714
  demo.queue(max_size=5).launch()
presets.py CHANGED
@@ -30,9 +30,9 @@ PRESETS = {
30
  "prompts": [
31
  "frontal view of the subject, facing camera directly",
32
  "side view of subject, profile view from the side",
33
- "dutch angle shot of subject, candid photography"
34
  ],
35
- "description": "Generate three different views of the subject"
36
  },
37
  "Style Variations": {
38
  "count": 3,
@@ -61,143 +61,16 @@ PRESETS = {
61
  ],
62
  "description": "Show the subject in different hairstyles"
63
  },
64
- "Seasonal Themes": {
65
- "count": 4,
66
  "prompts": [
67
- "in a snowy winter landscape, snowflakes falling gently",
68
- "in a sunny summer meadow, bright sunshine",
69
- "in a crisp autumn forest, leaves turning orange",
70
- "in a misty spring garden, blooming flowers"
71
  ],
72
- "description": "Render the subject in each of the four seasons"
73
- },
74
-
75
- "Emotional Mood": {
76
- "count": 4,
77
- "prompts": [
78
- "with a joyful grin, eyes sparkling",
79
- "with a thoughtful expression, slightly furrowed brow",
80
- "with a dramatic, intense stare",
81
- "with a gentle, serene smile"
82
- ],
83
- "description": "Show the subject’s emotions in four different moods"
84
- },
85
-
86
- "Historical Eras": {
87
- "count": 4,
88
- "prompts": [
89
- "in Victorian England, ornate lace and corset",
90
- "in the 1980s, neon lights and big hair",
91
- "in a medieval castle, knight armor",
92
- "in a futuristic cyber‑punk city, holographic backdrop"
93
- ],
94
- "description": "Place the subject in four iconic time periods"
95
- },
96
-
97
- "Camera Lens Effects": {
98
- "count": 4,
99
- "prompts": [
100
- "captured with a wide‑angle lens, exaggerated perspective",
101
- "shot with a telephoto lens, shallow depth of field",
102
- "taken with a fisheye lens, circular distortion",
103
- "rendered with a macro lens, extreme close‑up detail"
104
- ],
105
- "description": "Play with different lens styles for the same subject"
106
- },
107
-
108
- "Fantasy Elements": {
109
- "count": 3,
110
- "prompts": [
111
- "surrounded by a swirling vortex of light",
112
- "hovering above a floating island in the clouds",
113
- "paired with a companion dragon, breathing fire",
114
- ],
115
- "description": "Add a fantastical twist to each rendition"
116
- },
117
-
118
- "Texture Variations": {
119
- "count": 3,
120
- "prompts": [
121
- "covered in soft velvet, plush texture",
122
- "painted with glossy enamel, shiny finish",
123
- "wrapped in intricate lace patterns",
124
- ],
125
- "description": "Show the subject in three distinct surface textures"
126
- },
127
-
128
- "Color Palette Swaps": {
129
- "count": 3,
130
- "prompts": [
131
- "in pastel colors, soft hues",
132
- "with neon saturation, high‑contrast glow",
133
- "in monochrome black & white, dramatic contrast"
134
- ],
135
- "description": "Recolor the subject with three distinct palettes"
136
- },
137
- "Runway Catwalk": {
138
- "count": 4,
139
- "prompts": [
140
- "strutting down a glossy runway, spotlight on the model",
141
- "walking confidently with a high‑fashion backdrop",
142
- "posing with a dramatic, angular pose on the catwalk",
143
- "showcasing an avant‑garde ensemble, dramatic lighting"
144
- ],
145
- "description": "Capture the model on a high‑end runway"
146
- },
147
-
148
- "Vogue Editorial": {
149
- "count": 3,
150
- "prompts": [
151
- "editorial shot with a moody, artistic background",
152
- "intimate close‑up, dramatic lighting and subtle shadows",
153
- "wide‑angle full‑body shot, striking composition"
154
- ],
155
- "description": "Create a magazine‑quality editorial spread"
156
- },
157
- "Evening Gown Glam": {
158
- "count": 3,
159
- "prompts": [
160
- "floor‑length ballroom gown, shimmering crystal embellishments",
161
- "gold‑en, reflective lighting, chandelier backdrop",
162
- "soft, ethereal glow, elegant pose"
163
- ],
164
- "description": "Render an elegant evening dress in glamorous lighting"
165
- },
166
-
167
- "Chic Streetwear": {
168
- "count": 4,
169
- "prompts": [
170
- "urban street backdrop, high‑contrast graffiti walls",
171
- "modern athleisure look, bold colors and textures",
172
- "casual, relaxed pose with a casual jacket and sneakers",
173
- "dynamic movement shot, capturing fluidity"
174
- ],
175
- "description": "Showcase the model in trendy street‑style attire"
176
- },
177
- "Swimwear Showcase": {
178
- "count": 4,
179
- "prompts": [
180
- "wearing a vibrant neon bikini, sunny beach background",
181
- "in a sleek one‑piece with high‑waisted cut, poolside setting",
182
- "sporty swim trunks and matching tank top, surfing wave backdrop",
183
- "coastal vibe with a chic cover‑up over a solid‑color swimsuit"
184
- ],
185
- "description": "Highlight the model in four distinct swimwear styles, from bold bikinis to elegant one‑pieces and sporty swimwear"
186
- },
187
- "Polaroid and Film": {
188
- "count": 4,
189
- "prompts": [
190
- "captured in a vintage Polaroid frame, sepia‑tone filter, soft vignette",
191
- "black‑and‑white film shot with a slight grain, high‑contrast lighting",
192
- "soft focus, pastel color palette with a subtle yellow tint, nostalgic feel",
193
- "classic instant camera style, square format, warm lighting, subtle bokeh"
194
- ],
195
- "description": "Generate a nostalgic Polaroid/film aesthetic across four different looks"
196
  }
197
  }
198
 
199
-
200
-
201
  def get_preset_choices():
202
  """
203
  Return list of preset choices for Gradio dropdown.
 
30
  "prompts": [
31
  "frontal view of the subject, facing camera directly",
32
  "side view of subject, profile view from the side",
33
+ "back side view of subject, showing the rear/back view"
34
  ],
35
+ "description": "Generate 4 different views of the subject"
36
  },
37
  "Style Variations": {
38
  "count": 3,
 
61
  ],
62
  "description": "Show the subject in different hairstyles"
63
  },
64
+ "Color Comparison": {
65
+ "count": 2,
66
  "prompts": [
67
+ "painted in matte black paint and red accents",
68
+ "covered in gold glitter over white fabric"
 
 
69
  ],
70
+ "description": "Simple two-tone color comparison"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  }
72
  }
73
 
 
 
74
  def get_preset_choices():
75
  """
76
  Return list of preset choices for Gradio dropdown.