Spaces:
Running
on
Zero
Running
on
Zero
update app
Browse files
app.py
CHANGED
|
@@ -14,12 +14,12 @@ try:
|
|
| 14 |
except ImportError:
|
| 15 |
class spaces:
|
| 16 |
@staticmethod
|
| 17 |
-
def GPU(duration=
|
| 18 |
def decorator(func):
|
| 19 |
return func
|
| 20 |
return decorator
|
| 21 |
|
| 22 |
-
# --- Custom Theme Setup
|
| 23 |
colors.steel_blue = colors.Color(
|
| 24 |
name="steel_blue",
|
| 25 |
c50="#EBF3F8",
|
|
@@ -90,15 +90,7 @@ steel_blue_theme = SteelBlueTheme()
|
|
| 90 |
|
| 91 |
# --- Hardware Setup ---
|
| 92 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 93 |
-
|
| 94 |
-
print("CUDA_VISIBLE_DEVICES=", os.environ.get("CUDA_VISIBLE_DEVICES"))
|
| 95 |
-
print("torch.__version__ =", torch.__version__)
|
| 96 |
-
print("cuda available:", torch.cuda.is_available())
|
| 97 |
-
if torch.cuda.is_available():
|
| 98 |
-
print("current device:", torch.cuda.current_device())
|
| 99 |
-
print("device name:", torch.cuda.get_device_name(torch.cuda.current_device()))
|
| 100 |
-
|
| 101 |
-
print("Using device:", device)
|
| 102 |
|
| 103 |
# --- Imports for Custom Pipeline ---
|
| 104 |
from diffusers import FlowMatchEulerDiscreteScheduler
|
|
@@ -106,9 +98,8 @@ from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
|
|
| 106 |
from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
|
| 107 |
from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
|
| 108 |
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
# Load Pipeline with Rapid-AIO Transformer (Fast Version)
|
| 112 |
pipe = QwenImageEditPlusPipeline.from_pretrained(
|
| 113 |
"Qwen/Qwen-Image-Edit-2509",
|
| 114 |
transformer=QwenImageTransformer2DModel.from_pretrained(
|
|
@@ -120,31 +111,37 @@ pipe = QwenImageEditPlusPipeline.from_pretrained(
|
|
| 120 |
torch_dtype=dtype
|
| 121 |
).to(device)
|
| 122 |
|
| 123 |
-
#
|
| 124 |
-
print("Loading LoRA
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
-
#
|
| 127 |
-
|
| 128 |
-
weight_name="apply_texture_v2_qwen_image_edit_2509.safetensors",
|
| 129 |
-
adapter_name="texture-edit")
|
| 130 |
|
| 131 |
-
#
|
| 132 |
-
pipe.load_lora_weights("
|
| 133 |
-
weight_name="
|
| 134 |
-
adapter_name="
|
| 135 |
|
| 136 |
-
#
|
| 137 |
-
pipe.load_lora_weights("
|
| 138 |
-
weight_name="
|
| 139 |
-
adapter_name="
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
|
| 142 |
-
# Attempt
|
| 143 |
try:
|
| 144 |
pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
|
| 145 |
print("Flash Attention 3 Processor set successfully.")
|
| 146 |
except Exception as e:
|
| 147 |
-
print(f"Could not set FA3 processor (likely hardware mismatch): {e}.
|
| 148 |
|
| 149 |
MAX_SEED = np.iinfo(np.int32).max
|
| 150 |
|
|
@@ -163,15 +160,16 @@ def update_dimensions_on_upload(image):
|
|
| 163 |
aspect_ratio = original_width / original_height
|
| 164 |
new_width = int(new_height * aspect_ratio)
|
| 165 |
|
| 166 |
-
# Ensure dimensions are multiples of 16
|
| 167 |
new_width = (new_width // 16) * 16
|
| 168 |
new_height = (new_height // 16) * 16
|
| 169 |
|
| 170 |
return new_width, new_height
|
| 171 |
|
| 172 |
-
@spaces.GPU(duration=
|
| 173 |
def infer(
|
| 174 |
-
|
|
|
|
| 175 |
prompt,
|
| 176 |
lora_adapter,
|
| 177 |
seed,
|
|
@@ -180,34 +178,28 @@ def infer(
|
|
| 180 |
steps,
|
| 181 |
progress=gr.Progress(track_tqdm=True)
|
| 182 |
):
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
input_gallery_items: Since type="pil", this is a List[Tuple[PIL.Image, str]] or List[PIL.Image]
|
| 186 |
-
"""
|
| 187 |
-
if not input_gallery_items:
|
| 188 |
-
raise gr.Error("Please upload an image to edit.")
|
| 189 |
-
|
| 190 |
-
# Extract the image from the Gallery input
|
| 191 |
-
# When type='pil', Gradio Gallery returns a list of tuples (image, caption) or just images
|
| 192 |
-
first_item = input_gallery_items[0]
|
| 193 |
|
| 194 |
-
if
|
| 195 |
-
#
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
| 202 |
adapters_map = {
|
| 203 |
-
"Texture Edit": "texture
|
| 204 |
-
"Fuse-Objects": "
|
| 205 |
-
"Face-Swap": "
|
| 206 |
}
|
| 207 |
|
| 208 |
active_adapter = adapters_map.get(lora_adapter)
|
| 209 |
|
| 210 |
-
# Reset adapters first, then activate selected
|
| 211 |
if active_adapter:
|
| 212 |
pipe.set_adapters([active_adapter], adapter_weights=[1.0])
|
| 213 |
else:
|
|
@@ -219,11 +211,15 @@ def infer(
|
|
| 219 |
generator = torch.Generator(device=device).manual_seed(seed)
|
| 220 |
negative_prompt = "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry"
|
| 221 |
|
| 222 |
-
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
|
| 225 |
result = pipe(
|
| 226 |
-
image=
|
| 227 |
prompt=prompt,
|
| 228 |
negative_prompt=negative_prompt,
|
| 229 |
height=height,
|
|
@@ -235,32 +231,27 @@ def infer(
|
|
| 235 |
|
| 236 |
return result, seed
|
| 237 |
|
| 238 |
-
@spaces.GPU(duration=
|
| 239 |
-
def infer_example(
|
| 240 |
-
#
|
| 241 |
-
if
|
| 242 |
return None, 0
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
# However, since we use infer_example as the fn, we mimic the infer logic.
|
| 247 |
-
|
| 248 |
-
# For examples with type="pil", gradio usually converts paths to PIL.
|
| 249 |
-
return infer(
|
| 250 |
-
input_gallery_items,
|
| 251 |
prompt,
|
| 252 |
lora_adapter,
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
steps
|
| 257 |
)
|
| 258 |
-
|
| 259 |
|
| 260 |
css="""
|
| 261 |
#col-container {
|
| 262 |
margin: 0 auto;
|
| 263 |
-
max-width:
|
| 264 |
}
|
| 265 |
#main-title h1 {font-size: 2.1em !important;}
|
| 266 |
"""
|
|
@@ -268,72 +259,57 @@ css="""
|
|
| 268 |
with gr.Blocks(css=css, theme=steel_blue_theme) as demo:
|
| 269 |
with gr.Column(elem_id="col-container"):
|
| 270 |
gr.Markdown("# **Qwen-Image-Edit-2509-LoRAs-Fast-Fusion**", elem_id="main-title")
|
| 271 |
-
gr.Markdown("
|
| 272 |
|
| 273 |
with gr.Row(equal_height=True):
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
label="
|
| 278 |
-
|
| 279 |
-
type="pil",
|
| 280 |
-
interactive=True,
|
| 281 |
-
height=290,
|
| 282 |
-
columns=1
|
| 283 |
-
)
|
| 284 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
prompt = gr.Text(
|
| 286 |
label="Edit Prompt",
|
| 287 |
show_label=True,
|
| 288 |
-
placeholder="e.g.,
|
| 289 |
)
|
| 290 |
-
|
| 291 |
-
run_button = gr.Button("Edit Image", variant="primary")
|
| 292 |
-
|
| 293 |
-
with gr.Column():
|
| 294 |
-
output_image = gr.Image(label="Output Image", interactive=False, format="png", height=350)
|
| 295 |
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
choices=["Texture Edit", "Fuse-Objects", "Face-Swap"],
|
| 300 |
-
value="Texture Edit"
|
| 301 |
-
)
|
| 302 |
-
with gr.Accordion("Advanced Settings", open=False, visible=False):
|
| 303 |
seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0)
|
| 304 |
randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
|
| 305 |
-
guidance_scale = gr.Slider(label="Guidance Scale", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
|
| 306 |
steps = gr.Slider(label="Inference Steps", minimum=1, maximum=50, step=1, value=4)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
|
|
|
|
| 308 |
gr.Examples(
|
| 309 |
examples=[
|
| 310 |
-
|
| 311 |
-
[
|
| 312 |
-
|
| 313 |
-
"Change the material of the object to rusted metal texture.",
|
| 314 |
-
"Texture Edit"
|
| 315 |
-
],
|
| 316 |
-
[
|
| 317 |
-
["examples/fusion_sample.jpg"],
|
| 318 |
-
"Fuse the product naturally into the background.",
|
| 319 |
-
"Fuse-Objects"
|
| 320 |
-
],
|
| 321 |
-
[
|
| 322 |
-
["examples/face_sample.jpg"],
|
| 323 |
-
"Swap the face with a cyberpunk robot face.",
|
| 324 |
-
"Face-Swap"
|
| 325 |
-
],
|
| 326 |
],
|
| 327 |
-
inputs=[
|
| 328 |
outputs=[output_image, seed],
|
| 329 |
fn=infer_example,
|
| 330 |
cache_examples=False,
|
| 331 |
-
label="Examples (Ensure
|
| 332 |
)
|
| 333 |
|
| 334 |
run_button.click(
|
| 335 |
fn=infer,
|
| 336 |
-
inputs=[
|
| 337 |
outputs=[output_image, seed]
|
| 338 |
)
|
| 339 |
|
|
|
|
| 14 |
except ImportError:
|
| 15 |
class spaces:
|
| 16 |
@staticmethod
|
| 17 |
+
def GPU(duration=60):
|
| 18 |
def decorator(func):
|
| 19 |
return func
|
| 20 |
return decorator
|
| 21 |
|
| 22 |
+
# --- Custom Theme Setup ---
|
| 23 |
colors.steel_blue = colors.Color(
|
| 24 |
name="steel_blue",
|
| 25 |
c50="#EBF3F8",
|
|
|
|
| 90 |
|
| 91 |
# --- Hardware Setup ---
|
| 92 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 93 |
+
dtype = torch.bfloat16
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
# --- Imports for Custom Pipeline ---
|
| 96 |
from diffusers import FlowMatchEulerDiscreteScheduler
|
|
|
|
| 98 |
from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
|
| 99 |
from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
|
| 100 |
|
| 101 |
+
# --- Model Initialization ---
|
| 102 |
+
print("Loading Qwen Image Edit Pipeline...")
|
|
|
|
| 103 |
pipe = QwenImageEditPlusPipeline.from_pretrained(
|
| 104 |
"Qwen/Qwen-Image-Edit-2509",
|
| 105 |
transformer=QwenImageTransformer2DModel.from_pretrained(
|
|
|
|
| 111 |
torch_dtype=dtype
|
| 112 |
).to(device)
|
| 113 |
|
| 114 |
+
# 1. Load and Fuse Lightning (for speed)
|
| 115 |
+
print("Loading and Fusing Lightning LoRA...")
|
| 116 |
+
pipe.load_lora_weights("lightx2v/Qwen-Image-Lightning",
|
| 117 |
+
weight_name="Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors",
|
| 118 |
+
adapter_name="lightning")
|
| 119 |
+
pipe.fuse_lora(adapter_names=["lightning"], lora_scale=1.0)
|
| 120 |
|
| 121 |
+
# 2. Load Task Specific LoRAs
|
| 122 |
+
print("Loading Task Adapters...")
|
|
|
|
|
|
|
| 123 |
|
| 124 |
+
# Texture
|
| 125 |
+
pipe.load_lora_weights("tarn59/apply_texture_qwen_image_edit_2509",
|
| 126 |
+
weight_name="apply_texture_v2_qwen_image_edit_2509.safetensors",
|
| 127 |
+
adapter_name="texture")
|
| 128 |
|
| 129 |
+
# Fusion (Fuse-Objects)
|
| 130 |
+
pipe.load_lora_weights("dx8152/Qwen-Image-Edit-2509-Fusion",
|
| 131 |
+
weight_name="溶图.safetensors",
|
| 132 |
+
adapter_name="fusion")
|
| 133 |
|
| 134 |
+
# Face Swap
|
| 135 |
+
pipe.load_lora_weights("Alissonerdx/BFS-Best-Face-Swap",
|
| 136 |
+
weight_name="bfs_head_v3_qwen_image_edit_2509.safetensors",
|
| 137 |
+
adapter_name="faceswap")
|
| 138 |
|
| 139 |
+
# Attempt Flash Attention 3
|
| 140 |
try:
|
| 141 |
pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
|
| 142 |
print("Flash Attention 3 Processor set successfully.")
|
| 143 |
except Exception as e:
|
| 144 |
+
print(f"Could not set FA3 processor (likely hardware mismatch): {e}. using default attention.")
|
| 145 |
|
| 146 |
MAX_SEED = np.iinfo(np.int32).max
|
| 147 |
|
|
|
|
| 160 |
aspect_ratio = original_width / original_height
|
| 161 |
new_width = int(new_height * aspect_ratio)
|
| 162 |
|
| 163 |
+
# Ensure dimensions are multiples of 16
|
| 164 |
new_width = (new_width // 16) * 16
|
| 165 |
new_height = (new_height // 16) * 16
|
| 166 |
|
| 167 |
return new_width, new_height
|
| 168 |
|
| 169 |
+
@spaces.GPU(duration=60)
|
| 170 |
def infer(
|
| 171 |
+
image_1,
|
| 172 |
+
image_2,
|
| 173 |
prompt,
|
| 174 |
lora_adapter,
|
| 175 |
seed,
|
|
|
|
| 178 |
steps,
|
| 179 |
progress=gr.Progress(track_tqdm=True)
|
| 180 |
):
|
| 181 |
+
if image_1 is None or image_2 is None:
|
| 182 |
+
raise gr.Error("Please upload both images for Fusion/Texture/FaceSwap tasks.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
+
if not prompt:
|
| 185 |
+
# Add default prompts based on mode if user leaves empty (optional helper)
|
| 186 |
+
if lora_adapter == "Face-Swap":
|
| 187 |
+
prompt = "Swap the face."
|
| 188 |
+
elif lora_adapter == "Texture Edit":
|
| 189 |
+
prompt = "Apply texture to object."
|
| 190 |
+
elif lora_adapter == "Fuse-Objects":
|
| 191 |
+
prompt = "Fuse object into background."
|
| 192 |
+
|
| 193 |
+
# Switch Adapters
|
| 194 |
+
# Note: Lightning is already fused, so we just enable the style adapter
|
| 195 |
adapters_map = {
|
| 196 |
+
"Texture Edit": "texture",
|
| 197 |
+
"Fuse-Objects": "fusion",
|
| 198 |
+
"Face-Swap": "faceswap",
|
| 199 |
}
|
| 200 |
|
| 201 |
active_adapter = adapters_map.get(lora_adapter)
|
| 202 |
|
|
|
|
| 203 |
if active_adapter:
|
| 204 |
pipe.set_adapters([active_adapter], adapter_weights=[1.0])
|
| 205 |
else:
|
|
|
|
| 211 |
generator = torch.Generator(device=device).manual_seed(seed)
|
| 212 |
negative_prompt = "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry"
|
| 213 |
|
| 214 |
+
# Prepare Images
|
| 215 |
+
img1_pil = image_1.convert("RGB")
|
| 216 |
+
img2_pil = image_2.convert("RGB")
|
| 217 |
+
|
| 218 |
+
# Calculate dimensions based on the primary image (Image 1)
|
| 219 |
+
width, height = update_dimensions_on_upload(img1_pil)
|
| 220 |
|
| 221 |
result = pipe(
|
| 222 |
+
image=[img1_pil, img2_pil], # Pass both images
|
| 223 |
prompt=prompt,
|
| 224 |
negative_prompt=negative_prompt,
|
| 225 |
height=height,
|
|
|
|
| 231 |
|
| 232 |
return result, seed
|
| 233 |
|
| 234 |
+
@spaces.GPU(duration=60)
|
| 235 |
+
def infer_example(image_1, image_2, prompt, lora_adapter):
|
| 236 |
+
# Wrapper for examples that sets defaults
|
| 237 |
+
if image_1 is None or image_2 is None:
|
| 238 |
return None, 0
|
| 239 |
+
result, seed = infer(
|
| 240 |
+
image_1.convert("RGB"),
|
| 241 |
+
image_2.convert("RGB"),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
prompt,
|
| 243 |
lora_adapter,
|
| 244 |
+
0, # seed
|
| 245 |
+
True, # randomize
|
| 246 |
+
1.0, # guidance
|
| 247 |
+
4 # steps (Lightning optimized)
|
| 248 |
)
|
| 249 |
+
return result, seed
|
| 250 |
|
| 251 |
css="""
|
| 252 |
#col-container {
|
| 253 |
margin: 0 auto;
|
| 254 |
+
max-width: 1100px;
|
| 255 |
}
|
| 256 |
#main-title h1 {font-size: 2.1em !important;}
|
| 257 |
"""
|
|
|
|
| 259 |
with gr.Blocks(css=css, theme=steel_blue_theme) as demo:
|
| 260 |
with gr.Column(elem_id="col-container"):
|
| 261 |
gr.Markdown("# **Qwen-Image-Edit-2509-LoRAs-Fast-Fusion**", elem_id="main-title")
|
| 262 |
+
gr.Markdown("Advanced dual-image editing: **Texture Application**, **Object Fusion**, and **Face Swapping** using Qwen-Image-Edit-2509 + Lightning ⚡ (4 Steps).")
|
| 263 |
|
| 264 |
with gr.Row(equal_height=True):
|
| 265 |
+
# Left Column: Inputs
|
| 266 |
+
with gr.Column(scale=1):
|
| 267 |
+
with gr.Row():
|
| 268 |
+
image_1 = gr.Image(label="Base / Background / Body", type="pil", height=250)
|
| 269 |
+
image_2 = gr.Image(label="Reference / Texture / Face", type="pil", height=250)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
|
| 271 |
+
lora_adapter = gr.Dropdown(
|
| 272 |
+
label="Choose Editing Style",
|
| 273 |
+
choices=["Texture Edit", "Fuse-Objects", "Face-Swap"],
|
| 274 |
+
value="Texture Edit",
|
| 275 |
+
info="Select the operation to perform."
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
prompt = gr.Text(
|
| 279 |
label="Edit Prompt",
|
| 280 |
show_label=True,
|
| 281 |
+
placeholder="e.g., Apply wood texture to the mug...",
|
| 282 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
|
| 284 |
+
run_button = gr.Button("Generate Fusion", variant="primary")
|
| 285 |
+
|
| 286 |
+
with gr.Accordion("Advanced Settings", open=False):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0)
|
| 288 |
randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
|
| 289 |
+
guidance_scale = gr.Slider(label="True Guidance Scale", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
|
| 290 |
steps = gr.Slider(label="Inference Steps", minimum=1, maximum=50, step=1, value=4)
|
| 291 |
+
|
| 292 |
+
# Right Column: Output
|
| 293 |
+
with gr.Column(scale=1):
|
| 294 |
+
output_image = gr.Image(label="Output Image", interactive=False, format="png", height=550)
|
| 295 |
|
| 296 |
+
# Examples
|
| 297 |
gr.Examples(
|
| 298 |
examples=[
|
| 299 |
+
["examples/mug.jpg", "examples/wood.jpg", "Apply wood texture to the mug.", "Texture Edit"],
|
| 300 |
+
["examples/room.jpg", "examples/chair.jpg", "Put the chair in the room naturally.", "Fuse-Objects"],
|
| 301 |
+
["examples/body.jpg", "examples/face.jpg", "Swap the face.", "Face-Swap"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
],
|
| 303 |
+
inputs=[image_1, image_2, prompt, lora_adapter],
|
| 304 |
outputs=[output_image, seed],
|
| 305 |
fn=infer_example,
|
| 306 |
cache_examples=False,
|
| 307 |
+
label="Examples (Ensure files exist in 'examples/' folder)"
|
| 308 |
)
|
| 309 |
|
| 310 |
run_button.click(
|
| 311 |
fn=infer,
|
| 312 |
+
inputs=[image_1, image_2, prompt, lora_adapter, seed, randomize_seed, guidance_scale, steps],
|
| 313 |
outputs=[output_image, seed]
|
| 314 |
)
|
| 315 |
|