tukisuwa
update
9a19679
Raw
History Blame Contribute Delete
10.9 kB
import gradio as gr
import numpy as np
import random
import torch
import spaces
import os
from pathlib import Path
from PIL import Image
from diffusers import FlowMatchEulerDiscreteScheduler
from optimization import optimize_pipeline_
from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
import math
# --- Model Loading ---
dtype = torch.bfloat16
device = "cuda" if torch.cuda.is_available() else "cpu"
scheduler_config = {
"base_image_seq_len": 256,
"base_shift": math.log(3),
"invert_sigmas": False,
"max_image_seq_len": 8192,
"max_shift": math.log(3),
"num_train_timesteps": 1000,
"shift": 1.0,
"shift_terminal": None,
"stochastic_sampling": False,
"time_shift_type": "exponential",
"use_beta_sigmas": False,
"use_dynamic_shifting": True,
"use_exponential_sigmas": False,
"use_karras_sigmas": False,
}
scheduler = FlowMatchEulerDiscreteScheduler.from_config(scheduler_config)
pipe = QwenImageEditPlusPipeline.from_pretrained("Qwen/Qwen-Image-Edit-2509", scheduler=scheduler, torch_dtype=dtype)
# Load the texture LoRA
pipe.load_lora_weights("2vXpSwA7/iroiro-lora",
weight_name="qwen_lora/qie2509_lora_katame_transferring_01.safetensors", adapter_name="texture")
pipe.load_lora_weights("lightx2v/Qwen-Image-Lightning",
weight_name="Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors", adapter_name="lightning")
pipe.set_adapters(["texture", "lightning"], adapter_weights=[1.2, 1.])
pipe.fuse_lora(adapter_names=["texture", "lightning"], lora_scale=1)
pipe.unload_lora_weights()
pipe.transformer.__class__ = QwenImageTransformer2DModel
pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
pipe.to(device)
optimize_pipeline_(pipe, image=[Image.new("RGB", (1024, 1024)), Image.new("RGB", (1024, 1024))], prompt="prompt")
MAX_SEED = np.iinfo(np.int32).max
# --- Load sample images ---
def get_sample_images(folder):
"""Get all image files from a folder."""
folder_path = Path(folder)
if not folder_path.exists():
return []
image_extensions = {'.png', '.jpg', '.jpeg', '.webp', '.bmp'}
images = []
for file in sorted(folder_path.iterdir()):
if file.suffix.lower() in image_extensions:
images.append(str(file))
return images
slotA_images = get_sample_images("samples/slotA")
slotB_images = get_sample_images("samples/slotB")
def calculate_dimensions(image):
"""Calculate output dimensions based on content image, keeping largest side at 1024."""
if image is None:
return 1024, 1024
original_width, original_height = image.size
if original_width > original_height:
new_width = 1024
aspect_ratio = original_height / original_width
new_height = int(new_width * aspect_ratio)
else:
new_height = 1024
aspect_ratio = original_width / original_height
new_width = int(new_height * aspect_ratio)
# Ensure dimensions are multiples of 8
new_width = (new_width // 8) * 8
new_height = (new_height // 8) * 8
return new_width, new_height
@spaces.GPU
def apply_texture(
content_image,
texture_image,
prompt,
seed=42,
randomize_seed=False,
true_guidance_scale=False,
num_inference_steps=4,
progress=gr.Progress(track_tqdm=True)
):
if content_image is None:
raise gr.Error("Please upload a content image.")
if texture_image is None:
raise gr.Error("Please upload a texture image.")
if not prompt or not prompt.strip():
prompt = "change image1 character texture to image2 texture"
if randomize_seed:
seed = random.randint(0, MAX_SEED)
generator = torch.Generator(device=device).manual_seed(seed)
# Calculate dimensions based on content image
width, height = calculate_dimensions(content_image)
# Prepare images
content_pil = content_image.convert("RGB") if isinstance(content_image, Image.Image) else Image.open(content_image.name).convert("RGB")
texture_pil = texture_image.convert("RGB") if isinstance(texture_image, Image.Image) else Image.open(texture_image.name).convert("RGB")
pil_images = [content_pil, texture_pil]
result = pipe(
image=pil_images,
prompt=prompt,
height=height,
width=width,
num_inference_steps=num_inference_steps,
generator=generator,
true_cfg_scale=true_guidance_scale,
num_images_per_prompt=1,
).images[0]
return result, seed
# --- UI ---
css = '''
#col-container, #examples {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.dark .progress-text{
color: white !important;
}
/* Card style for image containers */
.image-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 4px;
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
}
/* Input section styling */
.input-section {
background: rgba(255,255,255,0.05);
border-radius: 12px;
padding: 20px;
margin-bottom: 15px;
}
/* Button styling */
.generate-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
border: none !important;
font-size: 18px !important;
font-weight: 600 !important;
padding: 12px 24px !important;
border-radius: 8px !important;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4) !important;
transition: all 0.3s ease !important;
}
.generate-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.6) !important;
}
/* Output section */
.output-section {
background: rgba(255,255,255,0.03);
border-radius: 12px;
padding: 20px;
min-height: 600px;
}
/* Accordion styling */
.accordion {
border-radius: 8px;
margin-top: 10px;
}
/* Image upload area */
.image-upload {
border: 2px dashed rgba(102, 126, 234, 0.3);
border-radius: 12px;
transition: all 0.3s ease;
}
.image-upload:hover {
border-color: rgba(102, 126, 234, 0.6);
background: rgba(102, 126, 234, 0.05);
}
'''
with gr.Blocks(css=css) as demo:
with gr.Column(elem_id="col-container"):
# Header
gr.Markdown("# 🎨 Qwen Image Edit - Katame Transfer")
gr.Markdown("""
Transform your images with AI-powered texture transfer using **Qwen Image Edit 2509**
Powered by [2vXpSwA7/iroiro-lora](https://huggingface.co/2vXpSwA7/iroiro-lora) β€’ [Qwen-Image-Lightning](https://huggingface.co/lightx2v/Qwen-Image-Lightning) ⚑
""")
gr.Markdown("---")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### πŸ“₯ Input Images")
with gr.Row():
with gr.Column():
gr.Markdown("**πŸ–ΌοΈ Content Image**")
content_image = gr.Image(label="", type="pil", elem_classes="image-upload")
with gr.Accordion("πŸ“ Sample Images", open=False):
slotA_gallery = gr.Gallery(
value=slotA_images,
label="",
columns=3,
height="auto",
allow_preview=True,
show_label=False
)
with gr.Column():
gr.Markdown("**🎨 Texture Image**")
texture_image = gr.Image(label="", type="pil", elem_classes="image-upload")
with gr.Accordion("πŸ“ Sample Textures", open=False):
slotB_gallery = gr.Gallery(
value=slotB_images,
label="",
columns=3,
height="auto",
allow_preview=True,
show_label=False
)
gr.Markdown("### ✍️ Description")
prompt = gr.Textbox(
label="",
info="",
placeholder="",
lines=2
)
button = gr.Button("✨ Generate Image", variant="primary", elem_classes="generate-btn")
with gr.Accordion("βš™οΈ Advanced Settings", open=False):
seed = gr.Slider(label="🎲 Seed", minimum=0, maximum=MAX_SEED, step=1, value=0)
randomize_seed = gr.Checkbox(label="πŸ”€ Randomize Seed", value=True)
true_guidance_scale = gr.Slider(
label="🎯 Guidance Scale",
minimum=1.0,
maximum=10.0,
step=0.1,
value=1.0,
info="Higher values = stronger adherence to prompt"
)
num_inference_steps = gr.Slider(
label="⚑ Inference Steps",
minimum=1,
maximum=40,
step=1,
value=4,
info="More steps = higher quality (but slower)"
)
with gr.Column(scale=1):
gr.Markdown("### 🎭 Generated Result")
output = gr.Image(label="", interactive=False, elem_classes="output-section")
with gr.Row():
seed_display = gr.Number(label="🌱 Used Seed", interactive=False, visible=True)
# Event handlers
def select_slotA_image(evt: gr.SelectData):
return slotA_images[evt.index]
def select_slotB_image(evt: gr.SelectData):
return slotB_images[evt.index]
slotA_gallery.select(fn=select_slotA_image, outputs=content_image)
slotB_gallery.select(fn=select_slotB_image, outputs=texture_image)
button.click(
fn=apply_texture,
inputs=[
content_image,
texture_image,
prompt,
seed,
randomize_seed,
true_guidance_scale,
num_inference_steps
],
outputs=[output, seed_display]
)
if __name__ == "__main__":
demo.launch()