sikeaditya commited on
Commit
9ebd95d
·
verified ·
1 Parent(s): b03a833

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +469 -0
  2. requirements.txt +8 -0
app.py ADDED
@@ -0,0 +1,469 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ return webdriver.Chrome(options=chrome_options)
253
+
254
+ def create_polygon_mask(image_size, points):
255
+ """Create a mask image from polygon points"""
256
+ mask = Image.new('L', image_size, 0)
257
+ draw = ImageDraw.Draw(mask)
258
+ polygon_points = [(p['x'], p['y']) for p in points]
259
+ draw.polygon(polygon_points, fill=255)
260
+ return mask
261
+
262
+ @app.route('/')
263
+ def index():
264
+ return render_template('index.html')
265
+
266
+ @app.route('/search_location', methods=['POST'])
267
+ def search_location():
268
+ try:
269
+ location = request.form.get('location')
270
+
271
+ # Geocode the location
272
+ geolocator = Nominatim(user_agent="map_screenshot_app")
273
+ location_data = geolocator.geocode(location)
274
+
275
+ if not location_data:
276
+ return jsonify({'error': 'Location not found'}), 404
277
+
278
+ # Create a Folium map with controls disabled
279
+ m = folium.Map(
280
+ location=[location_data.latitude, location_data.longitude],
281
+ zoom_start=20,
282
+ tiles='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
283
+ attr='Esri',
284
+ # zoom_control=False, # Disable zoom control
285
+ # dragging=False, # Disable dragging
286
+ # scrollWheelZoom=False # Disable scroll wheel zoom
287
+ )
288
+
289
+ # Save the map
290
+ map_path = os.path.join(app.static_folder, 'temp_map.html')
291
+ m.save(map_path)
292
+
293
+ return jsonify({
294
+ 'lat': location_data.latitude,
295
+ 'lon': location_data.longitude,
296
+ 'address': location_data.address
297
+ })
298
+
299
+ except Exception as e:
300
+ return jsonify({'error': str(e)}), 500
301
+
302
+ @app.route('/capture_screenshot', methods=['POST'])
303
+ def capture_screenshot():
304
+ try:
305
+ data = request.get_json()
306
+ width = data.get('width', 600)
307
+ height = data.get('height', 400)
308
+ polygon_points = data.get('polygon', None)
309
+ map_state = data.get('mapState', None)
310
+
311
+ filename = f"screenshot_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
312
+ filepath = os.path.join(SCREENSHOT_DIR, filename)
313
+
314
+ # Create a new map with the current state
315
+ if map_state:
316
+ center = map_state['center']
317
+ zoom = map_state['zoom']
318
+
319
+ m = folium.Map(
320
+ location=[center['lat'], center['lng']],
321
+ zoom_start=zoom,
322
+ tiles='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
323
+ attr='Esri',
324
+ width=width,
325
+ height=height
326
+ )
327
+
328
+ # Set the bounds
329
+ bounds = map_state['bounds']
330
+ m.fit_bounds([[bounds['south'], bounds['west']],
331
+ [bounds['north'], bounds['east']]])
332
+
333
+ # Add custom JavaScript to ensure correct zoom
334
+ m.get_root().html.add_child(folium.Element(f"""
335
+ <script>
336
+ document.addEventListener('DOMContentLoaded', function() {{
337
+ setTimeout(function() {{
338
+ var map = document.querySelector('#map');
339
+ if (map && map._leaflet_map) {{
340
+ map._leaflet_map.setView([{center['lat']}, {center['lng']}], {zoom});
341
+ }}
342
+ }}, 1000);
343
+ }});
344
+ </script>
345
+ """))
346
+
347
+ # Save the map
348
+ map_path = os.path.join(app.static_folder, 'temp_map.html')
349
+ m.save(map_path)
350
+
351
+ # Increase wait time to ensure map loads completely
352
+ time.sleep(1)
353
+
354
+ driver = setup_webdriver()
355
+ try:
356
+ driver.set_window_size(width + 50, height + 50) # Add padding to prevent scrollbars
357
+ map_url = f"http://localhost:{app.config['PORT']}/static/temp_map.html"
358
+ driver.get(map_url)
359
+
360
+ # Wait for map to load and settle
361
+ time.sleep(3)
362
+
363
+ # Take screenshot
364
+ driver.save_screenshot(filepath)
365
+
366
+ if polygon_points and len(polygon_points) >= 3:
367
+ # Create polygon cutout
368
+ img = Image.open(filepath)
369
+ mask = create_polygon_mask(img.size, polygon_points)
370
+
371
+ # Create cutout image
372
+ cutout = Image.new('RGBA', img.size, (0, 0, 0, 0))
373
+ cutout.paste(img, mask=mask)
374
+
375
+ # Save cutout
376
+ cutout_filename = f"cutout_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
377
+ cutout_filepath = os.path.join(SCREENSHOT_DIR, cutout_filename)
378
+ cutout.save(cutout_filepath)
379
+
380
+ return jsonify({
381
+ 'success': True,
382
+ 'screenshot_path': f'/static/screenshots/{filename}',
383
+ 'cutout_path': f'/static/screenshots/{cutout_filename}'
384
+ })
385
+
386
+ return jsonify({
387
+ 'success': True,
388
+ 'screenshot_path': f'/static/screenshots/{filename}'
389
+ })
390
+
391
+ finally:
392
+ driver.quit()
393
+
394
+ except Exception as e:
395
+ logging.error(f"Screenshot error: {str(e)}")
396
+ return jsonify({'error': str(e)}), 500
397
+
398
+ @app.route('/analyze')
399
+ def analyze():
400
+ try:
401
+ image_path = request.args.get('image')
402
+ if not image_path:
403
+ return "No image provided", 400
404
+
405
+ # Create masks directory if it doesn't exist
406
+ masks_dir = os.path.join(app.static_folder, 'masks')
407
+ os.makedirs(masks_dir, exist_ok=True)
408
+
409
+ # Clean up old mask files
410
+ for f in os.listdir(masks_dir):
411
+ if f.startswith(('mask_', 'segmented_')):
412
+ try:
413
+ os.remove(os.path.join(masks_dir, f))
414
+ except:
415
+ pass
416
+
417
+ # Clean up the image path
418
+ image_path = image_path.split('?')[0]
419
+ image_path = image_path.replace('/static/', '')
420
+ full_path = os.path.join(app.static_folder, image_path)
421
+
422
+ if not os.path.exists(full_path):
423
+ return f"Image file not found: {image_path}", 404
424
+
425
+ # Load and process image
426
+ image = Image.open(full_path)
427
+
428
+ # Ensure image is in RGB mode
429
+ if image.mode != 'RGB':
430
+ image = image.convert('RGB')
431
+
432
+ # Perform k-means segmentation
433
+ segmentation_results = kmeans_segmentation(image)
434
+
435
+ return render_template('analysis.html',
436
+ image_path=request.args.get('image').split('?')[0],
437
+ results=segmentation_results['percentages'],
438
+ masks=segmentation_results['masks'])
439
+
440
+ except Exception as e:
441
+ logging.error(f"Error processing image: {str(e)}")
442
+ return f"Error processing image: {str(e)}", 500
443
+
444
+ @app.route('/upload', methods=['POST'])
445
+ def upload_file():
446
+ if 'file' not in request.files:
447
+ return jsonify({'error': 'No file part'}), 400
448
+
449
+ file = request.files['file']
450
+ if file.filename == '':
451
+ return jsonify({'error': 'No selected file'}), 400
452
+
453
+ if file and allowed_file(file.filename):
454
+ filename = secure_filename(file.filename)
455
+ unique_filename = f"{uuid.uuid4().hex}_{filename}"
456
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
457
+ file.save(filepath)
458
+
459
+ return jsonify({
460
+ 'success': True,
461
+ 'filepath': f'/static/uploads/{unique_filename}'
462
+ })
463
+
464
+ return jsonify({'error': 'Invalid file type'}), 400
465
+
466
+ if __name__ == '__main__':
467
+ port = 5000
468
+ app.config['PORT'] = port
469
+ app.run(debug=True, port=port)
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ flask>=2.0.1
2
+ geopy>=2.2.0
3
+ folium>=0.12.1
4
+ selenium>=4.0.0
5
+ pillow>=9.0.0
6
+ webdriver_manager>=3.8.0
7
+ #python 3.9
8
+ numpy>=1.23.5