ZImage-Img2Img / app.py
NotRIK's picture
Update app.py
8abc410 verified
Raw History Blame Contribute Delete
10.8 kB
import random
import gradio as gr
import spaces
import torch
from PIL import Image
from diffusers import ZImageImg2ImgPipeline
# ============================================================
# CONFIG
# ============================================================
MODEL_ID = "Tongyi-MAI/Z-Image-Turbo"
# IMPORTANT:
# Z-Image strength=1.0 essentially ignores the input image.
# We intentionally cap the UI at 0.85 for better editing behavior.
MIN_STRENGTH = 0.15
MAX_STRENGTH = 0.85
DEFAULT_STRENGTH = 0.55
NUM_INFERENCE_STEPS = 8
GUIDANCE_SCALE = 0.0
MAX_IMAGE_SIZE = 1024
GPU_DURATION = 180
# ============================================================
# MODEL
# ============================================================
print(
"Loading Z-Image-Turbo...",
flush=True,
)
pipe = ZImageImg2ImgPipeline.from_pretrained(
MODEL_ID,
torch_dtype=torch.bfloat16,
)
pipe.to("cuda")
pipe.set_progress_bar_config(
disable=True
)
print(
"Z-Image-Turbo loaded successfully.",
flush=True,
)
# ============================================================
# IMAGE PREPARATION
# ============================================================
def prepare_image(
image: Image.Image,
) -> Image.Image:
if image is None:
raise ValueError(
"Please upload an image."
)
image = image.convert("RGB")
width, height = image.size
if width < 64 or height < 64:
raise ValueError(
"Image must be at least 64Γ—64 pixels."
)
# Keep the original aspect ratio.
scale = min(
MAX_IMAGE_SIZE / width,
MAX_IMAGE_SIZE / height,
1.0,
)
new_width = int(width * scale)
new_height = int(height * scale)
# Z-Image works with dimensions divisible by 8.
new_width = max(
64,
(new_width // 8) * 8,
)
new_height = max(
64,
(new_height // 8) * 8,
)
if (
new_width != image.width
or new_height != image.height
):
image = image.resize(
(new_width, new_height),
Image.Resampling.LANCZOS,
)
return image
# ============================================================
# PROMPT HANDLING
# ============================================================
def build_edit_prompt(
prompt: str,
strength: float,
) -> str:
prompt = prompt.strip()
# Lower strength = stronger preservation.
if strength <= 0.35:
prefix = (
"Edit the provided image while preserving "
"the original subject, identity, composition, "
"camera angle, pose, framing, and overall "
"structure. Make only the requested visual "
"changes. "
)
elif strength <= 0.60:
prefix = (
"Transform the provided image according to "
"the request while preserving the main subject, "
"recognizable identity, composition, and "
"important visual structure. "
)
else:
prefix = (
"Creatively transform the provided image "
"according to the request while keeping the "
"main subject and important visual identity "
"recognizable. Do not completely replace the "
"original concept unless explicitly requested. "
)
return prefix + prompt
# ============================================================
# GENERATION
# ============================================================
@spaces.GPU(
duration=GPU_DURATION
)
def generate(
image,
prompt,
strength,
seed,
):
if image is None:
raise gr.Error(
"Please upload an image first."
)
if not prompt or not prompt.strip():
raise gr.Error(
"Please enter a transformation prompt."
)
# --------------------------------------------------------
# Strength
# --------------------------------------------------------
try:
strength = float(strength)
except Exception:
raise gr.Error(
"Invalid transformation strength."
)
strength = max(
MIN_STRENGTH,
min(
MAX_STRENGTH,
strength,
),
)
# --------------------------------------------------------
# Seed
# --------------------------------------------------------
try:
seed = int(seed)
except Exception:
seed = -1
if seed < 0:
seed = random.SystemRandom().randint(
0,
2**32 - 1,
)
# --------------------------------------------------------
# Generation
# --------------------------------------------------------
try:
image = prepare_image(image)
final_prompt = build_edit_prompt(
prompt,
strength,
)
generator = torch.Generator(
device="cuda"
).manual_seed(seed)
print(
"Generation started | "
f"seed={seed} | "
f"strength={strength}",
flush=True,
)
print(
f"Prompt: {final_prompt}",
flush=True,
)
with torch.inference_mode():
result = pipe(
prompt=final_prompt,
image=image,
strength=strength,
num_inference_steps=NUM_INFERENCE_STEPS,
guidance_scale=GUIDANCE_SCALE,
generator=generator,
)
output = result.images[0]
print(
"Generation complete | "
f"seed={seed}",
flush=True,
)
return output, seed
except torch.cuda.OutOfMemoryError:
if torch.cuda.is_available():
torch.cuda.empty_cache()
raise gr.Error(
"GPU memory was insufficient. "
"Try a smaller input image."
)
except Exception as exc:
print(
"Generation error:",
repr(exc),
flush=True,
)
if torch.cuda.is_available():
torch.cuda.empty_cache()
raise gr.Error(
f"Generation failed: {exc}"
)
# ============================================================
# UI
# ============================================================
CSS = """
#title {
text-align: center;
}
#subtitle {
text-align: center;
opacity: 0.7;
}
.generate-btn {
min-height: 52px;
font-size: 18px !important;
font-weight: 600 !important;
}
.strength-info {
text-align: center;
opacity: 0.7;
font-size: 13px;
}
"""
with gr.Blocks(
title="IMAGINE β€” Z-Image Image to Image",
css=CSS,
) as demo:
gr.Markdown(
"# IMAGINE",
elem_id="title",
)
gr.Markdown(
"Z-Image-Turbo Image Transformation",
elem_id="subtitle",
)
with gr.Row():
# ====================================================
# INPUT
# ====================================================
with gr.Column():
input_image = gr.Image(
type="pil",
label="Upload Image",
)
prompt = gr.Textbox(
label="Transformation Prompt",
placeholder=(
"Example: Change the clothes to "
"a black formal suit while keeping "
"the person's face and pose."
),
lines=4,
)
strength = gr.Slider(
minimum=MIN_STRENGTH,
maximum=MAX_STRENGTH,
value=DEFAULT_STRENGTH,
step=0.05,
label="Transformation Strength",
info=(
"Lower = preserve more β€’ "
"Higher = transform more"
),
)
gr.Markdown(
"Preserve ←──────── Balanced ────────→ Transform",
elem_classes="strength-info",
)
seed = gr.Number(
value=-1,
precision=0,
label="Seed",
info="-1 = random",
)
generate_button = gr.Button(
"Generate Image",
variant="primary",
elem_classes="generate-btn",
)
# ====================================================
# OUTPUT
# ====================================================
with gr.Column():
output_image = gr.Image(
type="pil",
label="Generated Image",
)
output_seed = gr.Number(
label="Used Seed",
interactive=False,
)
# ========================================================
# EXAMPLES
# ========================================================
gr.Examples(
examples=[
[
(
"Change the lighting to warm golden "
"hour lighting while keeping the "
"same person, face, pose, background "
"and composition."
),
0.30,
-1,
],
[
(
"Change the person's clothing to a "
"premium black formal outfit while "
"keeping the same face, pose and "
"composition."
),
0.50,
-1,
],
[
(
"Transform this scene into a cinematic "
"fantasy environment while keeping "
"the main subject recognizable."
),
0.70,
-1,
],
],
inputs=[
prompt,
strength,
seed,
],
)
# ========================================================
# API
# ========================================================
generate_button.click(
fn=generate,
inputs=[
input_image,
prompt,
strength,
seed,
],
outputs=[
output_image,
output_seed,
],
api_name="generate",
)
# ============================================================
# LAUNCH
# ============================================================
if __name__ == "__main__":
demo.queue(
max_size=20,
default_concurrency_limit=1,
).launch(
server_name="0.0.0.0",
server_port=7860,
)