jbilcke-hf HF Staff commited on
Commit
b82e00d
·
verified ·
1 Parent(s): 8e5f6db

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +190 -207
app.py CHANGED
@@ -13,241 +13,214 @@ from sklearn.cluster import DBSCAN
13
  import trimesh
14
 
15
  class GPUSatelliteModelGenerator:
 
16
  def __init__(self, building_height=0.05):
17
  self.building_height = building_height
18
 
19
- # Move color arrays to GPU using cupy
20
- self.shadow_colors = cp.array([
21
- [31, 42, 76],
22
- [58, 64, 92],
23
- [15, 27, 56],
24
- [21, 22, 50],
25
- [76, 81, 99]
26
  ])
27
 
28
- self.road_colors = cp.array([
29
- [187, 182, 175],
30
- [138, 138, 138],
31
- [142, 142, 129],
32
- [202, 199, 189]
33
  ])
34
 
 
35
  self.water_colors = cp.array([
 
 
36
  [167, 225, 217],
37
  [67, 101, 97],
38
  [53, 83, 84],
39
  [47, 94, 100],
40
  [73, 131, 135]
41
  ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
- # Output colors (BGR for OpenCV)
44
-
45
- self.roof_colors = cp.array([
46
- [191, 148, 124],
47
- [190, 142, 121],
48
- [184, 154, 139],
49
- [178, 118, 118],
50
- [164, 109, 107],
51
- [155, 113, 105],
52
- [153, 111, 106],
53
- [155, 95, 96],
54
- [135, 82, 87],
55
- [117, 82, 78],
56
- [113, 62, 50],
57
- [166, 144, 135]
58
- ])
59
 
60
- # Convert roof colors to HSV
61
- self.roof_colors_hsv = cp.asarray(cv2.cvtColor(
62
- self.roof_colors.get().reshape(-1, 1, 3).astype(np.uint8),
63
- cv2.COLOR_RGB2HSV
64
- ).reshape(-1, 3))
65
-
66
- # Normalize roof HSV values
67
- self.roof_colors_hsv[:, 0] = self.roof_colors_hsv[:, 0] * 2
68
- self.roof_colors_hsv[:, 1:] = self.roof_colors_hsv[:, 1:] / 255
69
-
70
- # Add roof tolerance (tighter than terrain to avoid confusion)
71
- self.roof_tolerance = {
72
- 'hue': 8, # Tighter hue tolerance to differentiate from terrain
73
- 'sat': 0.15,
74
- 'val': 0.15
 
 
 
 
 
75
  }
76
 
77
- # Convert reference colors to HSV on GPU
78
- self.shadow_colors_hsv = cp.asarray(cv2.cvtColor(
79
- self.shadow_colors.get().reshape(-1, 1, 3).astype(np.uint8),
80
- cv2.COLOR_RGB2HSV
81
- ).reshape(-1, 3))
82
-
83
- self.road_colors_hsv = cp.asarray(cv2.cvtColor(
84
- self.road_colors.get().reshape(-1, 1, 3).astype(np.uint8),
85
- cv2.COLOR_RGB2HSV
86
- ).reshape(-1, 3))
87
-
88
- self.water_colors_hsv = cp.asarray(cv2.cvtColor(
89
- self.water_colors.get().reshape(-1, 1, 3).astype(np.uint8),
90
- cv2.COLOR_RGB2HSV
91
- ).reshape(-1, 3))
92
-
93
- # Normalize HSV values on GPU
94
- for colors_hsv in [self.shadow_colors_hsv, self.road_colors_hsv, self.water_colors_hsv]:
95
- colors_hsv[:, 0] = colors_hsv[:, 0] * 2
96
- colors_hsv[:, 1:] = colors_hsv[:, 1:] / 255
97
-
98
- # Color tolerances
99
- self.shadow_tolerance = {'hue': 15, 'sat': 0.15, 'val': 0.12}
100
- self.road_tolerance = {'hue': 10, 'sat': 0.12, 'val': 0.15}
101
- self.water_tolerance = {'hue': 20, 'sat': 0.15, 'val': 0.20}
102
-
103
- # Colors dictionary in [B, G, R]
104
- self.colors = {
105
- 'black': cp.array([0, 0, 0]), # Shadows
106
- 'blue': cp.array([255, 0, 0]), # Water
107
- 'green': cp.array([0, 255, 0]), # Vegetation
108
- 'gray': cp.array([128, 128, 128]), # Roads
109
- 'brown': cp.array([0, 140, 255]), # Terrain
110
- 'white': cp.array([255, 255, 255]), # Buildings
111
- 'salmon': cp.array([128, 128, 255]) # Roofs
112
  }
113
 
114
- self.min_area_for_clustering = 1000
115
- self.residential_height_factor = 0.6
116
- self.isolation_threshold = 0.6
 
 
 
 
117
 
118
  @staticmethod
 
119
  def gpu_color_distance_hsv(pixel_hsv, reference_hsv, tolerance):
120
- """HSV color distance calculation"""
121
- pixel_h = pixel_hsv[0] * 2
122
- pixel_s = pixel_hsv[1] / 255
123
- pixel_v = pixel_hsv[2] / 255
124
-
125
- # Calculate circular hue difference
126
- hue_diff = cp.minimum(cp.abs(pixel_h - reference_hsv[0]),
127
- 360 - cp.abs(pixel_h - reference_hsv[0]))
128
-
129
- # Calculate saturation and value differences with weighted importance
130
- sat_diff = cp.abs(pixel_s - reference_hsv[1])
131
- val_diff = cp.abs(pixel_v - reference_hsv[2])
132
-
133
- # Combined distance check with adjusted weights
134
- return cp.logical_and(
135
- cp.logical_and(
136
- hue_diff <= tolerance['hue'],
137
- sat_diff <= tolerance['sat']
138
- ),
139
- val_diff <= tolerance['val']
140
- )
 
 
 
 
 
 
141
 
142
  def segment_image_gpu(self, img):
143
- """GPU-accelerated image segmentation with roof detection"""
144
- # Transfer image to GPU
145
- gpu_img = cp.asarray(img)
146
- gpu_hsv = cp.asarray(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
147
 
148
  height, width = img.shape[:2]
149
- output = cp.zeros_like(gpu_img)
150
-
151
- # Create a sliding window view for neighborhood analysis
152
- pad = 2
153
- gpu_hsv_pad = cp.pad(gpu_hsv, ((pad, pad), (pad, pad), (0, 0)), mode='edge')
154
 
155
- # Prepare flattened HSV data
156
  hsv_pixels = gpu_hsv.reshape(-1, 3)
157
-
158
- # Initialize masks including roofs
159
- shadow_mask = cp.zeros((height * width,), dtype=bool)
160
- road_mask = cp.zeros((height * width,), dtype=bool)
161
- water_mask = cp.zeros((height * width,), dtype=bool)
162
- roof_mask = cp.zeros((height * width,), dtype=bool)
163
-
164
- # Color matching for predefined categories
165
- for ref_hsv in self.shadow_colors_hsv:
166
- temp_tolerance = {
167
- 'hue': self.shadow_tolerance['hue'] * 1.2,
168
- 'sat': self.shadow_tolerance['sat'] * 1.1,
169
- 'val': self.shadow_tolerance['val'] * 1.2
170
- }
171
- shadow_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, temp_tolerance)
172
-
173
- for ref_hsv in self.road_colors_hsv:
174
- temp_tolerance = {
175
- 'hue': self.road_tolerance['hue'] * 1.3,
176
- 'sat': self.road_tolerance['sat'] * 1.2,
177
- 'val': self.road_tolerance['val']
178
- }
179
- road_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, temp_tolerance)
180
-
181
- for ref_hsv in self.water_colors_hsv:
182
- water_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, self.water_tolerance)
183
-
184
- # Roof detection with specific color matching
185
- for ref_hsv in self.roof_colors_hsv:
186
- roof_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, self.roof_tolerance)
187
-
188
- # Normalize HSV values
189
  h, s, v = hsv_pixels.T
