jbilcke-hf HF Staff commited on
Commit
bcf8146
·
verified ·
1 Parent(s): 216ab8e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +255 -23
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import os
2
  import tempfile
3
  import torch
@@ -6,23 +7,239 @@ import gradio as gr
6
  from PIL import Image
7
  import cv2
8
  from diffusers import DiffusionPipeline
9
- from script import SatelliteModelGenerator
 
 
 
 
10
 
11
- # Initialize models and device
12
- device = "cuda" if torch.cuda.is_available() else "cpu"
13
- dtype = torch.bfloat16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- repo_id = "black-forest-labs/FLUX.1-dev"
16
- adapter_id = "jbilcke-hf/flux-satellite"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
- flux_pipe = DiffusionPipeline.from_pretrained(repo_id, torch_dtype=torch.bfloat16)
19
- flux_pipe.load_lora_weights(adapter_id)
20
- flux_pipe = flux_pipe.to(device)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  def generate_and_process_map(prompt: str) -> str | None:
23
- """Generate satellite image from prompt and convert to 3D model."""
24
  try:
25
- # Set dimensions
26
  width = height = 1024
27
 
28
  # Generate random seed
@@ -46,19 +263,19 @@ def generate_and_process_map(prompt: str) -> str | None:
46
  # Convert PIL Image to OpenCV format
47
  cv_image = cv2.cvtColor(np.array(generated_image), cv2.COLOR_RGB2BGR)
48
 
49
- # Initialize SatelliteModelGenerator
50
- generator = SatelliteModelGenerator(building_height=0.09)
51
 
52
- # Process image
53
- print("Segmenting image...")
54
- segmented_img = generator.segment_image(cv_image, window_size=5)
55
 
56
- print("Estimating heights...")
57
- height_map = generator.estimate_heights(cv_image, segmented_img)
58
 
59
- # Generate mesh
60
- print("Generating mesh...")
61
- mesh = generator.generate_mesh(height_map, cv_image, add_walls=True)
62
 
63
  # Export to GLB
64
  temp_dir = tempfile.mkdtemp()
@@ -75,8 +292,8 @@ def generate_and_process_map(prompt: str) -> str | None:
75
 
76
  # Create Gradio interface
77
  with gr.Blocks() as demo:
78
- gr.Markdown("# Text to Map")
79
- gr.Markdown("Generate 3D maps from text descriptions using FLUX and mesh generation.")
80
 
81
  with gr.Row():
82
  prompt_input = gr.Text(
@@ -102,4 +319,19 @@ with gr.Blocks() as demo:
102
  )
103
 
104
  if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  demo.queue().launch()
 
1
+ # text_to_map_app.py
2
  import os
3
  import tempfile
4
  import torch
 
7
  from PIL import Image
8
  import cv2
9
  from diffusers import DiffusionPipeline
10
+ import cupy as cp
11
+ from cupyx.scipy.ndimage import label as cp_label
12
+ from cupyx.scipy.ndimage import binary_dilation
13
+ from sklearn.cluster import DBSCAN
14
+ import trimesh
15
 
