stevee00 commited on
Commit
6df8db0
·
verified ·
1 Parent(s): dcb20f6

Upload src/interiorfusion/models/material_texture.py

Browse files
src/interiorfusion/models/material_texture.py ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Phase 5: Material & Texture Module.
2
+
3
+ Generates:
4
+ - PBR materials (albedo, metallic, roughness, normal)
5
+ - Texture baking from multi-view images
6
+ - Lighting estimation for relightable scenes
7
+ """
8
+
9
+ import os
10
+ from typing import Dict, List, Optional, Tuple, Union
11
+
12
+ import numpy as np
13
+ import torch
14
+ import torch.nn as nn
15
+ import torch.nn.functional as F
16
+ from PIL import Image
17
+
18
+
19
+ class MaterialTextureModule(nn.Module):
20
+ """Generate PBR materials and bake textures onto meshes."""
21
+
22
+ def __init__(
23
+ self,
24
+ model_size: str = "L",
25
+ device: str = "cuda",
26
+ dtype: torch.dtype = torch.float16,
27
+ use_pbr: bool = True,
28
+ cache_dir: Optional[str] = None,
29
+ ):
30
+ super().__init__()
31
+ self.model_size = model_size
32
+ self.device = device
33
+ self.dtype = dtype
34
+ self.use_pbr = use_pbr
35
+ self.cache_dir = cache_dir
36
+
37
+ # Material generation model (placeholder for now)
38
+ self._material_model = None
39
+
40
+ # Material type priors
41
+ self.material_priors = {
42
+ "wall": {"albedo": [0.9, 0.9, 0.9], "metallic": 0.0, "roughness": 0.8},
43
+ "floor_wood": {"albedo": [0.6, 0.4, 0.2], "metallic": 0.0, "roughness": 0.6},
44
+ "floor_tile": {"albedo": [0.8, 0.8, 0.8], "metallic": 0.1, "roughness": 0.3},
45
+ "floor_carpet": {"albedo": [0.5, 0.3, 0.2], "metallic": 0.0, "roughness": 0.9},
46
+ "ceiling": {"albedo": [0.95, 0.95, 0.95], "metallic": 0.0, "roughness": 0.9},
47
+ "furniture_wood": {"albedo": [0.5, 0.3, 0.15], "metallic": 0.0, "roughness": 0.5},
48
+ "furniture_fabric": {"albedo": [0.6, 0.5, 0.4], "metallic": 0.0, "roughness": 0.8},
49
+ "furniture_leather": {"albedo": [0.4, 0.2, 0.1], "metallic": 0.1, "roughness": 0.4},
50
+ "furniture_metal": {"albedo": [0.7, 0.7, 0.7], "metallic": 0.9, "roughness": 0.2},
51
+ "furniture_plastic": {"albedo": [0.8, 0.8, 0.8], "metallic": 0.0, "roughness": 0.3},
52
+ "furniture_glass": {"albedo": [0.9, 0.9, 0.9], "metallic": 0.0, "roughness": 0.05},
53
+ "default": {"albedo": [0.7, 0.7, 0.7], "metallic": 0.0, "roughness": 0.5},
54
+ }
55
+
56
+ def generate_room_materials(
57
+ self,
58
+ room_shell_mesh: "trimesh.Trimesh", # type: ignore
59
+ image: Image.Image,
60
+ semantic_seg: np.ndarray,
61
+ ) -> "trimesh.Trimesh": # type: ignore
62
+ """
63
+ Generate materials for room shell (walls, floor, ceiling).
64
+
65
+ Uses semantic segmentation to determine material types
66
+ and input image for color extraction.
67
+ """
68
+ if not self.use_pbr:
69
+ return room_shell_mesh
70
+
71
+ # Extract dominant colors from image regions
72
+ img_np = np.array(image)
73
+
74
+ # Determine material types from semantic segmentation
75
+ floor_region = semantic_seg == 1
76
+ ceiling_region = semantic_seg == 2
77
+ wall_regions = (semantic_seg == 3) | (semantic_seg == 4)
78
+
79
+ # Extract colors from corresponding image regions
80
+ floor_color = self._extract_dominant_color(img_np, floor_region)
81
+ ceiling_color = self._extract_dominant_color(img_np, ceiling_region)
82
+ wall_color = self._extract_dominant_color(img_np, wall_regions)
83
+
84
+ # Create materials
85
+ floor_mat = self._create_material("floor_wood", color=floor_color)
86
+ ceiling_mat = self._create_material("ceiling", color=ceiling_color)
87
+ wall_mat = self._create_material("wall", color=wall_color)
88
+
89
+ # Apply materials to mesh faces
90
+ # In practice, this would be done per-face based on which room part the face belongs to
91
+ # For now, store materials as mesh metadata
92
+ room_shell_mesh.materials = {
93
+ "floor": floor_mat,
94
+ "ceiling": ceiling_mat,
95
+ "walls": wall_mat,
96
+ }
97
+
98
+ return room_shell_mesh
99
+
100
+ def generate_object_materials(
101
+ self,
102
+ object_mesh: "trimesh.Trimesh", # type: ignore
103
+ multiviews: List[Image.Image],
104
+ object_info: Dict,
105
+ ) -> Tuple["trimesh.Trimesh", List[Dict]]: # type: ignore
106
+ """
107
+ Generate PBR materials for a furniture object.
108
+
109
+ Uses multi-view images to bake texture and infer material properties.
110
+ """
111
+ if not self.use_pbr:
112
+ return object_mesh, []
113
+
114
+ class_name = object_info.get("class_name", "furniture")
115
+
116
+ # Infer material type from class and image analysis
117
+ material_type = self._infer_material_type(class_name, multiviews[0])
118
+
119
+ # Extract dominant color from multi-view images
120
+ colors = [self._extract_dominant_color(np.array(mv), np.ones((mv.size[1], mv.size[0]), dtype=bool))
121
+ for mv in multiviews]
122
+ avg_color = np.mean(colors, axis=0)
123
+
124
+ # Create material
125
+ material = self._create_material(material_type, color=avg_color)
126
+
127
+ # Create simple UV atlas texture
128
+ texture = self._bake_texture(object_mesh, multiviews, material)
129
+
130
+ # Attach texture to mesh
131
+ if texture is not None:
132
+ object_mesh.visual = object_mesh.visual.to_texture()
133
+ # In production, set actual texture image
134
+ object_mesh.material_override = material
135
+
136
+ materials = [material]
137
+
138
+ return object_mesh, materials
139
+
140
+ def estimate_lighting(
141
+ self,
142
+ image: Image.Image,
143
+ ) -> Dict:
144
+ """
145
+ Estimate scene lighting from input image.
146
+
147
+ Returns:
148
+ {
149
+ "environment_map": HDR environment map (placeholder),
150
+ "key_light_direction": [x, y, z],
151
+ "key_light_intensity": float,
152
+ "fill_light_intensity": float,
153
+ "ambient_intensity": float,
154
+ "color_temperature": float, # Kelvin
155
+ }
156
+ """
157
+ img_np = np.array(image)
158
+
159
+ # Simple heuristic lighting estimation
160
+ # In production, use trained lighting estimation network
161
+
162
+ # Estimate brightness
163
+ brightness = img_np.mean()
164
+
165
+ # Estimate color temperature from average color
166
+ avg_color = img_np.mean(axis=(0, 1))
167
+
168
+ # Warm = more red, Cool = more blue
169
+ color_temp = 6500 # Default daylight
170
+ if avg_color[2] > avg_color[0] * 1.2:
171
+ color_temp = 8000 # Cool
172
+ elif avg_color[0] > avg_color[2] * 1.2:
173
+ color_temp = 3000 # Warm
174
+
175
+ # Estimate light direction from shadows
176
+ # Placeholder: assume light from top-left
177
+ light_dir = np.array([0.3, 0.8, 0.2])
178
+ light_dir = light_dir / np.linalg.norm(light_dir)
179
+
180
+ return {
181
+ "environment_map": None, # Would generate HDR probe
182
+ "key_light_direction": light_dir.tolist(),
183
+ "key_light_intensity": float(brightness / 255.0 * 2.0),
184
+ "fill_light_intensity": float(brightness / 255.0 * 0.5),
185
+ "ambient_intensity": float(brightness / 255.0 * 0.3),
186
+ "color_temperature": float(color_temp),
187
+ }
188
+
189
+ def _extract_dominant_color(
190
+ self,
191
+ image: np.ndarray,
192
+ mask: np.ndarray,
193
+ ) -> np.ndarray:
194
+ """Extract dominant color from image region."""
195
+ if mask.sum() == 0:
196
+ return np.array([0.7, 0.7, 0.7])
197
+
198
+ masked_pixels = image[mask]
199
+
200
+ # K-means-ish: use median for robustness
201
+ dominant_color = np.median(masked_pixels, axis=0) / 255.0
202
+
203
+ return dominant_color
204
+
205
+ def _create_material(
206
+ self,
207
+ material_type: str,
208
+ color: Optional[np.ndarray] = None,
209
+ ) -> Dict:
210
+ """Create PBR material from type and color."""
211
+ prior = self.material_priors.get(material_type, self.material_priors["default"])
212
+
213
+ if color is not None:
214
+ albedo = color.tolist()
215
+ else:
216
+ albedo = prior["albedo"]
217
+
218
+ return {
219
+ "type": material_type,
220
+ "albedo": albedo,
221
+ "metallic": prior["metallic"],
222
+ "roughness": prior["roughness"],
223
+ "normal_scale": 1.0,
224
+ "ao_scale": 1.0,
225
+ # Texture maps (would be actual textures in production)
226
+ "albedo_map": None,
227
+ "metallic_map": None,
228
+ "roughness_map": None,
229
+ "normal_map": None,
230
+ "ao_map": None,
231
+ }
232
+
233
+ def _infer_material_type(
234
+ self,
235
+ class_name: str,
236
+ image: Image.Image,
237
+ ) -> str:
238
+ """Infer material type from object class and visual appearance."""
239
+ class_lower = class_name.lower()
240
+
241
+ # Map class to material type
242
+ material_map = {
243
+ "sofa": "furniture_fabric",
244
+ "chair": "furniture_fabric",
245
+ "table": "furniture_wood",
246
+ "coffee_table": "furniture_wood",
247
+ "bed": "furniture_fabric",
248
+ "desk": "furniture_wood",
249
+ "bookshelf": "furniture_wood",
250
+ "lamp": "furniture_metal",
251
+ "wardrobe": "furniture_wood",
252
+ "tv_stand": "furniture_wood",
253
+ "rug": "floor_carpet",
254
+ }
255
+
256
+ return material_map.get(class_lower, "furniture_wood")
257
+
258
+ def _bake_texture(
259
+ self,
260
+ mesh: "trimesh.Trimesh", # type: ignore
261
+ multiviews: List[Image.Image],
262
+ material: Dict,
263
+ ) -> Optional[Image.Image]:
264
+ """
265
+ Bake multi-view images into a unified UV texture.
266
+
267
+ Uses visibility-aware projection to handle occlusions.
268
+ """
269
+ # Placeholder: in production, this would be proper UV unwrapping + projection
270
+ # For now, return the first multi-view as the texture
271
+
272
+ if multiviews:
273
+ return multiviews[0]
274
+ return None