190
  h = h * 2 # Convert to 0-360 range
191
  s = s / 255
192
  v = v / 255
193
 
194
- # Enhanced vegetation detection
195
- vegetation_mask = ((h >= 40) & (h <= 150) & (s >= 0.15))
196
-
197
- # Refined terrain detection to avoid roof confusion
198
- terrain_mask = (
199
- ((h >= 15) & (h <= 35) & (s >= 0.15) & (s <= 0.6)) | # Main terrain colors
200
- ((h >= 25) & (h <= 40) & (s >= 0.1) & (v >= 0.5)) # Lighter terrain
201
- ) & ~roof_mask # Explicitly exclude roof areas
202
-
203
- # Apply brightness-based corrections for roads
204
- gray_mask = (s <= 0.2) & (v >= 0.4) & (v <= 0.85)
205
- road_mask |= gray_mask & ~(shadow_mask | water_mask | vegetation_mask | terrain_mask | roof_mask)
206
-
207
- # Enhanced shadow detection
208
- dark_mask = (v <= 0.3)
209
- shadow_mask |= dark_mask & ~(water_mask | road_mask | roof_mask)
210
 
211
- # Building mask (everything that's not another category)
212
- building_mask = ~(shadow_mask | water_mask | road_mask | vegetation_mask | terrain_mask | roof_mask)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
 