16
+ class GPUSatelliteModelGenerator:
17
+ def __init__(self, building_height=0.05):
18
+ self.building_height = building_height
19
+
20
+ # Move color arrays to GPU using cupy
21
+ self.shadow_colors = cp.array([
22
+ [31, 42, 76],
23
+ [58, 64, 92],
24
+ [15, 27, 56],
25
+ [21, 22, 50],
26
+ [76, 81, 99]
27
+ ])
28
+
29
+ self.road_colors = cp.array([
30
+ [187, 182, 175],
31
+ [138, 138, 138],
32
+ [142, 142, 129],
33
+ [202, 199, 189]
34
+ ])
35
+
36
+ self.water_colors = cp.array([
37
+ [167, 225, 217],
38
+ [67, 101, 97],
39
+ [53, 83, 84],
40
+ [47, 94, 100],
41
+ [73, 131, 135]
42
+ ])
43
+
44
+ # Convert reference colors to HSV on GPU
45
+ self.shadow_colors_hsv = cp.asarray(cv2.cvtColor(
46
+ self.shadow_colors.get().reshape(-1, 1, 3).astype(np.uint8),
47
+ cv2.COLOR_RGB2HSV
48
+ ).reshape(-1, 3))
49
+
50
+ self.road_colors_hsv = cp.asarray(cv2.cvtColor(
51
+ self.road_colors.get().reshape(-1, 1, 3).astype(np.uint8),
52
+ cv2.COLOR_RGB2HSV
53
+ ).reshape(-1, 3))
54
+
55
+ self.water_colors_hsv = cp.asarray(cv2.cvtColor(
56
+ self.water_colors.get().reshape(-1, 1, 3).astype(np.uint8),
57
+ cv2.COLOR_RGB2HSV
58
+ ).reshape(-1, 3))
59
+
60
+ # Normalize HSV values on GPU
61
+ for colors_hsv in [self.shadow_colors_hsv, self.road_colors_hsv, self.water_colors_hsv]:
62
+ colors_hsv[:, 0] = colors_hsv[:, 0] * 2
63
+ colors_hsv[:, 1:] = colors_hsv[:, 1:] / 255
64
+
65
+ # Color tolerances
66
+ self.shadow_tolerance = {'hue': 15, 'sat': 0.15, 'val': 0.12}
67
+ self.road_tolerance = {'hue': 10, 'sat': 0.12, 'val': 0.15}
68
+ self.water_tolerance = {'hue': 20, 'sat': 0.15, 'val': 0.20}
69
+
70
+ # Output colors (BGR for OpenCV)
71
+ self.colors = {
72
+ 'black': cp.array([0, 0, 0]),
73
+ 'blue': cp.array([255, 0, 0]),
74
+ 'green': cp.array([0, 255, 0]),
75
+ 'gray': cp.array([128, 128, 128]),
76
+ 'brown': cp.array([0, 140, 255]),
77
+ 'white': cp.array([255, 255, 255])
78
+ }
79
+
80
+ self.min_area_for_clustering = 1000
81
+ self.residential_height_factor = 0.6
82
+ self.isolation_threshold = 0.6
83
 
84
+ @staticmethod
85
+ def gpu_color_distance_hsv(pixel_hsv, reference_hsv, tolerance):
86
+ """GPU-accelerated HSV color distance calculation"""
87
+ pixel_h = pixel_hsv[0] * 2
88
+ pixel_s = pixel_hsv[1] / 255
89
+ pixel_v = pixel_hsv[2] / 255
90
+
91
+ hue_diff = cp.minimum(cp.abs(pixel_h - reference_hsv[0]),
92
+ 360 - cp.abs(pixel_h - reference_hsv[0]))
93
+ sat_diff = cp.abs(pixel_s - reference_hsv[1])
94
+ val_diff = cp.abs(pixel_v - reference_hsv[2])
95
+
96
+ return cp.logical_and(
97
+ cp.logical_and(hue_diff <= tolerance['hue'],
98
+ sat_diff <= tolerance['sat']),
99
+ val_diff <= tolerance['val']
100
+ )
101
 
