mxpln commited on
Commit
4ace8d7
·
2 Parent(s): 8e669cf8399987

Merge branch 'main' of https://huggingface.co/spaces/Onise/qwen-image-edit-aio-lora

Browse files
Files changed (2) hide show
  1. app.py +47 -18
  2. qwenimage/pipeline_qwenimage_edit_plus.py +16 -2
app.py CHANGED
@@ -30,6 +30,7 @@ import os
30
  import base64
31
  from io import BytesIO
32
  import json
 
33
 
34
  SYSTEM_PROMPT = '''
35
  # Edit Instruction Rewriter
@@ -142,7 +143,7 @@ def polish_prompt_hf(original_prompt, img_list):
142
  # If img is a PIL Image
143
  if hasattr(img, 'save'): # Check if it's a PIL Image
144
  buffered = BytesIO()
145
- img.save(buffered, format="PNG")
146
  img_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
147
  image_url = f"data:image/png;base64,{img_base64}"
148
  # If img is already a file path (string)
@@ -217,7 +218,7 @@ def polish_prompt_hf(original_prompt, img_list):
217
  def encode_image(pil_image):
218
  import io
219
  buffered = io.BytesIO()
220
- pil_image.save(buffered, format="PNG")
221
  return base64.b64encode(buffered.getvalue()).decode("utf-8")
222
 
223
  # --- Model Loading ---
@@ -398,17 +399,13 @@ torch.cuda.empty_cache()
398
  # optimize_pipeline_(pipe, image=[Image.new("RGB", (1024, 1024)), Image.new("RGB", (1024, 1024))], prompt="prompt")
399
 
400
  # --- Custom LoRA loading (QWEN_MCNL) ---
401
- # If you want to temporarily disable this LoRA, comment out this block.
402
- try:
403
- print("loading custom LoRA: Daverrrr75/QWEN_MCNL ...")
404
- pipe.load_lora_weights(
405
- "Daverrrr75/QWEN_MCNL",
406
- weight_name="qwen_MCNL_v1.0.safetensors",
407
- )
408
- pipe.fuse_lora()
409
- print("custom LoRA fused successfully.")
410
- except Exception as e:
411
- print(f"Warning: failed to load/fuse custom LoRA Daverrrr75/QWEN_MCNL: {e}")
412
 
413
 
414
  # --- UI Constants and Helpers ---
@@ -504,8 +501,33 @@ def infer(
504
  except Exception:
505
  continue
506
 
507
- if height==256 and width==256:
508
- height, width = None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
  print(f"Calling pipeline with prompt: '{prompt}'")
510
  print(f"Negative Prompt: '{negative_prompt}'")
511
  print(f"Seed: {seed}, Steps: {num_inference_steps}, Guidance: {true_guidance_scale}, Size: {width}x{height}")
@@ -527,8 +549,15 @@ def infer(
527
  num_images_per_prompt=num_images_per_prompt,
528
  ).images
529
 
530
- # Return images, seed, and make button visible
531
- return image, seed, gr.update(visible=True)
 
 
 
 
 
 
 
532
 
533
  # --- Examples and UI Layout ---
534
  examples = []
@@ -568,7 +597,7 @@ with gr.Blocks(css=css) as demo:
568
  interactive=True)
569
 
570
  with gr.Column():
571
- result = gr.Gallery(label="Result", show_label=False, type="pil", interactive=False)
572
  # Add this button right after the result gallery - initially hidden
573
  use_output_btn = gr.Button("↗️ Use as input", variant="secondary", size="sm", visible=False)
574
 
 
30
  import base64
31
  from io import BytesIO
32
  import json
33
+ import tempfile
34
 
35
  SYSTEM_PROMPT = '''
36
  # Edit Instruction Rewriter
 
143
  # If img is a PIL Image
144
  if hasattr(img, 'save'): # Check if it's a PIL Image
145
  buffered = BytesIO()
146
+ img.save(buffered, format="PNG", compress_level=0, optimize=False)
147
  img_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
148
  image_url = f"data:image/png;base64,{img_base64}"
149
  # If img is already a file path (string)
 
218
  def encode_image(pil_image):
219
  import io
220
  buffered = io.BytesIO()
221
+ pil_image.save(buffered, format="PNG", compress_level=0, optimize=False)
222
  return base64.b64encode(buffered.getvalue()).decode("utf-8")
223
 
224
  # --- Model Loading ---
 
399
  # optimize_pipeline_(pipe, image=[Image.new("RGB", (1024, 1024)), Image.new("RGB", (1024, 1024))], prompt="prompt")
400
 
401
  # --- Custom LoRA loading (QWEN_MCNL) ---
402
+ print("loading custom LoRA: Daverrrr75/QWEN_MCNL ...")
403
+ pipe.load_lora_weights(
404
+ "Daverrrr75/QWEN_MCNL",
405
+ weight_name="qwen_MCNL_v1.0.safetensors",
406
+ )
407
+ pipe.fuse_lora()
408
+ print("custom LoRA fused successfully.")
 
 
 
 
409
 
410
 
411
  # --- UI Constants and Helpers ---
 
501
  except Exception:
502
  continue
503
 
