| import os |
|
|
| |
| os.environ.setdefault("NO_PROXY", "localhost,127.0.0.1,::1") |
| os.environ.setdefault("no_proxy", "localhost,127.0.0.1,::1") |
|
|
| import gradio as gr |
| from PIL import Image |
|
|
| from pipeline import run_pipeline |
| from pipeline.background_replace import warmup as warmup_rembg |
| from pipeline.planner import warmup |
| from pipeline.text_regions import warmup as warmup_ocr |
|
|
| _warmed_up = False |
|
|
|
|
| def _ensure_warmup(): |
| global _warmed_up |
| if _warmed_up: |
| return |
| print("Loading embedding planner, RapidOCR, and rembg...") |
| try: |
| warmup() |
| warmup_ocr() |
| warmup_rembg() |
| print("Models loaded.") |
| except Exception as exc: |
| print(f"Warmup failed (will retry on next request): {exc}") |
| return |
| _warmed_up = True |
|
|
| EXAMPLE_PROMPTS = [ |
| ["rotate randomly"], |
| ["rotate left"], |
| ["rotate right"], |
| ["flip upside down"], |
| ["rotate 180 degrees"], |
| ["gamma brighten"], |
| ["make it brighter"], |
| ["more contrast"], |
| ["vintage warm look"], |
| ["replace background"], |
| ["replace background with beach sunset"], |
| ["replace background with snowy mountains"], |
| ["replace background with anything"], |
| ["replace background with neon city at night and rotate"], |
| ["cover a piece but not text"], |
| ["just cover"], |
| ["cover and add cutout"], |
| ["cover and several cutouts avoid text"], |
| ["add cutout avoid text"], |
| ["add cutout"], |
| ["transparent cutout"], |
| ["perspective transform"], |
| ["cartoon style"], |
| ["anime style"], |
| ["starry night style"], |
| ["pencil sketch"], |
| ["stylize"], |
| ["mosaic painting"], |
| ["impressionist look"], |
| ["flip horizontally"], |
| ["blur everything softly"], |
| ] |
|
|
| SUPPORTED_EXAMPLES = "\n".join(f"- `{row[0]}`" for row in EXAMPLE_PROMPTS) |
|
|
|
|
| def _format_spatial(applied_spatial: list[dict]) -> str: |
| if not applied_spatial: |
| return "" |
| lines = [] |
| for item in applied_spatial: |
| op = item.get("op", "") |
| if op == "replace_background": |
| q = item.get("query") or "(random)" |
| src = item.get("source", "?") |
| lines.append(f"- replace_background: \"{q}\" via {src}") |
| continue |
| if op in ("cover_and_cutout", "cover_and_cutout_avoid_text"): |
| covers = item.get("cover_rects", []) |
| cutouts = item.get("cutouts", []) |
| fallback = " (fallback position)" if item.get("used_fallback") else "" |
| lines.append( |
| f"- {op}: {len(covers)} cover(s){fallback}, {len(cutouts)} cutout(s)" |
| ) |
| for idx, rect in enumerate(covers, 1): |
| lines.append(f" - cover {idx} at {rect}") |
| for idx, detail in enumerate(cutouts, 1): |
| shape = detail.get("shape", "?") |
| style = detail.get("style", "?") |
| lines.append(f" - cutout {idx} ({shape}, {style}) at {detail.get('rect')}") |
| continue |
| if op in ("add_cutout", "cutout") and item.get("cutouts"): |
| cutouts = item.get("cutouts", []) |
| lines.append(f"- {op}: {len(cutouts)} cutout(s)") |
| for idx, detail in enumerate(cutouts, 1): |
| shape = detail.get("shape", "?") |
| style = detail.get("style", "?") |
| lines.append(f" - cutout {idx} ({shape}, {style}) at {detail.get('rect')}") |
| continue |
| rect = item.get("rect", ()) |
| style = item.get("cutout_style") |
| shape = item.get("shape") |
| style_note = f", {style}" if style else "" |
| shape_note = f", {shape}" if shape else "" |
| fallback = " (fallback position)" if item.get("used_fallback") else "" |
| lines.append(f"- {op}{style_note}{shape_note} at {rect}{fallback}") |
| return "\n".join(lines) |
|
|
|
|
| def process(image, instruction, strength): |
| if image is None: |
| return None, None, "Please upload an image." |
|
|
| _ensure_warmup() |
| result = run_pipeline(image, instruction, strength=strength) |
|
|
| if not result["supported"]: |
| return ( |
| image, |
| image, |
| f"**Not supported**\n\n{result['reason']}\n\n**Try these prompts:**\n{SUPPORTED_EXAMPLES}", |
| ) |
|
|
| tags = ", ".join(result["applied_tags"]) or "(none)" |
| spatial_info = _format_spatial(result.get("applied_spatial", [])) |
| scores = result.get("planned_scores", []) |
| score_info = ", ".join(f"{name} ({score:.2f})" for name, score in scores) if scores else "(none)" |
| info = ( |
| f"**Instruction:** {result['instruction']}\n\n" |
| f"**Matched keywords:** {score_info}\n\n" |
| f"**Applied transforms:** {tags}\n\n" |
| ) |
| if spatial_info: |
| info += f"**Spatial ops:**\n{spatial_info}\n\n" |
| if result.get("text_regions_found", 0) > 0: |
| info += f"**Text regions detected:** {result['text_regions_found']}\n" |
|
|
| return image, result["image"], info |
|
|
|
|
| with gr.Blocks(title="Text-Driven Image Augmentation") as demo: |
| gr.Markdown( |
| "# Text-Driven Image Augmentation\n" |
| "Upload an image and describe what you want. Transforms are chosen by **semantic similarity** " |
| "to augmentation keywords (1–5 matches above a similarity threshold). " |
| "Background replacement uses CC-licensed photos from Openverse " |
| "(web only — describe any scene, e.g. `replace background with sunset over Paris`)." |
| ) |
|
|
| with gr.Row(): |
| input_image = gr.Image(type="pil", label="Input image") |
| instruction = gr.Textbox( |
| label="Instruction", |
| placeholder='e.g. "vintage warm look", "replace background with snowy mountains", "blur softly"', |
| lines=2, |
| ) |
|
|
| strength = gr.Slider( |
| minimum=0.5, |
| maximum=1.5, |
| value=1.0, |
| step=0.1, |
| label="Augmentation strength", |
| ) |
|
|
| gr.Examples( |
| examples=EXAMPLE_PROMPTS, |
| inputs=[instruction], |
| label="Example prompts (click to use)", |
| ) |
|
|
| run_btn = gr.Button("Augment", variant="primary") |
|
|
| with gr.Row(): |
| original_out = gr.Image(label="Original", interactive=False) |
| augmented_out = gr.Image(label="Augmented", interactive=False) |
|
|
| info_out = gr.Markdown() |
|
|
| run_btn.click( |
| fn=process, |
| inputs=[input_image, instruction, strength], |
| outputs=[original_out, augmented_out, info_out], |
| ) |
|
|
| if __name__ == "__main__": |
| demo.queue().launch( |
| server_name="0.0.0.0", |
| server_port=int(os.environ.get("PORT", 7860)), |
| share=False, |
| ) |
|
|