102
+ def segment_image_gpu(self, img):
103
+ """GPU-accelerated image segmentation"""
104
+ # Transfer image to GPU
105
+ gpu_img = cp.asarray(img)
106
+ gpu_hsv = cp.asarray(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
107
+
108
+ height, width = img.shape[:2]
109
+ output = cp.zeros_like(gpu_img)
110
+
111
+ # Vectorized color matching on GPU
112
+ hsv_pixels = gpu_hsv.reshape(-1, 3)
113
+
114
+ # Create masks for each category
115
+ shadow_mask = cp.zeros((height * width,), dtype=bool)
116
+ road_mask = cp.zeros((height * width,), dtype=bool)
117
+ water_mask = cp.zeros((height * width,), dtype=bool)
118
+
119
+ # Vectorized color matching
120
+ for ref_hsv in self.shadow_colors_hsv:
121
+ shadow_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, self.shadow_tolerance)
122
+
123
+ for ref_hsv in self.road_colors_hsv:
124
+ road_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, self.road_tolerance)
125
+
126
+ for ref_hsv in self.water_colors_hsv:
127
+ water_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, self.water_tolerance)
128
+
129
+ # Apply masks
130
+ output_flat = output.reshape(-1, 3)
131
+ output_flat[shadow_mask] = self.colors['black']
132
+ output_flat[water_mask] = self.colors['blue']
133
+ output_flat[road_mask] = self.colors['gray']
134
+
135
+ # Vegetation and building detection
136
+ h, s, v = hsv_pixels.T
137
+ h = h * 2 # Convert to 0-360 range
138
+ s = s / 255
139
+ v = v / 255
140
+
141
+ vegetation_mask = (h >= 40) & (h <= 150) & (s >= 0.15)
142
+ building_mask = ~(shadow_mask | water_mask | road_mask | vegetation_mask)
143
+
144
+ output_flat[vegetation_mask] = self.colors['green']
145
+ output_flat[building_mask] = self.colors['white']
146
+
147
+ return output.reshape(height, width, 3)
148
+
149
+ def estimate_heights_gpu(self, img, segmented):
150
+ """GPU-accelerated height estimation"""
151
+ gpu_segmented = cp.asarray(segmented)
152
+ buildings_mask = cp.all(gpu_segmented == self.colors['white'], axis=2)
153
+ shadows_mask = cp.all(gpu_segmented == self.colors['black'], axis=2)
154
+
155
+ # Connected components labeling on GPU
156
+ labeled_array, num_features = cp_label(buildings_mask)
157
+
158
+ # Calculate areas using GPU
159
+ areas = cp.bincount(labeled_array.ravel())[1:] # Skip background
160
+ max_area = cp.max(areas) if len(areas) > 0 else 1
161
+
162
+ height_map = cp.zeros_like(labeled_array, dtype=cp.float32)
163
+
164
+ # Process each building
165
+ for label in range(1, num_features + 1):
166
+ building_mask = (labeled_array == label)
167
+ if not cp.any(building_mask):
168
+ continue
169
+
170
+ area = areas[label-1]
171
+ size_factor = 0.3 + 0.7 * (area / max_area)
172
+
173
+ # Calculate shadow influence
174
+ dilated = binary_dilation(building_mask, structure=cp.ones((5,5)))
175
+ shadow_ratio = cp.sum(dilated & shadows_mask) / cp.sum(dilated)
176
+ shadow_factor = 0.2 + 0.8 * shadow_ratio
177
+
178
+ # Height calculation based on size and shadows
179
+ final_height = size_factor * shadow_factor
180
+ height_map[building_mask] = final_height
181
+
182
+ return height_map.get() * 0.15
183
+
184
+ def generate_mesh_gpu(self, height_map, texture_img):
185
+ """Generate 3D mesh using GPU-accelerated calculations"""
186
+ height_map_gpu = cp.asarray(height_map)
187
+ height, width = height_map.shape
188
+
189
+ # Generate vertex positions on GPU
190
+ x, z = cp.meshgrid(cp.arange(width), cp.arange(height))
191
+ vertices = cp.stack([x, height_map_gpu * self.building_height, z], axis=-1)
192
+ vertices = vertices.reshape(-1, 3)
193
+
194
+ # Normalize coordinates
195
+ scale = max(width, height)
196
+ vertices[:, 0] = vertices[:, 0] / scale * 2 - (width / scale)
197
+ vertices[:, 2] = vertices[:, 2] / scale * 2 - (height / scale)
198
+ vertices[:, 1] = vertices[:, 1] * 2 - 1
199
+
200
+ # Generate faces
201
+ i, j = cp.meshgrid(cp.arange(height-1), cp.arange(width-1), indexing='ij')
202
+ v0 = (i * width + j).flatten()
203
+ v1 = v0 + 1
204
+ v2 = ((i + 1) * width + j).flatten()
205
+ v3 = v2 + 1
206
+
207
+ faces = cp.vstack((
208
+ cp.column_stack((v0, v2, v1)),
209
+ cp.column_stack((v1, v2, v3))
210
+ ))
211
+
212
+ # Generate UV coordinates
213
+ uvs = cp.zeros((vertices.shape[0], 2))
214
+ uvs[:, 0] = x.flatten() / (width - 1)
215
+ uvs[:, 1] = 1 - (z.flatten() / (height - 1))
216
+
217
+ # Convert to CPU for mesh creation
218
+ vertices_cpu = vertices.get()
219
+ faces_cpu = faces.get()
220
+ uvs_cpu = uvs.get()
221
+
222
+ # Create mesh
223
+ if len(texture_img.shape) == 3 and texture_img.shape[2] == 4:
224
+ texture_img = cv2.cvtColor(texture_img, cv2.COLOR_BGRA2RGB)
225
+ elif len(texture_img.shape) == 3:
226
+ texture_img = cv2.cvtColor(texture_img, cv2.COLOR_BGR2RGB)
227
+
228
+ mesh = trimesh.Trimesh(
229
+ vertices=vertices_cpu,
230
+ faces=faces_cpu,
231
+ visual=trimesh.visual.TextureVisuals(
232
+ uv=uvs_cpu,
233
+ image=Image.fromarray(texture_img)
234
+ )
235
+ )
236
+
237
+ return mesh
238
 