214
- # Apply masks to create output
215
  output_flat = output.reshape(-1, 3)
216
- output_flat[shadow_mask] = self.colors['black']
217
- output_flat[water_mask] = self.colors['blue']
218
- output_flat[road_mask] = self.colors['gray']
219
- output_flat[vegetation_mask] = self.colors['green']
 
 
 
 
 
220
  output_flat[terrain_mask] = self.colors['brown']
221
- output_flat[roof_mask] = self.colors['salmon']
222
  output_flat[building_mask] = self.colors['white']
223
 
 
224
  segmented = output.reshape(height, width, 3)
 
225
 
226
- # Enhanced cleanup with roof consideration
227
- kernel = cp.ones((3, 3), dtype=bool)
228
- kernel[1, 1] = False
229
-
230
- # Two-pass cleanup
231
- for _ in range(2):
232
  for color_name, color_value in self.colors.items():
233
- if cp.array_equal(color_value, self.colors['white']):
234
  continue
235
 
236
  color_mask = cp.all(segmented == color_value, axis=2)
237
- dilated = binary_dilation(color_mask, structure=kernel)
238
 
239
  building_pixels = cp.all(segmented == self.colors['white'], axis=2)
240
- neighbor_count = binary_dilation(color_mask, structure=kernel).astype(int)
241
-
242
- # Special handling for roofs - they should be more granular
243
- if cp.array_equal(color_value, self.colors['salmon']):
244
- surrounded = (neighbor_count >= 4) & building_pixels # Less aggressive for roofs
245
- else:
246
- surrounded = (neighbor_count >= 5) & building_pixels
247
 
248
- segmented[surrounded] = color_value
 
249
 
250
  return segmented
 
251
 
252
  def estimate_heights_gpu(self, img, segmented):
253
  """GPU-accelerated height estimation with roof consideration"""
@@ -294,14 +267,17 @@ class GPUSatelliteModelGenerator:
294
  return height_map.get() * 0.25
295
 
296
  def generate_mesh_gpu(self, height_map, texture_img):
297
- """Generate 3D mesh using GPU-accelerated calculations"""
298
  height_map_gpu = cp.asarray(height_map)
299
  height, width = height_map.shape
300
 
301
- # Generate vertex positions on GPU
302
  x, z = cp.meshgrid(cp.arange(width), cp.arange(height))