504
+ # Adjust dimensions to preserve input resolution and ensure quality
505
+ if len(pil_images) > 0:
506
+ input_width, input_height = pil_images[0].size
507
+ aspect_ratio = input_width / input_height
508
+ else:
509
+ aspect_ratio = 1.0 # Default square if no image
510
+
511
+ # Preserve input resolution by default - use input dimensions directly
512
+ if height is None and width is None:
513
+ # Use input dimensions directly, rounded to required multiple
514
+ width = round(input_width / 32) * 32
515
+ height = round(input_height / 32) * 32
516
+ elif height is not None and width is None:
517
+ width = round(height * aspect_ratio / 32) * 32
518
+ elif width is not None and height is None:
519
+ height = round(width / aspect_ratio / 32) * 32
520
+ else:
521
+ # Both provided: adjust width to match height * aspect_ratio
522
+ width = round(height * aspect_ratio / 32) * 32
523
+
524
+ # Quality safeguard: ensure minimum size to avoid excessive quality loss
525
+ min_size = 512
526
+ if height < min_size or width < min_size:
527
+ # Scale up while preserving ratio if too small
528
+ scale_factor = max(min_size / height, min_size / width)
529
+ height = round(height * scale_factor / 32) * 32
530
+ width = round(width * scale_factor / 32) * 32
531
  print(f"Calling pipeline with prompt: '{prompt}'")
532
  print(f"Negative Prompt: '{negative_prompt}'")
533
  print(f"Seed: {seed}, Steps: {num_inference_steps}, Guidance: {true_guidance_scale}, Size: {width}x{height}")
 
549
  num_images_per_prompt=num_images_per_prompt,
550
  ).images
551
 
552
+ # Save images as PNG with 100% quality (lossless, no compression)
553
+ output_paths = []
554
+ for i, img in enumerate(image):
555
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_file:
556
+ img.save(tmp_file.name, format="PNG", compress_level=0, optimize=False)
557
+ output_paths.append(tmp_file.name)
558
+
559
+ # Return paths, seed, and make button visible
560
+ return output_paths, seed, gr.update(visible=True)
561
 
562
  # --- Examples and UI Layout ---
563
  examples = []
 
597
  interactive=True)
598
 
599
  with gr.Column():
600
+ result = gr.Gallery(label="Result", show_label=False, type="filepath", interactive=False)
601
  # Add this button right after the result gallery - initially hidden
602
  use_output_btn = gr.Button("↗️ Use as input", variant="secondary", size="sm", visible=False)
603
 
qwenimage/pipeline_qwenimage_edit_plus.py CHANGED
@@ -627,8 +627,18 @@ class QwenImageEditPlusPipeline(DiffusionPipeline, QwenImageLoraLoaderMixin):
627
  [`~pipelines.qwenimage.QwenImagePipelineOutput`] if `return_dict` is True, otherwise a `tuple`. When
628
  returning a tuple, the first element is a list with the generated images.
629
  """
 
630
  image_size = image[-1].size if isinstance(image, list) else image.size
631
- calculated_width, calculated_height = calculate_dimensions(1024 * 1024, image_size[0] / image_size[1])
 
 
 
 
 
 
 
 
 
632
  height = height or calculated_height
633
  width = width or calculated_width
634
 
@@ -677,7 +687,11 @@ class QwenImageEditPlusPipeline(DiffusionPipeline, QwenImageLoraLoaderMixin):
677
  condition_width, condition_height = calculate_dimensions(
678
  CONDITION_IMAGE_SIZE, image_width / image_height
679
  )
680
- vae_width, vae_height = calculate_dimensions(VAE_IMAGE_SIZE, image_width / image_height)
 
 
 
 
681
  condition_image_sizes.append((condition_width, condition_height))
682
  vae_image_sizes.append((vae_width, vae_height))
683
  condition_images.append(self.image_processor.resize(img, condition_height, condition_width))
 
627
  [`~pipelines.qwenimage.QwenImagePipelineOutput`] if `return_dict` is True, otherwise a `tuple`. When
628
  returning a tuple, the first element is a list with the generated images.
629
  """
630
+ # Preserve input resolution - use input dimensions directly
631
  image_size = image[-1].size if isinstance(image, list) else image.size
632
+ input_width, input_height = image_size[0], image_size[1]
633
+ aspect_ratio = input_width / input_height
634
+
635
+ # Use input dimensions if not specified, rounded to required multiple
636
+ if height is None and width is None:
637
+ calculated_width = round(input_width / (self.vae_scale_factor * 2)) * (self.vae_scale_factor * 2)
638
+ calculated_height = round(input_height / (self.vae_scale_factor * 2)) * (self.vae_scale_factor * 2)
639
+ else:
640
+ calculated_width, calculated_height = calculate_dimensions(1024 * 1024, aspect_ratio)
641
+
642
  height = height or calculated_height
643
  width = width or calculated_width
644
 
 
687
  condition_width, condition_height = calculate_dimensions(
688
  CONDITION_IMAGE_SIZE, image_width / image_height
689
  )
690
+ # Use input dimensions for VAE processing to preserve quality
691
+ # Round to required multiple for VAE compatibility
692
+ multiple_of = self.vae_scale_factor * 2
693
+ vae_width = round(image_width / multiple_of) * multiple_of
694
+ vae_height = round(image_height / multiple_of) * multiple_of
695
  condition_image_sizes.append((condition_width, condition_height))
696
  vae_image_sizes.append((vae_width, vae_height))
697
  condition_images.append(self.image_processor.resize(img, condition_height, condition_width))