File size: 6,068 Bytes
c46a5da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62122bd
 
c46a5da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62122bd
 
 
 
c46a5da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60b63d0
 
 
c46a5da
60b63d0
 
 
c46a5da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import spaces
import torch
import gradio as gr
import random
from PIL import Image
from diffusers import Flux2KleinPipeline

# ── Configuration ─────────────────────────────────────────────────────────────
BASE_MODEL = "black-forest-labs/FLUX.2-klein-9B"
LORA_REPO = "paom/texture2albedo-v2"
WEIGHT_NAME = "pytorch_lora_weights.safetensors"

DEFAULT_PROMPT = (
    "Unlit flat-shaded albedo map. Remove all shadows, reflections, highlights, "
    "and specularity. Maintain absolute pixel-per-pixel structural identity, shape, "
    "and spatial alignment with the original image, displaying only raw base color."
)

MAX_SEED = 2**31 - 1

# ── Model load at module scope ───────────────────────────────────────────────
print("Loading FLUX.2-klein-9B base pipeline...")
pipe = Flux2KleinPipeline.from_pretrained(
    BASE_MODEL,
    torch_dtype=torch.bfloat16,
)
pipe.to("cuda")
print("Loading LoRA weights...")
pipe.load_lora_weights(
    LORA_REPO,
    weight_name=WEIGHT_NAME,
    adapter_name="albedo",
)
# Model card loads the LoRA adapter and runs it unfused (default adapter
# weight = 1.0); do NOT fuse, to match the documented inference recipe.
print("Pipeline ready.")


# ── Inference ────────────────────────────────────────────────────────────────
@spaces.GPU(duration=90)
def generate_albedo(
    input_image,
    prompt,
    num_inference_steps,
    guidance_scale,
    seed,
    randomize_seed,
    progress=gr.Progress(track_tqdm=True),
):
    if input_image is None:
        raise gr.Error("Please upload a texture or photo first.")

    if not prompt or not prompt.strip():
        prompt = DEFAULT_PROMPT

    orig_width, orig_height = input_image.size

    # Resize to 1024x1024 for the model
    processed_input = input_image.resize((1024, 1024))

    if randomize_seed:
        seed = random.randint(0, MAX_SEED)
    # Model card seeds with torch.manual_seed(seed), i.e. a CPU generator.
    # A CUDA generator produces a different noise sequence for the same seed,
    # so match the documented recipe to keep outputs consistent with the card.
    generator = torch.manual_seed(seed)

    with torch.inference_mode():
        output_image = pipe(
            prompt=prompt,
            image=processed_input,
            guidance_scale=guidance_scale,
            num_inference_steps=int(num_inference_steps),
            generator=generator,
        ).images[0]

    # Resize back to original dimensions
    albedo_map = output_image.resize((orig_width, orig_height))

    return albedo_map, seed


# ── UI ───────────────────────────────────────────────────────────────────────
with gr.Blocks(title="Texture to Albedo β€” FLUX.2 Klein") as demo:
    gr.Markdown(
        """
        # Texture β†’ Albedo Studio
        Extract clean, flat, shadowless **albedo maps** from textures and photos using
        [paom/texture2albedo-v2](https://huggingface.co/paom/texture2albedo-v2) on
        [FLUX.2-klein-9B](https://huggingface.co/black-forest-labs/FLUX.2-klein-9B).
        Perfect for 3D/PBR material pipelines.
        """
    )

    with gr.Row(equal_height=True):
        with gr.Column(scale=1):
            input_img = gr.Image(label="Input Texture / Photo", type="pil")
            prompt_box = gr.Textbox(
                label="Prompt",
                value=DEFAULT_PROMPT,
                lines=3,
                placeholder="Describe what you want the albedo map to look like...",
            )

            with gr.Accordion("Advanced Parameters", open=False):
                inference_steps = gr.Slider(
                    minimum=1, maximum=12, value=4, step=1,
                    label="Inference Steps",
                )
                guidance = gr.Slider(
                    minimum=0.0, maximum=4.0, value=1.0, step=0.1,
                    label="Guidance Scale",
                )
                seed_input = gr.Slider(
                    minimum=0, maximum=MAX_SEED, value=0, step=1,
                    label="Seed",
                )
                randomize_seed = gr.Checkbox(
                    label="Randomize seed", value=True,
                )

            submit_btn = gr.Button("Generate Albedo Map", variant="primary", size="lg")

        with gr.Column(scale=1):
            albedo_out = gr.Image(label="Clean Albedo Map", type="pil")
            used_seed = gr.Number(label="Seed used", precision=0, interactive=False)

    gr.Examples(
        # The model-card example images are before/after composites
        # (left half = original texture, right half = albedo output).
        # Only the left "before" half is fed to the model as the example input.
        examples=[
            ["example_1_left.jpg", DEFAULT_PROMPT, 4, 1.0, 0, True],
            ["example_2_left.jpg", DEFAULT_PROMPT, 4, 1.0, 0, True],
            ["example_3_left.jpg", DEFAULT_PROMPT, 4, 1.0, 0, True],
        ],
        inputs=[input_img, prompt_box, inference_steps, guidance, seed_input, randomize_seed],
        outputs=[albedo_out, used_seed],
        fn=generate_albedo,
        cache_examples=True,
        cache_mode="lazy",
    )

    submit_btn.click(
        fn=generate_albedo,
        inputs=[input_img, prompt_box, inference_steps, guidance, seed_input, randomize_seed],
        outputs=[albedo_out, used_seed],
    )

    prompt_box.submit(
        fn=generate_albedo,
        inputs=[input_img, prompt_box, inference_steps, guidance, seed_input, randomize_seed],
        outputs=[albedo_out, used_seed],
    )


if __name__ == "__main__":
    demo.launch(theme=gr.themes.Citrus(), show_error=True)