import os os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") import spaces # MUST come before torch / diffusers / transformers import torch import gradio as gr import numpy as np import random from PIL import Image from diffusers import Flux2KleinPipeline # --------------------------------------------------------------------------- # Model setup # --------------------------------------------------------------------------- MODEL_ID = "black-forest-labs/FLUX.2-klein-9B" # distilled, 4-step base LORA_REPO = "Alissonerdx/CharacterSheet" LORA_WEIGHT = "QuadView_klein9b_v1.safetensors" # FLUX.2 Klein 9B QuadView LoRA TRIGGER_PROMPT = ( "Convert the character in the image to a Character Sheet showing a face " "close-up, front, side and back full body views" ) dtype = torch.bfloat16 device = "cuda" print("Loading FLUX.2 Klein 9B pipeline...") pipe = Flux2KleinPipeline.from_pretrained(MODEL_ID, torch_dtype=dtype).to("cuda") print("Loading CharacterSheet QuadView LoRA...") pipe.load_lora_weights(LORA_REPO, weight_name=LORA_WEIGHT) print("LoRA loaded. Ready.") MAX_SEED = np.iinfo(np.int32).max @spaces.GPU(duration=120) def generate( reference_image, prompt=TRIGGER_PROMPT, num_inference_steps=8, guidance_scale=1.0, lora_strength=1.0, seed=42, randomize_seed=True, width=1536, height=1024, progress=gr.Progress(track_tqdm=True), ): """Generate a multi-view character sheet from a single reference image. Uses the CharacterSheet QuadView LoRA on FLUX.2 Klein 9B to turn one character photo into a face close-up plus front, side, and back full-body views arranged on a single sheet. Args: reference_image: A clear, well-framed image of the character. prompt: Instruction caption; defaults to the QuadView trigger. num_inference_steps: Sampling steps (8 is the recommended starting point). guidance_scale: CFG scale (1.0 for distilled Klein 9B). lora_strength: LoRA scale (1.0 = full strength). seed: RNG seed for reproducibility. randomize_seed: Use a random seed instead of the provided one. width: Output width in pixels. height: Output height in pixels. """ if reference_image is None: raise gr.Error("Please upload a reference character image.") if randomize_seed: seed = random.randint(0, MAX_SEED) seed = int(seed) # Apply / adjust LoRA strength — load_lora_weights names the adapter "default_0" adapters = pipe.get_list_adapters() if adapters: adapter_names = [] for v in adapters.values(): if isinstance(v, list): adapter_names.extend(v) else: adapter_names.append(v) else: adapter_names = ["default_0"] pipe.set_adapters(adapter_names, adapter_weights=[float(lora_strength)] * len(adapter_names)) generator = torch.Generator(device=device).manual_seed(seed) img = pipe( image=reference_image, prompt=prompt, width=int(width), height=int(height), num_inference_steps=int(num_inference_steps), guidance_scale=float(guidance_scale), generator=generator, ).images[0] return img, seed # --------------------------------------------------------------------------- # UI # --------------------------------------------------------------------------- CSS = """ #col-container { max-width: 1100px; margin: 0 auto; } .dark .gradio-container { color: var(--body-text-color); } """ with gr.Blocks(css=CSS) as demo: with gr.Column(elem_id="col-container"): gr.Markdown( """# CharacterSheet LoRA — QuadView on FLUX.2 Klein 9B Turn a single character photo into a multi-view reference sheet (face close-up + front, side, and back full-body views) using the [CharacterSheet LoRA](https://huggingface.co/Alissonerdx/CharacterSheet) on [FLUX.2 Klein 9B](https://huggingface.co/black-forest-labs/FLUX.2-klein-9B).""" ) with gr.Row(): with gr.Column(): reference_image = gr.Image( label="Reference character image", type="pil", sources=["upload", "clipboard"], ) run_button = gr.Button("Generate character sheet", variant="primary") with gr.Accordion("Advanced settings", open=False): prompt = gr.Textbox( label="Instruction prompt", value=TRIGGER_PROMPT, lines=3, info="The trigger caption for the QuadView LoRA. Edit only if you know what you're doing.", ) with gr.Row(): num_inference_steps = gr.Slider( label="Steps", minimum=1, maximum=20, step=1, value=8, info="8 is the recommended starting point for Klein 9B QuadView.", ) guidance_scale = gr.Slider( label="CFG scale", minimum=0.0, maximum=10.0, step=0.1, value=1.0, ) lora_strength = gr.Slider( label="LoRA strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, ) with gr.Row(): seed = gr.Slider( label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42, ) randomize_seed = gr.Checkbox(label="Randomize seed", value=True) with gr.Row(): width = gr.Slider( label="Width", minimum=512, maximum=2048, step=16, value=1536, ) height = gr.Slider( label="Height", minimum=512, maximum=2048, step=16, value=1024, ) with gr.Column(): result = gr.Image(label="Character sheet", show_label=True) gr.Examples( examples=[ ["example_ref.jpg", TRIGGER_PROMPT, 8, 1.0, 1.0, 42, True, 1536, 1024], ["example_ref_2.jpg", TRIGGER_PROMPT, 8, 1.0, 1.0, 42, True, 1536, 1024], ["example_ref_3.jpg", TRIGGER_PROMPT, 8, 1.0, 1.0, 42, True, 1536, 1024], ], inputs=[ reference_image, prompt, num_inference_steps, guidance_scale, lora_strength, seed, randomize_seed, width, height, ], outputs=[result, seed], fn=generate, cache_examples=True, cache_mode="lazy", ) gr.on( triggers=[run_button.click], fn=generate, inputs=[ reference_image, prompt, num_inference_steps, guidance_scale, lora_strength, seed, randomize_seed, width, height, ], outputs=[result, seed], api_name="generate", ) demo.launch(mcp_server=True, theme=gr.themes.Citrus())