Spaces:
Running on Zero
Running on Zero
File size: 3,561 Bytes
6e067d6 5ac6a4a 6e067d6 7c62c84 6e067d6 7c62c84 6e067d6 7c62c84 6e067d6 7c62c84 6e067d6 00fb958 91b3539 2f815ed 00fb958 6e067d6 00fb958 6e067d6 00fb958 6e067d6 91b3539 6e067d6 00fb958 6e067d6 | 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 | import os
import gc
import gradio as gr
from gradio import Server
from fastapi.responses import HTMLResponse
import numpy as np
import spaces
import torch
import random
import base64
from io import BytesIO
from PIL import Image
from typing import Tuple
from diffusers import Flux2KleinPipeline
MAX_SEED = np.iinfo(np.int32).max
LANCZOS = getattr(Image, "Resampling", Image).LANCZOS
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
dtype = torch.bfloat16
print("Using device:", device)
print("Loading FLUX.2 Klein 9B model base...")
pipe = Flux2KleinPipeline.from_pretrained(
"black-forest-labs/FLUX.2-klein-9B",
torch_dtype=dtype,
).to(device)
print("Base Model loaded successfully.")
def pil_to_b64_png(image: Image.Image) -> str:
buf = BytesIO()
image.save(buf, format="PNG")
return f"data:image/png;base64,{base64.b64encode(buf.getvalue()).decode()}"
# ββ Gradio Server (Server mode): FastAPI + Gradio queue/API engine ββββββββββββ
app = Server(title="Scribble-Board-Fast")
@app.mcp.tool(name="generate_image")
@app.api(name="generate_image")
@spaces.GPU(size="xlarge")
def infer(
image_b64: str,
prompt: str,
seed: int,
randomize_seed: bool,
width: int,
height: int,
steps: int,
guidance_scale: float,
) -> dict:
"""Generates an image from a scribble using FLUX.2 Klein 9B.
NOTE: `image_b64` is the fully FLATTENED board sent by the frontend β
the free-hand sketch, every sticker/asset, every text object AND every
line/arrow/shape are already composited into this single PNG, so all of
them are reflected in the conditioning image passed to the pipeline.
"""
gc.collect()
torch.cuda.empty_cache()
if not prompt or prompt.strip() == "":
raise gr.Error("Please enter a prompt.")
if not image_b64:
raise gr.Error("Please draw a sketch first.")
try:
header, data = image_b64.split(",", 1)
pil_image = Image.open(BytesIO(base64.b64decode(data))).convert("RGB")
except Exception as e:
raise gr.Error(f"Invalid image data: {e}")
# Ensure dimensions are multiples of 8
final_width = max(256, min(1024, round(int(width) / 8) * 8))
final_height = max(256, min(1024, round(int(height) / 8) * 8))
# Resize the flattened board (sketch + assets + text + shapes) to the target dimensions
pil_image = pil_image.resize((final_width, final_height), LANCZOS).convert("RGB")
if randomize_seed:
seed = random.randint(0, MAX_SEED)
generator = torch.Generator(device="cpu").manual_seed(seed)
kwargs = dict(
prompt=prompt,
height=final_height,
width=final_width,
num_inference_steps=int(steps),
guidance_scale=float(guidance_scale),
generator=generator,
image=pil_image
)
try:
result_image = pipe(**kwargs).images[0]
return {"image": pil_to_b64_png(result_image), "seed": seed}
except Exception as e:
raise e
finally:
gc.collect()
torch.cuda.empty_cache()
@app.get("/api/config")
def client_config():
"""Plain FastAPI route: config data for the frontend."""
return {"app_name": "Scribble-Board-Fast"}
@app.get("/", response_class=HTMLResponse)
async def homepage():
html_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "index.html")
with open(html_path, "r", encoding="utf-8") as f:
return f.read()
if __name__ == "__main__":
app.launch(show_error=True, mcp_server=True) |