239
  def generate_and_process_map(prompt: str) -> str | None:
240
+ """Generate satellite image from prompt and convert to 3D model using GPU acceleration"""
241
  try:
242
+ # Set dimensions and device
243
  width = height = 1024
244
 
245
  # Generate random seed
 
263
  # Convert PIL Image to OpenCV format
264
  cv_image = cv2.cvtColor(np.array(generated_image), cv2.COLOR_RGB2BGR)
265
 
266
+ # Initialize GPU-accelerated generator
267
+ generator = GPUSatelliteModelGenerator(building_height=0.09)
268
 
269
+ # Process image using GPU
270
+ print("Segmenting image using GPU...")
271
+ segmented_img = generator.segment_image_gpu(cv_image)
272
 
273
+ print("Estimating heights using GPU...")
274
+ height_map = generator.estimate_heights_gpu(cv_image, segmented_img)
275
 
276
+ # Generate mesh using GPU-accelerated calculations
277
+ print("Generating mesh using GPU...")
278
+ mesh = generator.generate_mesh_gpu(height_map, cv_image)
279
 
280
  # Export to GLB
281
  temp_dir = tempfile.mkdtemp()
 
292
 
293
  # Create Gradio interface
294
  with gr.Blocks() as demo:
295
+ gr.Markdown("# GPU-Accelerated Text to Map")
296
+ gr.Markdown("Generate 3D maps from text descriptions using FLUX and GPU-accelerated mesh generation.")
297
 
298
  with gr.Row():
299
  prompt_input = gr.Text(
 
319
  )
320
 
321
  if __name__ == "__main__":
322
+ # Initialize FLUX pipeline
323
+ device = "cuda" if torch.cuda.is_available() else "cpu"
324
+ dtype = torch.bfloat16
325
+
326
+ repo_id = "black-forest-labs/FLUX.1-dev"
327
+ adapter_id = "jbilcke-hf/flux-satellite"
328
+
329
+ flux_pipe = DiffusionPipeline.from_pretrained(
330
+ repo_id,
331
+ torch_dtype=torch.bfloat16
332
+ )
333
+ flux_pipe.load_lora_weights(adapter_id)
334
+ flux_pipe = flux_pipe.to(device)
335
+
336
+ # Launch Gradio app
337
  demo.queue().launch()