303
  vertices = cp.stack([x, height_map_gpu * self.building_height, z], axis=-1)
304
- vertices = vertices.reshape(-1, 3)
 
 
 
305
 
306
  # Normalize coordinates
307
  scale = max(width, height)
@@ -309,45 +285,52 @@ class GPUSatelliteModelGenerator:
309
  vertices[:, 2] = vertices[:, 2] / scale * 2 - (height / scale)
310
  vertices[:, 1] = vertices[:, 1] * 2 - 1
311
 
312
- # Generate faces
 
 
 
 
 
 
 
 
 
313
  i, j = cp.meshgrid(cp.arange(height-1), cp.arange(width-1), indexing='ij')
314
  v0 = (i * width + j).flatten()
315
  v1 = v0 + 1
316
  v2 = ((i + 1) * width + j).flatten()
317
  v3 = v2 + 1
318
 
319
- faces = cp.vstack((
320
  cp.column_stack((v0, v2, v1)),
321
  cp.column_stack((v1, v2, v3))
322
  ))
323
-
324
- # Generate UV coordinates
325
- uvs = cp.zeros((vertices.shape[0], 2))
326
- uvs[:, 0] = x.flatten() / (width - 1)
327
- uvs[:, 1] = 1 - (z.flatten() / (height - 1))
328
-
329
- # Convert to CPU for mesh creation
330
- vertices_cpu = vertices.get()
331
- faces_cpu = faces.get()
332
- uvs_cpu = uvs.get()
333
-
334
- # Create mesh
335
  if len(texture_img.shape) == 3 and texture_img.shape[2] == 4:
336
  texture_img = cv2.cvtColor(texture_img, cv2.COLOR_BGRA2RGB)
337
  elif len(texture_img.shape) == 3:
338
  texture_img = cv2.cvtColor(texture_img, cv2.COLOR_BGR2RGB)
339
 
340
- mesh = trimesh.Trimesh(
341
- vertices=vertices_cpu,
342
- faces=faces_cpu,
343
  visual=trimesh.visual.TextureVisuals(
344
- uv=uvs_cpu,
345
  image=Image.fromarray(texture_img)
346
  )
347
  )
348
 
349
- return mesh
350
-
351
  def generate_and_process_map(prompt: str) -> tuple[str | None, np.ndarray | None]:
352
  """Generate satellite image from prompt and convert to 3D model using GPU acceleration"""
353
  try:
 
13
  import trimesh
14
 
15
  class GPUSatelliteModelGenerator:
16
+
17
  def __init__(self, building_height=0.05):
18
  self.building_height = building_height
19
 
20
+ # Add grass and tree colors
21
+ self.grass_colors = cp.array([
22
+ [47, 70, 69], # Light green grass
23
+ [40, 60, 55],
24
+ [45, 65, 60],
25
+ [50, 75, 65]
 
26
  ])
27
 
28
+ self.tree_colors = cp.array([
29
+ [19, 25, 16], # Dark green trees
30
+ [26, 33, 23],
31
+ [22, 30, 20],
32
+ [24, 35, 25]
33
  ])
34
 
35
+ # Expanded water colors
36
  self.water_colors = cp.array([
37
+ [40, 18, 4], # Dark blue water
38
+ [39, 25, 6],
39
  [167, 225, 217],
40
  [67, 101, 97],
41
  [53, 83, 84],
42
  [47, 94, 100],
43
  [73, 131, 135]
44
  ])
45
+
46
+ # Existing color arrays with optimized memory layout
47
+ self.shadow_colors = cp.asarray([
48
+ [31, 42, 76],
49
+ [58, 64, 92],
50
+ [15, 27, 56],
51
+ [21, 22, 50],
52
+ [76, 81, 99]
53
+ ], order='C') # Use C-contiguous memory layout
54
+
55
+ self.road_colors = cp.asarray([
56
+ [187, 182, 175],
57
+ [138, 138, 138],
58
+ [142, 142, 129],
59
+ [202, 199, 189]
60
+ ], order='C')
61
 
