# app.py (SpaceX-ish styling + background video + your exact logic) import gradio as gr from PIL import Image # Pipeline (must support: run_search_and_generate(user_imgs=[...], user_prompt=...)) from pipeline import run_search_and_generate, load_from_hf # --- Quick Starter file paths (must exist in your HF dataset repo) --- QS_1_PATH = "images/LakeWater/LakeWater_000550.jpg" QS_2_PATH = "images/DenseForest/DenseForest_000000.jpg" QS_3_PATH = "images/ResidentialDense/ResidentialDense_001050.jpg" # ---------------------------- # Styling # ---------------------------- CSS = """ :root{ --bg: #070A0F; --panel: rgba(15, 19, 29, 0.72); --panel2: rgba(10, 12, 18, 0.55); --line: rgba(255,255,255,0.12); --text: rgba(255,255,255,0.94); --muted: rgba(255,255,255,0.68); --accent: #7DF9FF; --accent2: #7C5CFF; } /* IMPORTANT: make the default Gradio background transparent so our image shows */ body, .gradio-container{ background: transparent !important; } /* Background image layer */ #bg-wrap{ position: fixed; inset: 0; z-index: -1; /* put behind everything */ pointer-events: none; overflow: hidden; } #bg-image{ position: absolute; inset: 0; background-image: radial-gradient(1200px 700px at 60% 25%, rgba(125,249,255,0.10), transparent 60%), radial-gradient(900px 600px at 15% 10%, rgba(124,92,255,0.10), transparent 60%), url("https://huggingface.co/spaces/LevyJonas/SurfaceChangePredictor/resolve/main/assets/image2.webp"); background-size: cover; background-position: 65% 80%; /* pushes the Earth into view */ background-repeat: no-repeat; opacity: 0.95; filter: brightness(1.25) contrast(1.08) saturate(1.12); } /* Light vignette (not too dark) */ #bg-overlay{ position: absolute; inset: 0; background: radial-gradient(900px 700px at 50% 30%, rgba(0,0,0,0.05), rgba(0,0,0,0.35)); } /* App container above background */ #app-wrap{ position: relative; z-index: 1; max-width: 1200px; margin: 0 auto; padding: 26px 16px 40px; } /* Hero card */ .hero{ border: 1px solid var(--line); background: linear-gradient(180deg, rgba(15,19,29,0.85), rgba(10,12,18,0.55)); border-radius: 16px; padding: 22px 22px; box-shadow: 0 10px 40px rgba(0,0,0,0.45); margin-bottom: 14px; } .hero h1{ margin: 0; font-size: 28px; letter-spacing: 0.6px; } .hero p{ margin: 8px 0 0; color: var(--muted); line-height: 1.45; } .badges{ margin-top: 12px; display:flex; flex-wrap:wrap; gap:10px; } .badge{ border: 1px solid var(--line); background: rgba(0,0,0,0.25); padding: 6px 10px; border-radius: 999px; font-size: 12px; color: var(--muted); } .badge b{ color: var(--text); } /* Panels */ .panel{ border: 1px solid var(--line); background: var(--panel); border-radius: 16px; padding: 16px; box-shadow: 0 10px 40px rgba(0,0,0,0.35); } /* Inputs */ textarea, input{ background: rgba(0,0,0,0.25) !important; color: var(--text) !important; border: 1px solid var(--line) !important; border-radius: 12px !important; } /* Buttons */ button, .gr-button{ border-radius: 12px !important; border: 1px solid var(--line) !important; } #run-btn{ background: linear-gradient(90deg, rgba(125,249,255,0.95), rgba(124,92,255,0.95)) !important; color: #05060A !important; font-weight: 800 !important; border: none !important; } #qs-row button{ background: rgba(255,255,255,0.06) !important; color: var(--text) !important; } #qs-row button:hover{ border-color: rgba(125,249,255,0.45) !important; } /* Galleries */ .gr-gallery{ border-radius: 16px !important; border: 1px solid var(--line) !important; background: var(--panel2) !important; } """ # ---------------------------- # Core helpers (unchanged logic) # ---------------------------- def _files_to_pil_list(files, n_use: int): """Convert uploaded files to a list of PIL images (use first n_use, capped to 1..4).""" if not files: return [] n = max(1, min(4, int(n_use), len(files))) imgs = [] for f in files[:n]: path = f.name if hasattr(f, "name") else str(f) imgs.append(Image.open(path).convert("RGB")) return imgs def run_app(files, n_user_imgs, prompt, k_retrieve, n_i2i, n_t2i, strength_i2i, steps, gen_size, seed): try: if not files: return [], [], [], "Error: Please upload at least 1 image." max_allowed = int(n_user_imgs) # slider value (1–4) if len(files) > max_allowed: return [], [], [], f"Error: You uploaded {len(files)} images, but the limit is {max_allowed}. Please remove extra files." user_imgs = _files_to_pil_list(files, max_allowed) retrieved, gen_i2i, gen_t2i, info = run_search_and_generate( user_imgs=user_imgs, user_prompt=prompt, k_retrieve=k_retrieve, n_i2i=n_i2i, n_t2i=n_t2i, strength_i2i=strength_i2i, steps=steps, gen_size=gen_size, seed=int(seed), ) retr_gallery = [(r["img"], f"{r['label']} | cos={r['sim']:.3f}") for r in retrieved] i2i_gallery = [(im, f"img2img #{i+1}") for i, im in enumerate(gen_i2i)] t2i_gallery = [(im, f"txt2img #{i+1}") for i, im in enumerate(gen_t2i)] summary = "\n".join([f"{k}: {v}" for k, v in info.items()]) return retr_gallery, i2i_gallery, t2i_gallery, summary except Exception as e: return [], [], [], f"Error: {e}" def run_quickstarter(rel_path, prompt, k_retrieve, n_i2i, n_t2i, strength_i2i, steps, gen_size, seed): try: img = load_from_hf(rel_path) retrieved, gen_i2i, gen_t2i, info = run_search_and_generate( user_imgs=[img], user_prompt=prompt, k_retrieve=k_retrieve, n_i2i=n_i2i, n_t2i=n_t2i, strength_i2i=strength_i2i, steps=steps, gen_size=gen_size, seed=int(seed), ) retr_gallery = [(r["img"], f"{r['label']} | cos={r['sim']:.3f}") for r in retrieved] i2i_gallery = [(im, f"img2img #{i+1}") for i, im in enumerate(gen_i2i)] t2i_gallery = [(im, f"txt2img #{i+1}") for i, im in enumerate(gen_t2i)] summary = "\n".join([f"{k}: {v}" for k, v in info.items()]) return retr_gallery, i2i_gallery, t2i_gallery, summary except Exception as e: return [], [], [], f"Error: {e}" # ---------------------------- # UI (SpaceX-ish) # ---------------------------- with gr.Blocks(title="Satellite Patch: Retrieve + Generate", css=CSS) as demo: # OPTIONAL background image: # If you don't have it, comment this HTML block out. gr.HTML("""
""") # Hero section gr.HTML("""

