gvecchio commited on
Commit
bf40bd4
Β·
verified Β·
1 Parent(s): c847b92
Files changed (3) hide show
  1. README.md +27 -14
  2. app.py +256 -0
  3. requirements.txt +11 -0
README.md CHANGED
@@ -1,14 +1,27 @@
1
- ---
2
- title: MatFuse
3
- emoji: 😻
4
- colorFrom: yellow
5
- colorTo: pink
6
- sdk: gradio
7
- sdk_version: 6.6.0
8
- python_version: '3.12'
9
- app_file: app.py
10
- pinned: false
11
- license: mit
12
- ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: MatFuse
3
+ emoji: 🧱
4
+ colorFrom: yellow
5
+ colorTo: pink
6
+ sdk: gradio
7
+ sdk_version: 6.6.0
8
+ python_version: '3.12'
9
+ app_file: app.py
10
+ pinned: true
11
+ license: mit
12
+ tags:
13
+ - pbr
14
+ - material
15
+ - texture
16
+ - diffusion
17
+ - generation
18
+ ---
19
+
20
+ # MatFuse β€” PBR Material Generator
21
+
22
+ Generate seamless PBR material maps (diffuse, normal, roughness, specular) from
23
+ text descriptions, reference images, sketches, and color palettes.
24
+
25
+ **Paper:** [MatFuse: Controllable Multi-Modal Image Generation via Diffusion Models](https://arxiv.org/abs/2308.11408)
26
+
27
+ **Model:** [gvecchio/MatFuse](https://huggingface.co/gvecchio/MatFuse)
app.py ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MatFuse β€” PBR Material Generation Demo
3
+
4
+ Gradio app for generating physically-based rendering (PBR) material maps
5
+ using the MatFuse diffusion model. Supports text, image, sketch, and
6
+ color-palette conditioning.
7
+
8
+ Designed for Hugging Face Spaces with ZeroGPU support.
9
+ """
10
+
11
+ import os
12
+ import random
13
+ from typing import Optional
14
+
15
+ import gradio as gr
16
+ import numpy as np
17
+ import spaces
18
+ import torch
19
+ from diffusers import DiffusionPipeline
20
+ from PIL import Image
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Model loading
24
+ # ---------------------------------------------------------------------------
25
+
26
+ REPO_ID = os.environ.get("MATFUSE_REPO", "gvecchio/MatFuse")
27
+
28
+ pipe = DiffusionPipeline.from_pretrained(
29
+ REPO_ID,
30
+ trust_remote_code=True,
31
+ torch_dtype=torch.float16,
32
+ )
33
+
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Palette extraction (lightweight K-Means, no heavy deps)
37
+ # ---------------------------------------------------------------------------
38
+
39
+ def extract_palette(image: Image.Image, n_colors: int = 5) -> list[list[int]]:
40
+ """Extract dominant colors from an image using simple K-Means."""
41
+ img = image.convert("RGB").resize((64, 64))
42
+ pixels = np.array(img).reshape(-1, 3).astype(np.float32)
43
+
44
+ # Mini K-Means
45
+ rng = np.random.default_rng(0)
46
+ centroids = pixels[rng.choice(len(pixels), n_colors, replace=False)]
47
+ for _ in range(20):
48
+ dists = np.linalg.norm(pixels[:, None] - centroids[None], axis=2)
49
+ labels = dists.argmin(axis=1)
50
+ for k in range(n_colors):
51
+ mask = labels == k
52
+ if mask.any():
53
+ centroids[k] = pixels[mask].mean(axis=0)
54
+ return centroids.clip(0, 255).astype(np.uint8).tolist()
55
+
56
+
57
+ def palette_to_image(colors: list[list[int]], height: int = 50) -> Image.Image:
58
+ """Render a palette swatch strip."""
59
+ n = len(colors)
60
+ w_each = 60
61
+ img = Image.new("RGB", (w_each * n, height))
62
+ for i, c in enumerate(colors):
63
+ for x in range(w_each):
64
+ for y in range(height):
65
+ img.putpixel((i * w_each + x, y), tuple(c))
66
+ return img
67
+
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # Generation
71
+ # ---------------------------------------------------------------------------
72
+
73
+ @spaces.GPU
74
+ @torch.inference_mode()
75
+ def generate(
76
+ prompt: Optional[str],
77
+ image: Optional[Image.Image],
78
+ palette_image: Optional[Image.Image],
79
+ sketch: Optional[Image.Image],
80
+ guidance_scale: float,
81
+ num_steps: int,
82
+ seed: int,
83
+ randomize_seed: bool,
84
+ ):
85
+ """Run the MatFuse pipeline and return the four PBR maps + palette preview."""
86
+
87
+ if randomize_seed:
88
+ seed = random.randint(0, 2**31 - 1)
89
+
90
+ # Move to GPU (ZeroGPU allocates on call)
91
+ pipe.to("cuda")
92
+
93
+ # --- Build kwargs -------------------------------------------------------
94
+ kwargs: dict = dict(
95
+ num_inference_steps=num_steps,
96
+ guidance_scale=guidance_scale,
97
+ generator=torch.Generator("cuda").manual_seed(seed),
98
+ )
99
+
100
+ # Text
101
+ if prompt and prompt.strip():
102
+ kwargs["text"] = prompt.strip()
103
+
104
+ # Reference image
105
+ if image is not None:
106
+ kwargs["image"] = image
107
+
108
+ # Sketch
109
+ if sketch is not None:
110
+ kwargs["sketch"] = sketch
111
+
112
+ # Palette (extracted from an uploaded image)
113
+ palette_preview = None
114
+ if palette_image is not None:
115
+ colors = extract_palette(palette_image, n_colors=5)
116
+ palette_arr = np.array(colors, dtype=np.float32) / 255.0
117
+ kwargs["palette"] = palette_arr
118
+ palette_preview = palette_to_image(colors)
119
+
120
+ result = pipe(**kwargs)
121
+
122
+ diffuse_img = result["diffuse"][0]
123
+ normal_img = result["normal"][0]
124
+ roughness_img = result["roughness"][0]
125
+ specular_img = result["specular"][0]
126
+
127
+ return diffuse_img, normal_img, roughness_img, specular_img, palette_preview, seed
128
+
129
+
130
+ # ---------------------------------------------------------------------------
131
+ # Example data
132
+ # ---------------------------------------------------------------------------
133
+
134
+ EXAMPLE_PROMPTS = [
135
+ "Red brick wall with white mortar",
136
+ "Polished oak wood floor",
137
+ "Rough concrete with cracks",
138
+ "Mossy cobblestone path",
139
+ "Shiny marble tiles",
140
+ "Rusted metal panel",
141
+ ]
142
+
143
+
144
+ # ---------------------------------------------------------------------------
145
+ # UI
146
+ # ---------------------------------------------------------------------------
147
+
148
+ css = """
149
+ #matfuse-title { text-align: center; margin-bottom: 0.5em; }
150
+ #matfuse-subtitle { text-align: center; color: #666; margin-top: 0; }
151
+ .output-map img { border-radius: 8px; }
152
+ footer { display: none !important; }
153
+ """
154
+
155
+ with gr.Blocks(title="MatFuse β€” PBR Material Generator") as demo:
156
+
157
+ # Header
158
+ gr.Markdown("# MatFuse", elem_id="matfuse-title")
159
+ gr.Markdown(
160
+ "Generate seamless PBR material maps (diffuse, normal, roughness, specular) "
161
+ "from text, images, sketches, and color palettes. "
162
+ "[Paper](https://arxiv.org/abs/2308.11408)  |  "
163
+ "[Code](https://github.com/gvecchio/matfuse-sd)",
164
+ elem_id="matfuse-subtitle",
165
+ )
166
+
167
+ with gr.Row():
168
+ # ── Left column: inputs ──────────────────────────────────────
169
+ with gr.Column(scale=1):
170
+ prompt = gr.Textbox(
171
+ label="Text prompt",
172
+ placeholder="e.g. 'Old wooden floor with scratches'",
173
+ lines=2,
174
+ )
175
+
176
+ with gr.Accordion("Image conditioning", open=False):
177
+ image_input = gr.Image(
178
+ label="Reference image",
179
+ type="pil",
180
+ sources=["upload", "clipboard"],
181
+ )
182
+
183
+ with gr.Accordion("Palette conditioning", open=False):
184
+ palette_image = gr.Image(
185
+ label="Upload image to extract palette",
186
+ type="pil",
187
+ sources=["upload", "clipboard"],
188
+ )
189
+
190
+ with gr.Accordion("Sketch conditioning", open=False):
191
+ sketch_input = gr.Image(
192
+ label="Binary sketch / edge map",
193
+ type="pil",
194
+ image_mode="L",
195
+ sources=["upload", "clipboard"],
196
+ )
197
+
198
+ with gr.Accordion("Generation settings", open=False):
199
+ guidance_scale = gr.Slider(
200
+ label="Guidance scale",
201
+ minimum=1.0,
202
+ maximum=15.0,
203
+ value=4.0,
204
+ step=0.5,
205
+ )
206
+ num_steps = gr.Slider(
207
+ label="Inference steps",
208
+ minimum=10,
209
+ maximum=100,
210
+ value=50,
211
+ step=5,
212
+ )
213
+ with gr.Row():
214
+ seed = gr.Number(label="Seed", value=42, precision=0)
215
+ randomize_seed = gr.Checkbox(label="Randomize", value=True)
216
+
217
+ generate_btn = gr.Button("Generate", variant="primary", size="lg")
218
+
219
+ gr.Examples(
220
+ examples=[[p] for p in EXAMPLE_PROMPTS],
221
+ inputs=[prompt],
222
+ label="Example prompts",
223
+ )
224
+
225
+ # ── Right column: outputs ────────────────────────────────────
226
+ with gr.Column(scale=1):
227
+ with gr.Row():
228
+ diffuse_out = gr.Image(label="Diffuse", elem_classes="output-map", interactive=False)
229
+ normal_out = gr.Image(label="Normal", elem_classes="output-map", interactive=False)
230
+ with gr.Row():
231
+ roughness_out = gr.Image(label="Roughness", elem_classes="output-map", interactive=False)
232
+ specular_out = gr.Image(label="Specular", elem_classes="output-map", interactive=False)
233
+ palette_out = gr.Image(label="Extracted palette", visible=True, height=60, interactive=False)
234
+ seed_out = gr.Number(label="Seed used", interactive=False)
235
+
236
+ # ── Wiring ──────────────────────────────────────────────────────
237
+ generate_btn.click(
238
+ fn=generate,
239
+ inputs=[
240
+ prompt,
241
+ image_input,
242
+ palette_image,
243
+ sketch_input,
244
+ guidance_scale,
245
+ num_steps,
246
+ seed,
247
+ randomize_seed,
248
+ ],
249
+ outputs=[diffuse_out, normal_out, roughness_out, specular_out, palette_out, seed_out],
250
+ )
251
+
252
+ if __name__ == "__main__":
253
+ demo.launch(css=css, theme=gr.themes.Soft())
254
+
255
+
256
+ # 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
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch
2
+ diffusers>=0.30.0
3
+ transformers
4
+ safetensors
5
+ accelerate
6
+ spaces
7
+ gradio>=5.0.0
8
+ Pillow
9
+ numpy
10
+ sentence-transformers
11
+ open_clip_torch