62
+ # Output colors (BGR for OpenCV) - optimized memory layout
63
+ self.colors = {
64
+ 'black': cp.asarray([0, 0, 0], order='C'), # Shadows
65
+ 'blue': cp.asarray([255, 0, 0], order='C'), # Water
66
+ 'dark_green': cp.asarray([0, 100, 0], order='C'), # Trees
67
+ 'light_green': cp.asarray([0, 255, 0], order='C'), # Grass
68
+ 'gray': cp.asarray([128, 128, 128], order='C'), # Roads
69
+ 'brown': cp.asarray([0, 140, 255], order='C'), # Terrain
70
+ 'white': cp.asarray([255, 255, 255], order='C'), # Buildings
71
+ 'salmon': cp.asarray([128, 128, 255], order='C') # Roofs
72
+ }
 
 
 
 
 
73
 
74
+ # Convert all color arrays to HSV space at initialization
75
+ self.initialize_hsv_colors()
76
+
77
+ # Pre-compute kernels for morphological operations
78
+ self.cleanup_kernel = cp.ones((3, 3), dtype=bool)
79
+ self.cleanup_kernel[1, 1] = False
80
+ self.tree_kernel = cp.ones((5, 5), dtype=bool)
81
+
82
+ # Optimization parameters
83
+ self.min_area = 1000
84
+ self.eps = 0.3
85
+ self.min_samples = 5
86
+ def initialize_hsv_colors(self):
87
+ """Initialize all HSV color spaces at once"""
88
+ color_arrays = {
89
+ 'grass': self.grass_colors,
90
+ 'tree': self.tree_colors,
91
+ 'water': self.water_colors,
92
+ 'shadow': self.shadow_colors,
93
+ 'road': self.road_colors
94
  }
95
 
96
+ self.hsv_colors = {}
97
+ self.tolerances = {
98
+ 'grass': {'hue': 15, 'sat': 0.2, 'val': 0.15},
99
+ 'tree': {'hue': 12, 'sat': 0.25, 'val': 0.15},
100
+ 'water': {'hue': 25, 'sat': 0.2, 'val': 0.25},
101
+ 'shadow': {'hue': 15, 'sat': 0.15, 'val': 0.12},
102
+ 'road': {'hue': 10, 'sat': 0.12, 'val': 0.15}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  }
104
 
105
+ for name, colors in color_arrays.items():
106
+ hsv = cv2.cvtColor(colors.get().reshape(-1, 1, 3).astype(np.uint8),
107
+ cv2.COLOR_RGB2HSV)
108
+ hsv_gpu = cp.asarray(hsv.reshape(-1, 3))
109
+ hsv_gpu[:, 0] = hsv_gpu[:, 0] * 2 # Scale hue to 0-360
110
+ hsv_gpu[:, 1:] = hsv_gpu[:, 1:] / 255 # Normalize S and V
111
+ self.hsv_colors[name] = hsv_gpu
112
 
113
  @staticmethod
114
+ @cp.fuse() # Use CuPy's JIT compilation
115
  def gpu_color_distance_hsv(pixel_hsv, reference_hsv, tolerance):
116
+ """Optimized HSV color distance calculation using CuPy's JIT"""
117
+ h_diff = cp.minimum(cp.abs(pixel_hsv[0] - reference_hsv[0]),
118
+ 360 - cp.abs(pixel_hsv[0] - reference_hsv[0]))
119
+ s_diff = cp.abs(pixel_hsv[1] - reference_hsv[1])
120
+ v_diff = cp.abs(pixel_hsv[2] - reference_hsv[2])
121
+
122
+ return (h_diff <= tolerance['hue']) & \
123
+ (s_diff <= tolerance['sat']) & \
124
+ (v_diff <= tolerance['val'])
125
+
126
+ def generate_tree_vertices(self, tree_mask, base_vertices):
127
+ """Generate randomized tree heights and positions"""
128
+ tree_positions = cp.where(tree_mask)
129
+ num_trees = len(tree_positions[0])
130
+
131
+ # Random height variation for trees
132
+ tree_heights = cp.random.uniform(0.15, 0.25, num_trees)
133
+
134
+ # Create vertex displacements for tree geometry
135
+ tree_vertices = base_vertices.copy()
136
+ tree_vertices[tree_positions] += cp.stack([
137
+ cp.zeros(num_trees), # x offset
138
+ tree_heights, # y offset (height)
139
+ cp.zeros(num_trees) # z offset
140
+ ], axis=1)
141
+
142
+ return tree_vertices
143
 
