Spaces:
Running on Zero
Running on Zero
File size: 9,047 Bytes
3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 0b000fa 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 d714174 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 d714174 3f17887 66f36d3 3f17887 66f36d3 3f17887 66f36d3 3f17887 d714174 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | import os
# Allocator config helps with transient spikes from the pixel-space / large
# attention-mask ops in the region-attention editor.
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
import spaces # noqa: E402 (must precede torch / CUDA-touching imports)
import time # noqa: E402
import random # noqa: E402
import torch # noqa: E402
import gradio as gr # noqa: E402
from PIL import Image # noqa: E402
from replan.pipelines.replan import RePlanPipeline # noqa: E402
# -----------------------------------------------------------------------------
# Model configuration
# -----------------------------------------------------------------------------
# RePlan couples a reasoning VLM planner (Qwen2.5-VL-7B, fine-tuned by the
# authors) with a diffusion editor. We use FLUX.2 [klein] 4B as the editor:
# it is Apache-2.0 (ungated), compact (~16 GB bf16), distilled (4 steps), and
# is one of the backbones the RePlan authors explicitly support.
VLM_CKPT = "TainU/RePlan-Qwen2.5-VL-7B"
DIFFUSION_MODEL = "black-forest-labs/FLUX.2-klein-4B"
PIPELINE_TYPE = "klein"
# klein-specific defaults from RePlan's run_replan.py
DEFAULT_STEPS = 4
DEFAULT_EXPAND_VALUE = 0.15
DEFAULT_ATTENTION_SWITCH_STEP = 0.05
MAX_SEED = 2**31 - 1
# Load both stages once at module scope, eagerly moved to CUDA. ZeroGPU packs
# the weights to disk at startup and streams them into VRAM on the first
# @spaces.GPU call. enable_flex_attn is left False (see below), so the editor
# uses a dense attention mask via SDPA and never needs torch.compile.
pipeline = RePlanPipeline(
vlm_ckpt_path=VLM_CKPT,
diffusion_model_name=DIFFUSION_MODEL,
pipeline_type=PIPELINE_TYPE,
output_dir=None,
device="cuda",
torch_dtype=torch.bfloat16,
vlm_prompt_template_path="replan.txt",
init_vlm=True,
image_dir=None,
stage_offload="none",
)
# Measured GPU time for a single-region 4-step edit is ~5-6s. Multi-region
# instructions produce longer VLM reasoning (up to 2048 tokens) + more diffusion
# work, so we keep a comfortable-but-lean margin.
@spaces.GPU(duration=60)
def edit_image(
image: Image.Image,
instruction: str,
seed: int = 0,
randomize_seed: bool = True,
steps: int = DEFAULT_STEPS,
guidance_scale: float = 4.0,
expand_value: float = DEFAULT_EXPAND_VALUE,
progress=gr.Progress(track_tqdm=True),
):
"""Edit an image following a complex natural-language instruction.
RePlan first runs a reasoning VLM planner that decomposes the instruction
into region-specific bounding boxes + local hints and a global prompt, then
executes the edit with a diffusion backbone using training-free
region-constrained attention injection.
Args:
image: The input image to edit.
instruction: The natural-language editing instruction.
seed: RNG seed for reproducibility.
randomize_seed: If True, pick a fresh random seed each run.
steps: Number of diffusion denoising steps.
guidance_scale: Classifier-free guidance scale for the editor.
expand_value: Ratio by which planned bounding boxes are expanded.
Returns:
A tuple of (edited image, VLM plan/reasoning text, the seed used).
"""
if image is None:
raise gr.Error("Please provide an input image.")
if not instruction or not instruction.strip():
raise gr.Error("Please provide an editing instruction.")
if randomize_seed:
seed = random.randint(0, MAX_SEED)
seed = int(seed)
if isinstance(image, str):
image = Image.open(image)
image = image.convert("RGB")
t0 = time.perf_counter()
results = pipeline.edit_single(
image=image,
instruction=instruction.strip(),
expand_value=float(expand_value),
expand_mode="ratio",
attention_switch_step=DEFAULT_ATTENTION_SWITCH_STEP,
bboxes_attend_to_each_other=True,
symmetric_masking=False,
enable_flex_attn=False, # dense-mask SDPA path (no torch.compile)
flex_attn_use_bitmask=True,
skip_save=True,
num_inference_steps=int(steps),
guidance_scale=float(guidance_scale),
generator=torch.manual_seed(seed),
)
elapsed = time.perf_counter() - t0
edited = results["edited_image"]
response = results.get("vlm_response", "") or ""
global_prompt = results.get("global_prompt", "") or ""
bbox_data = results.get("bbox_data") or []
# Build a compact, readable plan summary from the raw VLM response.
plan_lines = [f"Inference time: {elapsed:.1f}s | seed: {seed}", ""]
if global_prompt.strip():
plan_lines.append(f"Global edit: {global_prompt.strip()}")
if bbox_data:
plan_lines.append("")
plan_lines.append(f"Planned regions ({len(bbox_data)}):")
for i, b in enumerate(bbox_data, 1):
hint = b.get("hint", "")
box = b.get("bbox_2d", "")
plan_lines.append(f" {i}. {hint} @ bbox {box}")
plan_lines.append("")
plan_lines.append("--- Full VLM reasoning ---")
plan_lines.append(response.strip())
plan_text = "\n".join(plan_lines)
return edited, plan_text, seed
# -----------------------------------------------------------------------------
# UI
# -----------------------------------------------------------------------------
CSS = """
#col-container { max-width: 1200px; margin: 0 auto; }
.dark .gradio-container { color: var(--body-text-color); }
"""
EXAMPLES = [
["examples/cup.png", "Replace the glass that has been used and left on the desk with a small potted plant"],
["examples/crowd.png", "Find the woman with light blue backpack and change the color of her shoes to red"],
["examples/festival.png", "The sun has now set, and someone has decorated the large bush on the right with colorful fairy lights for the evening's festivities."],
["examples/sunglasses.png", "Remove the sunglasses from the person sitting on the left."],
]
with gr.Blocks() as demo:
with gr.Column(elem_id="col-container"):
gr.Markdown(
"""
# RePlan — Reasoning-guided Region Planning for Image Editing
RePlan tackles **complex instruction-based image editing** by first having a
reasoning VLM planner decompose your instruction into region-specific guidance
(bounding boxes + local hints), then executing the edit with training-free
**region-constrained attention injection** on a diffusion editor.
Planner: [`TainU/RePlan-Qwen2.5-VL-7B`](https://huggingface.co/TainU/RePlan-Qwen2.5-VL-7B) ·
Editor: [`FLUX.2-klein-4B`](https://huggingface.co/black-forest-labs/FLUX.2-klein-4B) ·
[Paper](https://huggingface.co/papers/2512.16864) ·
[Code](https://github.com/dvlab-research/RePlan)
"""
)
with gr.Row():
with gr.Column():
input_image = gr.Image(label="Input image", type="pil", height=380)
instruction = gr.Textbox(
label="Editing instruction",
placeholder="e.g. Find the woman with the blue backpack and change her shoes to red",
lines=2,
)
run_btn = gr.Button("Edit image", variant="primary")
with gr.Accordion("Advanced settings", open=False):
randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
seed = gr.Number(label="Seed", value=0, precision=0)
steps = gr.Slider(
label="Denoising steps", minimum=2, maximum=12, step=1,
value=DEFAULT_STEPS,
)
guidance_scale = gr.Slider(
label="Guidance scale", minimum=1.0, maximum=6.0, step=0.5,
value=4.0,
)
expand_value = gr.Slider(
label="Region expansion (ratio)", minimum=0.0, maximum=0.5,
step=0.05, value=DEFAULT_EXPAND_VALUE,
)
with gr.Column():
output_image = gr.Image(label="Edited image", height=380)
plan_out = gr.Textbox(
label="Planner reasoning & regions", lines=12,
)
gr.Examples(
examples=EXAMPLES,
inputs=[input_image, instruction],
outputs=[output_image, plan_out, seed],
fn=edit_image,
cache_examples=True,
cache_mode="lazy",
)
inputs = [input_image, instruction, seed, randomize_seed, steps, guidance_scale, expand_value]
outputs = [output_image, plan_out, seed]
run_btn.click(edit_image, inputs=inputs, outputs=outputs, api_name="edit")
instruction.submit(edit_image, inputs=inputs, outputs=outputs, api_name=False)
if __name__ == "__main__":
demo.launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True)
|