AdityaAdaki commited on
Commit
880bb9c
·
1 Parent(s): 35911bb

Add main.py

Browse files
Files changed (1) hide show
  1. main.py +462 -4
main.py CHANGED
@@ -1,10 +1,468 @@
1
- from flask import Flask
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  app = Flask(__name__)
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  @app.route('/')
6
- def hello_world():
7
- return 'Hello, World!'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  if __name__ == '__main__':
10
- app.run(debug=True)
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ from geopy.geocoders import Nominatim
3
+ import folium
4
+ import os
5
+ import time
6
+ from datetime import datetime
7
+ from selenium import webdriver
8
+ from selenium.webdriver.chrome.options import Options
9
+ import cv2
10
+ import numpy as np
11
+ from PIL import Image
12
+ import logging
13
+ import uuid
14
+ from werkzeug.utils import secure_filename
15
+ from PIL import Image, ImageDraw
16
 
17
  app = Flask(__name__)
18
 
19
+ # Configure screenshot directory
20
+ SCREENSHOT_DIR = os.path.join(app.static_folder, 'screenshots')
21
+ os.makedirs(SCREENSHOT_DIR, exist_ok=True)
22
+
23
+ UPLOAD_FOLDER = os.path.join(app.static_folder, 'uploads')
24
+ ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'tif', 'tiff'}
25
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
26
+
27
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
28
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size
29
+
30
+ def allowed_file(filename):
31
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
32
+
33
+ def kmeans_segmentation(image, n_clusters=8):
34
+ """
35
+ Enhanced segmentation using multiple color spaces and improved filters
36
+ """
37
+ try:
38
+ # Convert PIL Image to CV2 format
39
+ cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
40
+
41
+ # Create mask for non-black pixels with more lenient threshold
42
+ hsv = cv2.cvtColor(cv_image, cv2.COLOR_BGR2HSV)
43
+ non_black_mask = cv2.inRange(hsv, np.array([0, 0, 15]), np.array([180, 255, 255]))
44
+
45
+ # Enhanced color ranges for better classification
46
+ color_ranges = {
47
+ 'vegetation': {
48
+ 'hsv': {
49
+ 'lower': np.array([30, 40, 40]),
50
+ 'upper': np.array([90, 255, 255])
51
+ },
52
+ 'lab': {
53
+ 'lower': np.array([0, 0, 125]),
54
+ 'upper': np.array([255, 120, 255])
55
+ },
56
+ 'color': (0, 255, 0) # Green
57
+ },
58
+ 'water': {
59
+ 'hsv': {
60
+ 'lower': np.array([85, 30, 30]),
61
+ 'upper': np.array([140, 255, 255])
62
+ },
63
+ 'lab': {
64
+ 'lower': np.array([0, 115, 0]),
65
+ 'upper': np.array([255, 255, 130])
66
+ },
67
+ 'color': (255, 0, 0) # Blue
68
+ },
69
+ 'building': {
70
+ 'hsv': {
71
+ 'lower': np.array([0, 0, 100]),
72
+ 'upper': np.array([180, 50, 255])
73
+ },
74
+ 'lab': {
75
+ 'lower': np.array([50, 115, 115]),
76
+ 'upper': np.array([200, 140, 140])
77
+ },
78
+ 'color': (128, 128, 128) # Gray
79
+ },
80
+ 'terrain': {
81
+ 'hsv': {
82
+ 'lower': np.array([0, 20, 40]), # Broader range for terrain
83
+ 'upper': np.array([30, 255, 220])
84
+ },
85
+ 'lab': {
86
+ 'lower': np.array([20, 110, 110]), # Adjusted LAB range
87
+ 'upper': np.array([200, 140, 140])
88
+ },
89
+ 'color': (139, 69, 19) # Brown
90
+ }
91
+ }
92
+
93
+ # Get only non-black pixels for clustering
94
+ valid_pixels = cv_image[non_black_mask > 0].reshape(-1, 3).astype(np.float32)
95
+
96
+ if len(valid_pixels) == 0:
97
+ raise ValueError("No valid pixels found after filtering")
98
+
99
+ # Perform k-means clustering on non-black pixels
100
+ criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
101
+ _, labels, centers = cv2.kmeans(valid_pixels, n_clusters, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
102
+
103
+ # Convert centers to uint8
104
+ centers = np.uint8(centers)
105
+
106
+ # Create segmented image
107
+ height, width = cv_image.shape[:2]
108
+ segmented = np.zeros((height, width, 3), dtype=np.uint8)
109
+
110
+ # Create mask for each cluster
111
+ valid_indices = np.where(non_black_mask > 0)
112
+ segmented[valid_indices] = centers[labels.flatten()]
113
+
114
+ results = {}
115
+ masks = {}
116
+ total_valid_pixels = np.count_nonzero(non_black_mask)
117
+
118
+ # Initialize masks for each feature
119
+ for feature in color_ranges:
120
+ masks[feature] = np.zeros((height, width, 3), dtype=np.uint8)
121
+ masks['other'] = np.zeros((height, width, 3), dtype=np.uint8)
122
+
123
+ # Analyze original image colors for each cluster
124
+ for cluster_id in range(n_clusters):
125
+ cluster_mask = np.zeros((height, width), dtype=np.uint8)
126
+ cluster_mask[valid_indices] = (labels.flatten() == cluster_id).astype(np.uint8)
127
+
128
+ # Get original colors for this cluster
129
+ cluster_pixels = cv_image[cluster_mask > 0]
130
+ if len(cluster_pixels) == 0:
131
+ continue
132
+
133
+ # Convert to both HSV and LAB color spaces
134
+ cluster_hsv = cv2.cvtColor(cluster_pixels.reshape(-1, 1, 3), cv2.COLOR_BGR2HSV)
135
+ cluster_lab = cv2.cvtColor(cluster_pixels.reshape(-1, 1, 3), cv2.COLOR_BGR2LAB)
136
+
137
+ # Count pixels matching each feature in both color spaces
138
+ feature_counts = {}
139
+ for feature, ranges in color_ranges.items():
140
+ hsv_mask = cv2.inRange(cluster_hsv, ranges['hsv']['lower'], ranges['hsv']['upper'])
141
+ lab_mask = cv2.inRange(cluster_lab, ranges['lab']['lower'], ranges['lab']['upper'])
142
+
143
+ # Combine results from both color spaces
144
+ combined_mask = cv2.bitwise_or(hsv_mask, lab_mask)
145
+ feature_counts[feature] = np.count_nonzero(combined_mask)
146
+
147
+ # Additional texture analysis for building detection
148
+ if feature == 'building':
149
+ gray = cv2.cvtColor(cluster_pixels.reshape(-1, 1, 3), cv2.COLOR_BGR2GRAY)
150
+ local_std = np.std(gray)
151
+
152
+ # Calculate gradient magnitude using Sobel
153
+ sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
154
+ sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
155
+ gradient_magnitude = np.sqrt(sobelx**2 + sobely**2)
156
+
157
+ # Adjust feature count based on texture analysis
158
+ if local_std < 30 and np.mean(gradient_magnitude) > 10:
159
+ feature_counts[feature] *= 1.5 # Boost building detection score
160
+ elif local_std > 50:
161
+ feature_counts[feature] *= 0.5 # Reduce building detection score
162
+
163
+ # Additional texture and color analysis for terrain/ground
164
+ elif feature == 'terrain':
165
+ # Calculate texture features
166
+ gray = cv2.cvtColor(cluster_pixels.reshape(-1, 1, 3), cv2.COLOR_BGR2GRAY)
167
+ local_std = np.std(gray)
168
+
169
+ # Calculate GLCM features
170
+ glcm = np.zeros((256, 256), dtype=np.uint8)
171
+ for i in range(len(gray)-1):
172
+ glcm[gray[i], gray[i+1]] += 1
173
+ glcm_sum = np.sum(glcm)
174
+ if glcm_sum > 0:
175
+ glcm = glcm / glcm_sum
176
+
177
+ # Calculate homogeneity
178
+ homogeneity = np.sum(glcm / (1 + np.abs(np.arange(256)[:, None] - np.arange(256))))
179
+
180
+ # Color analysis
181
+ avg_saturation = np.mean(cluster_hsv[:, :, 1])
182
+ avg_value = np.mean(cluster_hsv[:, :, 2])
183
+
184
+ # Adjust feature count based on multiple criteria
185
+ if (20 < local_std < 60 and homogeneity > 0.5
186
+ and avg_saturation < 100 and 40 < avg_value < 200):
187
+ feature_counts[feature] *= 1.8 # Boost terrain detection
188
+ elif local_std > 80 or avg_saturation > 150:
189
+ feature_counts[feature] *= 0.4 # Reduce score
190
+
191
+ # Check for grass-like patterns
192
+ if (30 <= np.mean(cluster_hsv[:, :, 0]) <= 90
193
+ and avg_saturation > 30 and local_std < 40):
194
+ feature_counts['vegetation'] = feature_counts.get('vegetation', 0) + feature_counts[feature]
195
+ feature_counts[feature] *= 0.5
196
+
197
+ # Assign cluster to feature with highest pixel count
198
+ if any(feature_counts.values()):
199
+ dominant_feature = max(feature_counts.items(), key=lambda x: x[1])[0]
200
+ if dominant_feature not in results:
201
+ results[dominant_feature] = 0
202
+
203
+ pixel_count = np.count_nonzero(cluster_mask)
204
+ percentage = (pixel_count / total_valid_pixels) * 100
205
+ results[dominant_feature] += percentage
206
+
207
+ # Update feature mask
208
+ masks[dominant_feature][cluster_mask > 0] = color_ranges[dominant_feature]['color']
209
+ else:
210
+ # Unclassified pixels
211
+ if 'other' not in results:
212
+ results['other'] = 0
213
+ pixel_count = np.count_nonzero(cluster_mask)
214
+ percentage = (pixel_count / total_valid_pixels) * 100
215
+ results['other'] += percentage
216
+ masks['other'][cluster_mask > 0] = (200, 200, 200) # Light gray
217
+
218
+ # Filter results and save masks
219
+ filtered_results = {}
220
+ filtered_masks = {}
221
+
222
+ for feature, percentage in results.items():
223
+ if percentage > 0.5: # Only include if more than 0.5%
224
+ filtered_results[feature] = round(percentage, 1)
225
+
226
+ # Save mask
227
+ mask_filename = f'mask_{feature}_{uuid.uuid4().hex[:8]}.png'
228
+ mask_path = os.path.join(app.static_folder, 'masks', mask_filename)
229
+ cv2.imwrite(mask_path, masks[feature])
230
+ filtered_masks[feature] = f'/static/masks/{mask_filename}'
231
+
232
+ # Save segmented image
233
+ segmented_filename = f'segmented_{uuid.uuid4().hex[:8]}.png'
234
+ segmented_path = os.path.join(app.static_folder, 'masks', segmented_filename)
235
+ cv2.imwrite(segmented_path, segmented)
236
+ filtered_masks['segmented'] = f'/static/masks/{segmented_filename}'
237
+
238
+ return {
239
+ 'percentages': dict(sorted(filtered_results.items(), key=lambda x: x[1], reverse=True)),
240
+ 'masks': filtered_masks
241
+ }
242
+
243
+ except Exception as e:
244
+ logging.error(f"Segmentation error: {str(e)}")
245
+ raise
246
+
247
+ def setup_webdriver():
248
+ chrome_options = Options()
249
+ chrome_options.add_argument('--headless')
250
+ chrome_options.add_argument('--no-sandbox')
251
+ chrome_options.add_argument('--disable-dev-shm-usage')
252
+ chrome_options.binary_location = '/usr/bin/chromium'
253
+ return webdriver.Chrome(options=chrome_options)
254
+
255
+ def create_polygon_mask(image_size, points):
256
+ """Create a mask image from polygon points"""
257
+ mask = Image.new('L', image_size, 0)
258
+ draw = ImageDraw.Draw(mask)
259
+ polygon_points = [(p['x'], p['y']) for p in points]
260
+ draw.polygon(polygon_points, fill=255)
261
+ return mask
262
+
263
  @app.route('/')
264
+ def index():
265
+ return render_template('index.html')
266
+
267
+ @app.route('/search_location', methods=['POST'])
268
+ def search_location():
269
+ try:
270
+ location = request.form.get('location')
271
+
272
+ # Geocode the location
273
+ geolocator = Nominatim(user_agent="map_screenshot_app")
274
+ location_data = geolocator.geocode(location)
275
+
276
+ if not location_data:
277
+ return jsonify({'error': 'Location not found'}), 404
278
+
279
+ # Create a Folium map with controls disabled
280
+ m = folium.Map(
281
+ location=[location_data.latitude, location_data.longitude],
282
+ zoom_start=20,
283
+ tiles='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
284
+ attr='Esri',
285
+ # zoom_control=False, # Disable zoom control
286
+ # dragging=False, # Disable dragging
287
+ # scrollWheelZoom=False # Disable scroll wheel zoom
288
+ )
289
+
290
+ # Save the map
291
+ map_path = os.path.join(app.static_folder, 'temp_map.html')
292
+ m.save(map_path)
293
+
294
+ return jsonify({
295
+ 'lat': location_data.latitude,
296
+ 'lon': location_data.longitude,
297
+ 'address': location_data.address
298
+ })
299
+
300
+ except Exception as e:
301
+ return jsonify({'error': str(e)}), 500
302
+
303
+ @app.route('/capture_screenshot', methods=['POST'])
304
+ def capture_screenshot():
305
+ try:
306
+ data = request.get_json()
307
+ width = data.get('width', 600)
308
+ height = data.get('height', 400)
309
+ polygon_points = data.get('polygon', None)
310
+ map_state = data.get('mapState', None)
311
+
312
+ filename = f"screenshot_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
313
+ filepath = os.path.join(SCREENSHOT_DIR, filename)
314
+
315
+ # Create a new map with the current state
316
+ if map_state:
317
+ center = map_state['center']
318
+ zoom = map_state['zoom']
319
+
320
+ m = folium.Map(
321
+ location=[center['lat'], center['lng']],
322
+ zoom_start=zoom,
323
+ tiles='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
324
+ attr='Esri',
325
+ width=width,
326
+ height=height
327
+ )
328
+
329
+ # Set the bounds
330
+ bounds = map_state['bounds']
331
+ m.fit_bounds([[bounds['south'], bounds['west']],
332
+ [bounds['north'], bounds['east']]])
333
+
334
+ # Add custom JavaScript to ensure correct zoom
335
+ m.get_root().html.add_child(folium.Element(f"""
336
+ <script>
337
+ document.addEventListener('DOMContentLoaded', function() {{
338
+ setTimeout(function() {{
339
+ var map = document.querySelector('#map');
340
+ if (map && map._leaflet_map) {{
341
+ map._leaflet_map.setView([{center['lat']}, {center['lng']}], {zoom});
342
+ }}
343
+ }}, 1000);
344
+ }});
345
+ </script>
346
+ """))
347
+
348
+ # Save the map
349
+ map_path = os.path.join(app.static_folder, 'temp_map.html')
350
+ m.save(map_path)
351
+
352
+ # Increase wait time to ensure map loads completely
353
+ time.sleep(1)
354
+
355
+ driver = setup_webdriver()
356
+ try:
357
+ driver.set_window_size(width + 50, height + 50) # Add padding to prevent scrollbars
358
+ map_url = f"http://localhost:{app.config['PORT']}/static/temp_map.html"
359
+ driver.get(map_url)
360
+
361
+ # Wait for map to load and settle
362
+ time.sleep(3)
363
+
364
+ # Take screenshot
365
+ driver.save_screenshot(filepath)
366
+
367
+ if polygon_points and len(polygon_points) >= 3:
368
+ # Create polygon cutout
369
+ img = Image.open(filepath)
370
+ mask = create_polygon_mask(img.size, polygon_points)
371
+
372
+ # Create cutout image
373
+ cutout = Image.new('RGBA', img.size, (0, 0, 0, 0))
374
+ cutout.paste(img, mask=mask)
375
+
376
+ # Save cutout
377
+ cutout_filename = f"cutout_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
378
+ cutout_filepath = os.path.join(SCREENSHOT_DIR, cutout_filename)
379
+ cutout.save(cutout_filepath)
380
+
381
+ return jsonify({
382
+ 'success': True,
383
+ 'screenshot_path': f'/static/screenshots/{filename}',
384
+ 'cutout_path': f'/static/screenshots/{cutout_filename}'
385
+ })
386
+
387
+ return jsonify({
388
+ 'success': True,
389
+ 'screenshot_path': f'/static/screenshots/{filename}'
390
+ })
391
+
392
+ finally:
393
+ driver.quit()
394
+
395
+ except Exception as e:
396
+ logging.error(f"Screenshot error: {str(e)}")
397
+ return jsonify({'error': str(e)}), 500
398
+
399
+ @app.route('/analyze')
400
+ def analyze():
401
+ try:
402
+ image_path = request.args.get('image')
403
+ if not image_path:
404
+ return "No image provided", 400
405
+
406
+ # Create masks directory if it doesn't exist
407
+ masks_dir = os.path.join(app.static_folder, 'masks')
408
+ os.makedirs(masks_dir, exist_ok=True)
409
+
410
+ # Clean up old mask files
411
+ for f in os.listdir(masks_dir):
412
+ if f.startswith(('mask_', 'segmented_')):
413
+ try:
414
+ os.remove(os.path.join(masks_dir, f))
415
+ except:
416
+ pass
417
+
418
+ # Clean up the image path
419
+ image_path = image_path.split('?')[0]
420
+ image_path = image_path.replace('/static/', '')
421
+ full_path = os.path.join(app.static_folder, image_path)
422
+
423
+ if not os.path.exists(full_path):
424
+ return f"Image file not found: {image_path}", 404
425
+
426
+ # Load and process image
427
+ image = Image.open(full_path)
428
+
429
+ # Ensure image is in RGB mode
430
+ if image.mode != 'RGB':
431
+ image = image.convert('RGB')
432
+
433
+ # Perform k-means segmentation
434
+ segmentation_results = kmeans_segmentation(image)
435
+
436
+ return render_template('analysis.html',
437
+ image_path=request.args.get('image').split('?')[0],
438
+ results=segmentation_results['percentages'],
439
+ masks=segmentation_results['masks'])
440
+
441
+ except Exception as e:
442
+ logging.error(f"Error processing image: {str(e)}")
443
+ return f"Error processing image: {str(e)}", 500
444
+
445
+ @app.route('/upload', methods=['POST'])
446
+ def upload_file():
447
+ if 'file' not in request.files:
448
+ return jsonify({'error': 'No file part'}), 400
449
+
450
+ file = request.files['file']
451
+ if file.filename == '':
452
+ return jsonify({'error': 'No selected file'}), 400
453
+
454
+ if file and allowed_file(file.filename):
455
+ filename = secure_filename(file.filename)
456
+ unique_filename = f"{uuid.uuid4().hex}_{filename}"
457
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
458
+ file.save(filepath)
459
+
460
+ return jsonify({
461
+ 'success': True,
462
+ 'filepath': f'/static/uploads/{unique_filename}'
463
+ })
464
+
465
+ return jsonify({'error': 'Invalid file type'}), 400
466
 
467
  if __name__ == '__main__':
468
+ app.run(host='0.0.0.0', port=7860)