144
  def segment_image_gpu(self, img):
145
+ """Optimized GPU-accelerated image segmentation"""
146
+ # Transfer image to GPU with optimal memory layout
147
+ gpu_img = cp.asarray(img, order='C')
148
+ gpu_hsv = cp.asarray(cv2.cvtColor(img, cv2.COLOR_BGR2HSV), order='C')
149
 
150
  height, width = img.shape[:2]
151
+ output = cp.zeros_like(gpu_img, order='C')
 
 
 
 
152
 
153
+ # Prepare HSV data
154
  hsv_pixels = gpu_hsv.reshape(-1, 3)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  h, s, v = hsv_pixels.T
156
  h = h * 2 # Convert to 0-360 range
157
  s = s / 255
158
  v = v / 255
159
 
160
+ # Initialize masks with pre-allocated memory
161
+ masks = {
162
+ 'shadow': cp.zeros(height * width, dtype=bool),
163
+ 'road': cp.zeros(height * width, dtype=bool),
164
+ 'water': cp.zeros(height * width, dtype=bool),
165
+ 'grass': cp.zeros(height * width, dtype=bool),
166
+ 'tree': cp.zeros(height * width, dtype=bool)
167
+ }
 
 
 
 
 
 
 
 
168
 
169
+ # Parallel color matching using CuPy's optimized operations
170
+ for category, hsv_refs in self.hsv_colors.items():
171
+ tolerance = self.tolerances[category]
172
+ for ref_hsv in hsv_refs:
173
+ masks[category] |= self.gpu_color_distance_hsv(
174
+ cp.stack([h, s, v]),
175
+ ref_hsv,
176
+ tolerance
177
+ )
178
+
179
+ # Optimized terrain and building detection
180
+ vegetation_mask = ((h >= 40) & (h <= 150) & (s >= 0.15))
181
+ terrain_mask = ((h >= 15) & (h <= 35) & (s >= 0.15) & (s <= 0.6))
182
+ building_mask = ~(masks['shadow'] | masks['water'] | masks['road'] |
183
+ masks['grass'] | masks['tree'] | vegetation_mask |
184
+ terrain_mask)
185
 
186
+ # Apply masks efficiently using CuPy's advanced indexing
187
  output_flat = output.reshape(-1, 3)
188
+ for category, color_name in [
189
+ ('shadow', 'black'),
190
+ ('water', 'blue'),
191
+ ('grass', 'light_green'),
192
+ ('tree', 'dark_green'),
193
+ ('road', 'gray')
194
+ ]:
195
+ output_flat[masks[category]] = self.colors[color_name]
196
+
197
  output_flat[terrain_mask] = self.colors['brown']
 
198
  output_flat[building_mask] = self.colors['white']
199
 
200
+ # Reshape and clean up
201
  segmented = output.reshape(height, width, 3)
202
+ segmented = self.apply_morphological_cleanup(segmented)
203
 
204
+ return segmented
205
+
206
+ def apply_morphological_cleanup(self, segmented):
207
+ """Apply optimized morphological operations for cleanup"""
208
+ for _ in range(2): # Two passes for better results
 
209
  for color_name, color_value in self.colors.items():
210
+ if color_name in ['white', 'dark_green']: # Skip buildings and trees
211
  continue
212
 
