Qwen-Image-Edit / app.py
HAL1993's picture
Update app.py
26147d6 verified
import os
import math
import random
import logging
import requests
import numpy as np
import torch
import spaces
from fastapi import FastAPI, HTTPException
from diffusers import FlowMatchEulerDiscreteScheduler
from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
from optimization import optimize_pipeline_
from PIL import Image
import gradio as gr
logging.basicConfig(
level=logging.INFO,
filename="qwen_image_editor.log",
filemode="a",
format="%(asctime)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)
@spaces.GPU
def translate_albanian_to_english(text: str, language: str = "en"):
if not text.strip():
raise gr.Error("Please enter a description.")
for attempt in range(2):
try:
response = requests.post(
"https://hal1993-mdftranslation1234567890abcdef1234567890-fc073a6.hf.space/v1/translate",
json={"from_language": "sq", "to_language": "en", "input_text": text},
headers={"accept": "application/json", "Content-Type": "application/json"},
timeout=5,
)
response.raise_for_status()
translated = response.json().get("translate", "")
logger.info(f"Translation response: {translated}")
return translated
except Exception as e:
logger.error(f"Translation error (attempt {attempt + 1}): {e}")
if attempt == 1:
raise gr.Error("Translation failed. Please try again.")
raise gr.Error("Translation failed. Please try again.")
dtype = torch.bfloat16
device = "cuda" if torch.cuda.is_available() else "cpu"
scheduler_cfg = {
"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_cfg)
pipe = QwenImageEditPlusPipeline.from_pretrained(
"Qwen/Qwen-Image-Edit-2509",
scheduler=scheduler,
torch_dtype=dtype,
).to(device)
pipe.load_lora_weights(
"lightx2v/Qwen-Image-Lightning",
weight_name="Qwen-Image-Lightning-4steps-V2.0.safetensors",
)
pipe.fuse_lora()
pipe.transformer.__class__ = QwenImageTransformer2DModel
pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
optimize_pipeline_(
pipe,
image=[Image.new("RGB", (1024, 1024)), Image.new("RGB", (1024, 1024))],
prompt="prompt",
)
MAX_SEED = np.iinfo(np.int32).max
QUALITY_PROMPT = ", high quality, detailed, vibrant, professional lighting"
@spaces.GPU(duration=40)
def infer(image, prompt):
negative_prompt = ""
seed = random.randint(0, MAX_SEED)
generator = torch.Generator(device=device).manual_seed(seed)
prompt_en = translate_albanian_to_english(prompt.strip(), language="en") + QUALITY_PROMPT
pil_img = None
if image is not None:
if isinstance(image, Image.Image):
pil_img = image.convert("RGB")
elif isinstance(image, str):
pil_img = Image.open(image).convert("RGB")
elif hasattr(image, "name"):
pil_img = Image.open(image.name).convert("RGB")
output = pipe(
image=[pil_img] if pil_img is not None else None,
prompt=prompt_en,
height=None,
width=None,
negative_prompt=negative_prompt,
num_inference_steps=4,
generator=generator,
true_cfg_scale=1.0,
num_images_per_prompt=1,
).images
return output[0] if output else None
def create_demo():
with gr.Blocks(css="", title="Qwen Image Editor") as demo:
gr.HTML(
"""
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700&display=swap');
@keyframes glow {0%{box-shadow:0 0 14px rgba(0,255,128,0.5);}50%{box-shadow:0 0 14px rgba(0,255,128,0.7);}100%{box-shadow:0 0 14px rgba(0,255,128,0.5);}}
@keyframes glow-hover {0%{box-shadow:0 0 20px rgba(0,255,128,0.7);}50%{box-shadow:0 0 20px rgba(0,255,128,0.9);}100%{box-shadow:0 0 20px rgba(0,255,128,0.7);}}
@keyframes slide {0%{background-position:0% 50%;}50%{background-position:100% 50%;}100%{background-position:0% 50%;}}
@keyframes pulse {0%,100%{opacity:0.7;}50%{opacity:1;}}
body{
background:#000000 !important;
color:#FFFFFF !important;
font-family:'Orbitron',sans-serif;
min-height:100vh;
margin:0 !important;
padding:0 !important;
width:100% !important;
max-width:100vw !important;
overflow-x:hidden !important;
display:flex !important;
justify-content:center;
align-items:center;
flex-direction:column;
}
body::before{
content:"";
display:block;
height:600px;
background:#000000 !important;
}
.gr-blocks,.container{
width:100% !important;
max-width:100vw !important;
margin:0 !important;
padding:0 !important;
box-sizing:border-box !important;
overflow-x:hidden !important;
background:#000000 !important;
color:#FFFFFF !important;
}
.gr-row,.gr-column{
width:100% !important;
max-width:100vw !important;
margin:0 !important;
padding:0 !important;
box-sizing:border-box !important;
}
.gradio-container,.gradio-app,.gradio-interface{
width:100% !important;
max-width:100vw !important;
margin:0 !important;
padding:0 !important;
box-sizing:border-box !important;
}
#general_items{
width:100% !important;
max-width:100vw !important;
margin:2rem 0 !important;
display:flex !important;
flex-direction:column;
align-items:center;
justify-content:center;
background:#000000 !important;
color:#FFFFFF !important;
}
#input_column{
background:#000000 !important;
border:none !important;
border-radius:8px;
padding:1rem !important;
box-shadow:0 0 10px rgba(255,255,255,0.3) !important;
width:100% !important;
max-width:100vw !important;
box-sizing:border-box !important;
color:#FFFFFF !important;
}
h1{
font-size:5rem;
font-weight:700;
text-align:center;
color:#FFFFFF !important;
text-shadow:0 0 8px rgba(255,255,255,0.3) !important;
margin:0 auto .5rem;
display:block;
max-width:100%;
}
#subtitle{
font-size:1rem;
text-align:center;
color:#FFFFFF !important;
opacity:0.8;
margin-bottom:1rem;
display:block;
max-width:100%;
}
.gradio-component{
background:#000000 !important;
border:none;
margin:0.75rem 0;
width:100% !important;
max-width:100vw !important;
color:#FFFFFF !important;
}
.image-container{
aspect-ratio:1/1;
width:100% !important;
max-width:100vw !important;
min-height:500px;
height:auto;
border:0.5px solid #FFFFFF !important;
border-radius:4px;
box-sizing:border-box !important;
background:#000000 !important;
box-shadow:0 0 10px rgba(255,255,255,0.3) !important;
position:relative;
color:#FFFFFF !important;
}
.image-container img{
width:100% !important;
height:auto;
box-sizing:border-box !important;
display:block !important;
}
.image-container[aria-label="Input Image"] .file-upload,
.image-container[aria-label="Input Image"] .file-preview,
.image-container[aria-label="Input Image"] .image-actions,
.image-container[aria-label="Input Image"] .gr-file-upload,
.image-container[aria-label="Input Image"] .gr-file,
.image-container[aria-label="Input Image"] .gr-actions,
.image-container[aria-label="Input Image"] .gr-upload-button,
.image-container[aria-label="Input Image"] .gr-image-toolbar,
.image-container[aria-label="Input Image"] .gr-file-actions,
.image-container[aria-label="Input Image"] .gr-upload-options,
div[aria-label="Input Image"] > div > div:not(.image-container),
div[aria-label="Input Image"] .gr-button,
.image-container[aria-label="Result Image"] .file-upload,
.image-container[aria-label="Result Image"] .file-preview,
.image-container[aria-label="Result Image"] .image-actions,
.image-container[aria-label="Result Image"] .gr-file-upload,
.image-container[aria-label="Result Image"] .gr-file,
.image-container[aria-label="Result Image"] .gr-actions,
.image-container[aria-label="Result Image"] .gr-upload-button,
.image-container[aria-label="Result Image"] .gr-image-toolbar,
.image-container[aria-label="Result Image"] .gr-file-actions,
.image-container[aria-label="Result Image"] .gr-upload-options,
div[aria-label="Result Image"] > div > div:not(.image-container),
div[aria-label="Result Image"] .gr-button{
display:none !important;
}
.image-container[aria-label="Result Image"].processing{
background:#000000 !important;
position:relative !important;
}
.image-container[aria-label="Result Image"].processing::before{
content:"PROCESSING...";
position:absolute !important;
top:50% !important;
left:50% !important;
transform:translate(-50%,-50%) !important;
color:#FFFFFF !important;
font-family:'Orbitron',sans-serif !important;
font-size:1.8rem !important;
font-weight:700 !important;
text-align:center !important;
text-shadow:0 0 10px rgba(0,255,128,0.8) !important;
animation:pulse 1.5s ease-in-out infinite,glow 2s ease-in-out infinite !important;
z-index:9999 !important;
width:100% !important;
height:100% !important;
display:flex !important;
align-items:center !important;
justify-content:center !important;
pointer-events:none !important;
background:#000000 !important;
border-radius:4px !important;
box-sizing:border-box !important;
}
.image-container[aria-label="Result Image"].processing *{
display:none !important;
}
.image-container[aria-label="Result Image"].processing img{
display:none !important;
}
input,textarea{
background:#000000 !important;
color:#FFFFFF !important;
border:1px solid #FFFFFF !important;
border-radius:4px;
padding:0.5rem;
width:100% !important;
max-width:100vw !important;
box-sizing:border-box !important;
}
input:hover,textarea:hover{
box-shadow:0 0 8px rgba(255,255,255,0.3) !important;
transition:box-shadow 0.3s;
}
.gr-button-primary{
background:linear-gradient(90deg,rgba(0,255,128,0.3),rgba(0,200,100,0.3),rgba(0,255,128,0.3)) !important;
background-size:200% 100%;
animation:slide 4s ease-in-out infinite,glow 3s ease-in-out infinite;
color:#FFFFFF !important;
border:1px solid #FFFFFF !important;
border-radius:6px;
padding:0.75rem 1.5rem;
font-size:1.1rem;
font-weight:600;
box-shadow:0 0 14px rgba(0,255,128,0.7) !important;
transition:box-shadow 0.3s,transform 0.3s;
width:100% !important;
max-width:100vw !important;
min-height:48px;
cursor:pointer;
}
.gr-button-primary:hover{
box-shadow:0 0 20px rgba(0,255,128,0.9) !important;
animation:slide 4s ease-in-out infinite,glow-hover 3s ease-in-out infinite;
transform:scale(1.05);
}
button[aria-label="Fullscreen"],button[aria-label="Share"]{
display:none !important;
}
button[aria-label="Download"]{
transform:scale(3);
transform-origin:top right;
background:#000000 !important;
color:#FFFFFF !important;
border:1px solid #FFFFFF !important;
border-radius:4px;
padding:0.4rem !important;
margin:0.5rem !important;
box-shadow:0 0 8px rgba(255,255,255,0.3) !important;
transition:box-shadow 0.3s;
}
button[aria-label="Download"]:hover{
box-shadow:0 0 12px rgba(255,255,255,0.5) !important;
}
.progress-text,.gr-progress,.gr-prose,.gr-log{
display:none !important;
}
footer,.gr-button-secondary{
display:none !important;
}
.gr-group{
background:#000000 !important;
border:none !important;
width:100% !important;
max-width:100vw !important;
}
@media (max-width:768px){
h1{font-size:4rem;}
#subtitle{font-size:0.9rem;}
.gr-button-primary{
padding:0.6rem 1rem;
font-size:1rem;
box-shadow:0 0 10px rgba(0,255,128,0.7) !important;
animation:slide 4s ease-in-out infinite,glow 3s ease-in-out infinite;
}
.image-container{
min-height:300px;
box-shadow:0 0 8px rgba(255,255,255,0.3) !important;
}
.image-container[aria-label="Result Image"].processing::before{
font-size:1.2rem !important;
}
}
</style>
<script>
const allowedPath = /^\\/y7u8i9o0p1l2k3j4h5g6f7d8s9a0q1w2e3r4t5y6u7i8o9p0l1k2j3h4g5f6d7s8(\\/.*)?$/;
if (!allowedPath.test(window.location.pathname)) {
document.body.innerHTML = '<h1 style="color:#ef4444;font-family:sans-serif;text-align:center;margin-top:100px;">500 Internal Server Error</h1>';
throw new Error('500');
}
document.addEventListener('DOMContentLoaded', () => {
const generateBtn = document.querySelector('.gr-button-primary');
const resultContainer = document.querySelector('.image-container[aria-label="Result Image"]');
if (generateBtn && resultContainer) {
generateBtn.addEventListener('click', () => {
resultContainer.classList.add('processing');
resultContainer.querySelectorAll('*').forEach(child => {
if (child.tagName !== 'IMG') child.style.display = 'none';
});
});
const imgObserver = new MutationObserver(muts => {
muts.forEach(m => {
m.addedNodes.forEach(node => {
if (node.nodeType === 1 && (node.tagName === 'IMG' || node.querySelector('img'))) {
resultContainer.classList.remove('processing');
imgObserver.disconnect();
}
});
});
});
imgObserver.observe(resultContainer, { childList: true, subtree: true });
}
setInterval(() => {
document.querySelectorAll('.progress-text,.gr-progress,[class*="progress"]').forEach(el => el.remove());
}, 500);
});
</script>
"""
)
with gr.Row(elem_id="general_items"):
gr.Markdown("# ")
gr.Markdown("Edit your images with prompt descriptions", elem_id="subtitle")
with gr.Column(elem_id="input_column"):
input_image = gr.Image(
label="Input Image",
type="pil",
sources=["upload"],
show_download_button=False,
show_share_button=False,
interactive=True,
elem_classes=["gradio-component", "image-container"]
)
prompt = gr.Textbox(
label="Prompt",
lines=3,
elem_classes=["gradio-component"]
)
run_button = gr.Button(
"Edit!",
variant="primary",
elem_classes=["gradio-component", "gr-button-primary"]
)
result_image = gr.Image(
label="Result Image",
type="pil",
interactive=False,
show_download_button=True,
show_share_button=False,
elem_classes=["gradio-component", "image-container"]
)
run_button.click(fn=infer, inputs=[input_image, prompt], outputs=[result_image])
prompt.submit(fn=infer, inputs=[input_image, prompt], outputs=[result_image])
return demo
app = FastAPI()
demo = create_demo()
app.mount("/y7u8i9o0p1l2k3j4h5g6f7d8s9a0q1w2e3r4t5y6u7i8o9p0l1k2j3h4g5f6d7s8", demo.app)
@app.get("/{path:path}")
async def catch_all(path: str):
raise HTTPException(status_code=500, detail="Internal Server Error")
if __name__ == "__main__":
logger.info(f"Gradio version: {gr.__version__}")
demo.queue().launch(share=True)