Satellite Patch — Retrieve + Generate

Upload up to 4 context patches + write a prompt. The system returns Top-K similar patches from the dataset and new generated variants (img2img + txt2img). Built for “missing patch” exploration.

Embeddings: DINOv2-Small
Generator: SD-Turbo
Dataset: HF Hub
Limits: 0–5 outputs
""") # Quick Starters + main layout with gr.Row(elem_id="app-wrap"): # Left panel (controls) with gr.Column(scale=1, elem_classes=["panel"]): gr.Markdown("### Quick Starters (1-click examples)") with gr.Row(elem_id="qs-row"): qs1 = gr.Button("LakeWater") qs2 = gr.Button("DenseForest") qs3 = gr.Button("ResidentialDense") gr.Markdown("### Upload + Prompt") files = gr.Files( label="Upload up to 4 satellite patch images", file_types=["image"], file_count="multiple" ) n_user_imgs = gr.Slider(1, 4, value=1, step=1, label="How many uploaded images to use (1–4)") prompt = gr.Textbox( label="Prompt (required for generation)", value="Satellite-like RGB patch, realistic remote sensing, top-down view", lines=2 ) gr.Markdown("### Output Controls") k_retrieve = gr.Slider(0, 5, value=2, step=1, label="# Retrieved images (0–5)") n_i2i = gr.Slider(0, 5, value=2, step=1, label="# img2img generated (0–5)") n_t2i = gr.Slider(0, 5, value=2, step=1, label="# txt2img generated (0–5)") strength_i2i = gr.Slider(0.25, 0.80, value=0.35, step=0.01, label="img2img strength (lower = closer)") steps = gr.Slider(1, 2, value=1, step=1, label="steps (1–2)") gen_size = gr.Radio([384, 512], value=512, label="generation size") seed = gr.Number(value=42, precision=0, label="seed") btn = gr.Button("Run", elem_id="run-btn") # Right panel (outputs) with gr.Column(scale=2, elem_classes=["panel"]): gr.Markdown("### Results") out_retr = gr.Gallery(label="Retrieved from Dataset", columns=5, height=260) out_i2i = gr.Gallery(label="Generated (img2img)", columns=5, height=260) out_t2i = gr.Gallery(label="Generated (txt2img)", columns=5, height=260) out_txt = gr.Textbox(label="Summary", lines=8) # Normal run btn.click( run_app, inputs=[files, n_user_imgs, prompt, k_retrieve, n_i2i, n_t2i, strength_i2i, steps, gen_size, seed], outputs=[out_retr, out_i2i, out_t2i, out_txt], ) # Quick Starter runs (1 click) qs1.click( run_quickstarter, inputs=[gr.State(QS_1_PATH), prompt, k_retrieve, n_i2i, n_t2i, strength_i2i, steps, gen_size, seed], outputs=[out_retr, out_i2i, out_t2i, out_txt], ) qs2.click( run_quickstarter, inputs=[gr.State(QS_2_PATH), prompt, k_retrieve, n_i2i, n_t2i, strength_i2i, steps, gen_size, seed], outputs=[out_retr, out_i2i, out_t2i, out_txt], ) qs3.click( run_quickstarter, inputs=[gr.State(QS_3_PATH), prompt, k_retrieve, n_i2i, n_t2i, strength_i2i, steps, gen_size, seed], outputs=[out_retr, out_i2i, out_t2i, out_txt], ) demo.launch()