Spaces:
Sleeping
Sleeping
File size: 19,811 Bytes
880bb9c d09ff05 96cbe01 d09ff05 880bb9c 1265a74 880bb9c 9afb6a2 880bb9c d09ff05 880bb9c 96cbe01 880bb9c 9afb6a2 880bb9c 1265a74 880bb9c 9afb6a2 880bb9c 9afb6a2 880bb9c 9afb6a2 880bb9c 9afb6a2 880bb9c 9afb6a2 880bb9c 9afb6a2 880bb9c 96cbe01 880bb9c 96cbe01 880bb9c d09ff05 1265a74 |
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 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 |
from flask import Flask, render_template, request, jsonify
from geopy.geocoders import Nominatim
import folium
import os
import time
from datetime import datetime
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import cv2
import numpy as np
from PIL import Image
import logging
import uuid
from werkzeug.utils import secure_filename
from PIL import Image, ImageDraw
logging.basicConfig(level=logging.DEBUG)
app = Flask(__name__)
# Configure screenshot directory
SCREENSHOT_DIR = os.path.join(app.static_folder, 'screenshots')
os.makedirs(SCREENSHOT_DIR, exist_ok=True)
UPLOAD_FOLDER = os.path.join(app.static_folder, 'uploads')
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'tif', 'tiff'}
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size
PORT = int(os.getenv('PORT', 7860))
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def kmeans_segmentation(image, n_clusters=8):
"""
Enhanced segmentation using multiple color spaces and improved filters
"""
try:
# Convert PIL Image to CV2 format
cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
# Create mask for non-black pixels with more lenient threshold
hsv = cv2.cvtColor(cv_image, cv2.COLOR_BGR2HSV)
non_black_mask = cv2.inRange(hsv, np.array([0, 0, 15]), np.array([180, 255, 255]))
# Enhanced color ranges for better classification
color_ranges = {
'vegetation': {
'hsv': {
'lower': np.array([30, 40, 40]),
'upper': np.array([90, 255, 255])
},
'lab': {
'lower': np.array([0, 0, 125]),
'upper': np.array([255, 120, 255])
},
'color': (0, 255, 0) # Green
},
'water': {
'hsv': {
'lower': np.array([85, 30, 30]),
'upper': np.array([140, 255, 255])
},
'lab': {
'lower': np.array([0, 115, 0]),
'upper': np.array([255, 255, 130])
},
'color': (255, 0, 0) # Blue
},
'building': {
'hsv': {
'lower': np.array([0, 0, 100]),
'upper': np.array([180, 50, 255])
},
'lab': {
'lower': np.array([50, 115, 115]),
'upper': np.array([200, 140, 140])
},
'color': (128, 128, 128) # Gray
},
'terrain': {
'hsv': {
'lower': np.array([0, 20, 40]), # Broader range for terrain
'upper': np.array([30, 255, 220])
},
'lab': {
'lower': np.array([20, 110, 110]), # Adjusted LAB range
'upper': np.array([200, 140, 140])
},
'color': (139, 69, 19) # Brown
}
}
# Get only non-black pixels for clustering
valid_pixels = cv_image[non_black_mask > 0].reshape(-1, 3).astype(np.float32)
if len(valid_pixels) == 0:
raise ValueError("No valid pixels found after filtering")
# Perform k-means clustering on non-black pixels
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
_, labels, centers = cv2.kmeans(valid_pixels, n_clusters, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
# Convert centers to uint8
centers = np.uint8(centers)
# Create segmented image
height, width = cv_image.shape[:2]
segmented = np.zeros((height, width, 3), dtype=np.uint8)
# Create mask for each cluster
valid_indices = np.where(non_black_mask > 0)
segmented[valid_indices] = centers[labels.flatten()]
results = {}
masks = {}
total_valid_pixels = np.count_nonzero(non_black_mask)
# Initialize masks for each feature
for feature in color_ranges:
masks[feature] = np.zeros((height, width, 3), dtype=np.uint8)
masks['other'] = np.zeros((height, width, 3), dtype=np.uint8)
# Analyze original image colors for each cluster
for cluster_id in range(n_clusters):
cluster_mask = np.zeros((height, width), dtype=np.uint8)
cluster_mask[valid_indices] = (labels.flatten() == cluster_id).astype(np.uint8)
# Get original colors for this cluster
cluster_pixels = cv_image[cluster_mask > 0]
if len(cluster_pixels) == 0:
continue
# Convert to both HSV and LAB color spaces
cluster_hsv = cv2.cvtColor(cluster_pixels.reshape(-1, 1, 3), cv2.COLOR_BGR2HSV)
cluster_lab = cv2.cvtColor(cluster_pixels.reshape(-1, 1, 3), cv2.COLOR_BGR2LAB)
# Count pixels matching each feature in both color spaces
feature_counts = {}
for feature, ranges in color_ranges.items():
hsv_mask = cv2.inRange(cluster_hsv, ranges['hsv']['lower'], ranges['hsv']['upper'])
lab_mask = cv2.inRange(cluster_lab, ranges['lab']['lower'], ranges['lab']['upper'])
# Combine results from both color spaces
combined_mask = cv2.bitwise_or(hsv_mask, lab_mask)
feature_counts[feature] = np.count_nonzero(combined_mask)
# Additional texture analysis for building detection
if feature == 'building':
gray = cv2.cvtColor(cluster_pixels.reshape(-1, 1, 3), cv2.COLOR_BGR2GRAY)
local_std = np.std(gray)
# Calculate gradient magnitude using Sobel
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
gradient_magnitude = np.sqrt(sobelx**2 + sobely**2)
# Adjust feature count based on texture analysis
if local_std < 30 and np.mean(gradient_magnitude) > 10:
feature_counts[feature] *= 1.5 # Boost building detection score
elif local_std > 50:
feature_counts[feature] *= 0.5 # Reduce building detection score
# Additional texture and color analysis for terrain/ground
elif feature == 'terrain':
# Calculate texture features
gray = cv2.cvtColor(cluster_pixels.reshape(-1, 1, 3), cv2.COLOR_BGR2GRAY)
local_std = np.std(gray)
# Calculate GLCM features
glcm = np.zeros((256, 256), dtype=np.uint8)
for i in range(len(gray)-1):
glcm[gray[i], gray[i+1]] += 1
glcm_sum = np.sum(glcm)
if glcm_sum > 0:
glcm = glcm / glcm_sum
# Calculate homogeneity
homogeneity = np.sum(glcm / (1 + np.abs(np.arange(256)[:, None] - np.arange(256))))
# Color analysis
avg_saturation = np.mean(cluster_hsv[:, :, 1])
avg_value = np.mean(cluster_hsv[:, :, 2])
# Adjust feature count based on multiple criteria
if (20 < local_std < 60 and homogeneity > 0.5
and avg_saturation < 100 and 40 < avg_value < 200):
feature_counts[feature] *= 1.8 # Boost terrain detection
elif local_std > 80 or avg_saturation > 150:
feature_counts[feature] *= 0.4 # Reduce score
# Check for grass-like patterns
if (30 <= np.mean(cluster_hsv[:, :, 0]) <= 90
and avg_saturation > 30 and local_std < 40):
feature_counts['vegetation'] = feature_counts.get('vegetation', 0) + feature_counts[feature]
feature_counts[feature] *= 0.5
# Assign cluster to feature with highest pixel count
if any(feature_counts.values()):
dominant_feature = max(feature_counts.items(), key=lambda x: x[1])[0]
if dominant_feature not in results:
results[dominant_feature] = 0
pixel_count = np.count_nonzero(cluster_mask)
percentage = (pixel_count / total_valid_pixels) * 100
results[dominant_feature] += percentage
# Update feature mask
masks[dominant_feature][cluster_mask > 0] = color_ranges[dominant_feature]['color']
else:
# Unclassified pixels
if 'other' not in results:
results['other'] = 0
pixel_count = np.count_nonzero(cluster_mask)
percentage = (pixel_count / total_valid_pixels) * 100
results['other'] += percentage
masks['other'][cluster_mask > 0] = (200, 200, 200) # Light gray
# Filter results and save masks
filtered_results = {}
filtered_masks = {}
for feature, percentage in results.items():
if percentage > 0.5: # Only include if more than 0.5%
filtered_results[feature] = round(percentage, 1)
# Save mask
mask_filename = f'mask_{feature}_{uuid.uuid4().hex[:8]}.png'
mask_path = os.path.join(app.static_folder, 'masks', mask_filename)
cv2.imwrite(mask_path, masks[feature])
filtered_masks[feature] = f'/static/masks/{mask_filename}'
# Save segmented image
segmented_filename = f'segmented_{uuid.uuid4().hex[:8]}.png'
segmented_path = os.path.join(app.static_folder, 'masks', segmented_filename)
cv2.imwrite(segmented_path, segmented)
filtered_masks['segmented'] = f'/static/masks/{segmented_filename}'
return {
'percentages': dict(sorted(filtered_results.items(), key=lambda x: x[1], reverse=True)),
'masks': filtered_masks
}
except Exception as e:
logging.error(f"Segmentation error: {str(e)}")
raise
def setup_webdriver():
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
# Check if running on Windows or Linux
if os.name == 'nt': # Windows
# Let Selenium Manager handle driver installation
chrome_options.binary_location = None # Use default Chrome installation
return webdriver.Chrome(options=chrome_options)
else: # Linux
chrome_options.binary_location = os.getenv('CHROME_BINARY_LOCATION', '/usr/bin/google-chrome')
return webdriver.Chrome(options=chrome_options)
def create_polygon_mask(image_size, points):
"""Create a mask image from polygon points"""
mask = Image.new('L', image_size, 0)
draw = ImageDraw.Draw(mask)
polygon_points = [(p['x'], p['y']) for p in points]
draw.polygon(polygon_points, fill=255)
return mask
@app.route('/')
def index():
logging.info("Index route accessed")
return render_template('index.html')
@app.route('/search_location', methods=['POST'])
def search_location():
try:
location = request.form.get('location')
# Geocode the location
geolocator = Nominatim(user_agent="map_screenshot_app")
location_data = geolocator.geocode(location)
if not location_data:
return jsonify({'error': 'Location not found'}), 404
# Create a Folium map with controls disabled
m = folium.Map(
location=[location_data.latitude, location_data.longitude],
zoom_start=20,
tiles='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
attr='Esri',
# zoom_control=False, # Disable zoom control
# dragging=False, # Disable dragging
# scrollWheelZoom=False # Disable scroll wheel zoom
)
# Save the map
map_path = os.path.join(app.static_folder, 'temp_map.html')
m.save(map_path)
return jsonify({
'lat': location_data.latitude,
'lon': location_data.longitude,
'address': location_data.address
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/capture_screenshot', methods=['POST'])
def capture_screenshot():
try:
data = request.get_json()
width = data.get('width', 600)
height = data.get('height', 400)
polygon_points = data.get('polygon', None)
map_state = data.get('mapState', None)
filename = f"screenshot_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
filepath = os.path.join(SCREENSHOT_DIR, filename)
# Create a new map with the current state
if map_state:
center = map_state['center']
zoom = map_state['zoom']
m = folium.Map(
location=[center['lat'], center['lng']],
zoom_start=zoom,
tiles='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
attr='Esri',
width=width,
height=height
)
map_path = os.path.join(app.static_folder, 'temp_map.html')
m.save(map_path)
time.sleep(1)
try:
driver = setup_webdriver()
except Exception as e:
logging.error(f"Webdriver setup error: {str(e)}")
error_msg = str(e)
if "chromedriver" in error_msg.lower():
return jsonify({
'error': 'ChromeDriver setup failed. Please ensure Chrome is installed.'
}), 500
return jsonify({
'error': 'Failed to initialize screenshot capture. Please try again.'
}), 500
try:
driver.set_window_size(width + 50, height + 50)
map_url = f"http://localhost:{PORT}/static/temp_map.html"
driver.get(map_url)
time.sleep(3)
# Check if the map loaded properly
if not os.path.exists(map_path):
raise Exception("Map file not generated")
driver.save_screenshot(filepath)
if not os.path.exists(filepath):
raise Exception("Screenshot not saved")
if polygon_points and len(polygon_points) >= 3:
img = Image.open(filepath)
mask = create_polygon_mask(img.size, polygon_points)
cutout = Image.new('RGBA', img.size, (0, 0, 0, 0))
cutout.paste(img, mask=mask)
cutout_filename = f"cutout_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
cutout_filepath = os.path.join(SCREENSHOT_DIR, cutout_filename)
cutout.save(cutout_filepath)
if not os.path.exists(cutout_filepath):
raise Exception("Cutout not saved")
return jsonify({
'success': True,
'screenshot_path': f'/static/screenshots/{filename}',
'cutout_path': f'/static/screenshots/{cutout_filename}'
})
return jsonify({
'success': True,
'screenshot_path': f'/static/screenshots/{filename}'
})
except Exception as e:
logging.error(f"Screenshot capture error: {str(e)}")
error_msg = str(e)
if "timeout" in error_msg.lower():
return jsonify({
'error': 'Map loading timed out. Please try again.'
}), 500
return jsonify({
'error': 'Failed to capture screenshot. Please try again.'
}), 500
finally:
try:
driver.quit()
except:
pass
except Exception as e:
logging.error(f"Screenshot error: {str(e)}")
return jsonify({
'error': 'An unexpected error occurred. Please try again.'
}), 500
@app.route('/analyze')
def analyze():
logging.info("Analyze route accessed")
try:
image_path = request.args.get('image')
if not image_path:
return "No image provided", 400
# Create masks directory if it doesn't exist
masks_dir = os.path.join(app.static_folder, 'masks')
os.makedirs(masks_dir, exist_ok=True)
# Clean up old mask files
for f in os.listdir(masks_dir):
if f.startswith(('mask_', 'segmented_')):
try:
os.remove(os.path.join(masks_dir, f))
except:
pass
# Clean up the image path
image_path = image_path.split('?')[0]
image_path = image_path.replace('/static/', '')
full_path = os.path.join(app.static_folder, image_path)
if not os.path.exists(full_path):
return f"Image file not found: {image_path}", 404
# Load and process image
image = Image.open(full_path)
# Ensure image is in RGB mode
if image.mode != 'RGB':
image = image.convert('RGB')
# Perform k-means segmentation
segmentation_results = kmeans_segmentation(image)
return render_template('analysis.html',
image_path=request.args.get('image').split('?')[0],
results=segmentation_results['percentages'],
masks=segmentation_results['masks'])
except Exception as e:
logging.error(f"Error in analyze route: {str(e)}", exc_info=True)
return f"Error processing image: {str(e)}", 500
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return jsonify({'error': 'No file part'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No selected file'}), 400
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
unique_filename = f"{uuid.uuid4().hex}_{filename}"
filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
file.save(filepath)
return jsonify({
'success': True,
'filepath': f'/static/uploads/{unique_filename}'
})
return jsonify({'error': 'Invalid file type'}), 400
if __name__ == '__main__':
app.run(host='0.0.0.0', port=PORT) |