213
  color_mask = cp.all(segmented == color_value, axis=2)
214
+ dilated = binary_dilation(color_mask, structure=self.cleanup_kernel)
215
 
216
  building_pixels = cp.all(segmented == self.colors['white'], axis=2)
217
+ neighbor_count = cp.sum(dilated)
 
 
 
 
 
 
218
 
219
+ if neighbor_count > 5:
220
+ segmented[building_pixels & dilated] = color_value
221
 
222
  return segmented
223
+
224
 
225
  def estimate_heights_gpu(self, img, segmented):
226
  """GPU-accelerated height estimation with roof consideration"""
 
267
  return height_map.get() * 0.25
268
 
269
  def generate_mesh_gpu(self, height_map, texture_img):
270
+ """Generate optimized 3D mesh with tree geometry"""
271
  height_map_gpu = cp.asarray(height_map)
272
  height, width = height_map.shape
273
 
274
+ # Generate base vertices
275
  x, z = cp.meshgrid(cp.arange(width), cp.arange(height))
276
  vertices = cp.stack([x, height_map_gpu * self.building_height, z], axis=-1)
277
+
278
+ # Detect tree areas and generate tree geometry
279
+ tree_mask = cp.all(texture_img == self.colors['dark_green'], axis=2)
280
+ vertices = self.generate_tree_vertices(tree_mask, vertices.reshape(-1, 3))
281
 
282
  # Normalize coordinates
283
  scale = max(width, height)
 
285
  vertices[:, 2] = vertices[:, 2] / scale * 2 - (height / scale)
286
  vertices[:, 1] = vertices[:, 1] * 2 - 1
287
 
288
+ # Generate optimized faces and UVs
289
+ faces = self.generate_faces_gpu(height, width)
290
+ uvs = self.generate_uvs_gpu(vertices, width, height)
291
+
292
+ # Create textured mesh
293
+ return self.create_textured_mesh(vertices, faces, uvs, texture_img)
294
+
295
+ @staticmethod
296
+ def generate_faces_gpu(height, width):
297
+ """Generate optimized face indices"""
298
  i, j = cp.meshgrid(cp.arange(height-1), cp.arange(width-1), indexing='ij')
299
  v0 = (i * width + j).flatten()
300
  v1 = v0 + 1
301
  v2 = ((i + 1) * width + j).flatten()
302
  v3 = v2 + 1
303
 
304
+ return cp.vstack((
305
  cp.column_stack((v0, v2, v1)),
306
  cp.column_stack((v1, v2, v3))
307
  ))
308
+
309
+ @staticmethod
310
+ def generate_uvs_gpu(vertices, width, height):
311
+ """Generate optimized UV coordinates"""
312
+ uvs = cp.zeros((vertices.shape[0], 2), order='C')
313
+ uvs[:, 0] = vertices[:, 0] / width
314
+ uvs[:, 1] = 1 - (vertices[:, 2] / height)
315
+ return uvs
316
+
317
+ @staticmethod
318
+ def create_textured_mesh(vertices, faces, uvs, texture_img):
319
+ """Create textured mesh with proper color conversion"""
320
  if len(texture_img.shape) == 3 and texture_img.shape[2] == 4:
321
  texture_img = cv2.cvtColor(texture_img, cv2.COLOR_BGRA2RGB)
322
  elif len(texture_img.shape) == 3:
323
  texture_img = cv2.cvtColor(texture_img, cv2.COLOR_BGR2RGB)
324
 
325
+ return trimesh.Trimesh(
326
+ vertices=vertices.get(),
327
+ faces=faces.get(),
328
  visual=trimesh.visual.TextureVisuals(
329
+ uv=uvs.get(),
330
  image=Image.fromarray(texture_img)
331
  )
332
  )
333
 
 
 
334
  def generate_and_process_map(prompt: str) -> tuple[str | None, np.ndarray | None]:
335
  """Generate satellite image from prompt and convert to 3D model using GPU acceleration"""
336
  try: