| import os |
| import gradio as gr |
| import folium |
| from folium import plugins |
| import geopandas as gpd |
| import rasterio |
| from rasterio.warp import transform_bounds |
| import json |
| import tempfile |
| import shutil |
| import uuid |
| import logging |
| import traceback |
| import numpy as np |
| from PIL import Image |
|
|
| |
| logging.basicConfig( |
| level=logging.INFO, |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', |
| handlers=[logging.StreamHandler()] |
| ) |
| logger = logging.getLogger('forestai') |
|
|
| |
| |
| |
|
|
| |
| FEATURE_STYLES = { |
| 'trees': {"color": "yellow", "fillColor": "yellow", "fillOpacity": 0.3, "weight": 2} |
| } |
|
|
| |
| EXAMPLE_FILE_PATH = "example.tif" |
|
|
| |
| |
| |
|
|
| def setup_temp_dirs(): |
| """Create temporary directories.""" |
| temp_base = tempfile.mkdtemp(prefix="forestai_") |
| dirs = { |
| 'uploads': os.path.join(temp_base, 'uploads'), |
| 'processed': os.path.join(temp_base, 'processed'), |
| 'static': os.path.join(temp_base, 'static') |
| } |
| |
| for dir_path in dirs.values(): |
| os.makedirs(dir_path, exist_ok=True) |
| |
| return dirs |
|
|
| |
| TEMP_DIRS = setup_temp_dirs() |
|
|
| |
| |
| |
|
|
| def get_bounds_from_geotiff(geotiff_path): |
| """Extract bounds from GeoTIFF and convert to WGS84.""" |
| try: |
| with rasterio.open(geotiff_path) as src: |
| bounds = src.bounds |
| if src.crs: |
| west, south, east, north = transform_bounds( |
| src.crs, 'EPSG:4326', |
| bounds.left, bounds.bottom, bounds.right, bounds.top |
| ) |
| return west, south, east, north |
| else: |
| return -74.1, 40.6, -73.9, 40.8 |
| except Exception as e: |
| logger.error(f"Error extracting bounds: {str(e)}") |
| return -74.1, 40.6, -73.9, 40.8 |
|
|
| def create_split_view_map(geojson_data, bounds): |
| """Create split-view map with detected trees.""" |
| try: |
| west, south, east, north = bounds |
| center = [(south + north) / 2, (west + east) / 2] |
| |
| |
| lat_diff = north - south |
| lon_diff = east - west |
| max_diff = max(lat_diff, lon_diff) |
| |
| if max_diff < 0.01: |
| zoom = 16 |
| elif max_diff < 0.05: |
| zoom = 14 |
| elif max_diff < 0.1: |
| zoom = 12 |
| else: |
| zoom = 10 |
|
|
| |
| m = folium.Map(location=center, zoom_start=zoom) |
|
|
| |
| left_layer = folium.TileLayer( |
| tiles='OpenStreetMap', |
| name='OpenStreetMap', |
| overlay=False, |
| control=False |
| ) |
| |
| right_layer = folium.TileLayer( |
| tiles='https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', |
| attr='Google Satellite', |
| name='Google Satellite', |
| overlay=False, |
| control=False |
| ) |
|
|
| left_layer.add_to(m) |
| right_layer.add_to(m) |
|
|
| |
| if geojson_data and 'features' in geojson_data and geojson_data['features']: |
| style = FEATURE_STYLES['trees'] |
| |
| geojson_layer = folium.GeoJson( |
| geojson_data, |
| name='Detected Trees', |
| style_function=lambda x: style, |
| popup=folium.GeoJsonPopup( |
| fields=['confidence'] if 'confidence' in str(geojson_data) else [], |
| aliases=['Confidence:'] if 'confidence' in str(geojson_data) else [], |
| localize=True |
| ) |
| ) |
| geojson_layer.add_to(m) |
|
|
| |
| folium.Rectangle( |
| bounds=[[south, west], [north, east]], |
| color='red', |
| weight=3, |
| fill=False |
| ).add_to(m) |
|
|
| |
| plugins.SideBySideLayers( |
| layer_left=left_layer, |
| layer_right=right_layer |
| ).add_to(m) |
|
|
| |
| folium.LayerControl().add_to(m) |
|
|
| |
| m.fit_bounds([[south, west], [north, east]], padding=(20, 20)) |
|
|
| return m |
|
|
| except Exception as e: |
| logger.error(f"Error creating map: {str(e)}") |
| |
| m = folium.Map(location=[40.7, -74.0], zoom_start=10) |
| return m |
|
|
| def process_geotiff_file(geotiff_file): |
| """Process uploaded GeoTIFF file for tree detection.""" |
| if geotiff_file is None: |
| return None, "Please upload a GeoTIFF file or use the example file" |
|
|
| try: |
| |
| unique_id = str(uuid.uuid4().hex)[:8] |
| |
| |
| if hasattr(geotiff_file, 'name'): |
| filename = os.path.basename(geotiff_file.name) |
| else: |
| filename = os.path.basename(geotiff_file) |
|
|
| |
| geotiff_path = os.path.join(TEMP_DIRS['uploads'], f"{unique_id}_{filename}") |
|
|
| if hasattr(geotiff_file, 'read'): |
| file_content = geotiff_file.read() |
| with open(geotiff_path, "wb") as f: |
| f.write(file_content) |
| else: |
| shutil.copy(geotiff_file, geotiff_path) |
|
|
| logger.info(f"File saved to {geotiff_path}") |
|
|
| |
| from utils.advanced_extraction import extract_features_from_geotiff |
| |
| logger.info("Extracting tree features...") |
| geojson_data = extract_features_from_geotiff(geotiff_path, TEMP_DIRS['processed'], "trees") |
|
|
| if not geojson_data or not geojson_data.get('features'): |
| return None, "No trees detected in the image" |
|
|
| |
| bounds = get_bounds_from_geotiff(geotiff_path) |
| map_obj = create_split_view_map(geojson_data, bounds) |
|
|
| if map_obj: |
| |
| html_path = os.path.join(TEMP_DIRS['static'], f"map_{unique_id}.html") |
| map_obj.save(html_path) |
|
|
| |
| with open(html_path, 'r', encoding='utf-8') as f: |
| html_content = f.read() |
|
|
| |
| iframe_html = f''' |
| <div style="width:100%; height:600px; border:1px solid #ddd; border-radius:8px; overflow:hidden;"> |
| <iframe srcdoc="{html_content.replace('"', '"')}" |
| width="100%" height="600px" style="border:none;"></iframe> |
| </div> |
| ''' |
|
|
| num_features = len(geojson_data['features']) |
| return iframe_html, f"β
Detected {num_features} tree areas in {filename}" |
| else: |
| return None, "Failed to create map" |
| |
| except Exception as e: |
| logger.error(f"Error processing file: {str(e)}") |
| return None, f"β Error: {str(e)}" |
|
|
| def load_example_file(): |
| """Load the example.tif file and return it for processing.""" |
| try: |
| if os.path.exists(EXAMPLE_FILE_PATH): |
| logger.info("Loading example file...") |
| return EXAMPLE_FILE_PATH |
| else: |
| logger.warning("Example file not found") |
| return None |
| except Exception as e: |
| logger.error(f"Error loading example file: {str(e)}") |
| return None |
|
|
| def process_example_file(): |
| """Process the example file and return results.""" |
| example_file = load_example_file() |
| if example_file: |
| return process_geotiff_file(example_file) |
| else: |
| return None, "β Example file (example.tif) not found in the root directory" |
|
|
| def check_example_file_exists(): |
| """Check if example file exists and return appropriate message.""" |
| if os.path.exists(EXAMPLE_FILE_PATH): |
| return f"β
Example file found: {EXAMPLE_FILE_PATH}" |
| else: |
| return f"β οΈ Example file not found: {EXAMPLE_FILE_PATH}" |
|
|
| |
| |
| |
|
|
| def create_gradio_interface(): |
| """Create the Gradio interface for tree detection.""" |
| |
| css = """ |
| .gradio-container { |
| max-width: 100% !important; |
| width: 100% !important; |
| margin: 0 !important; |
| padding: 10px !important; |
| } |
| .map-container { |
| border-radius: 8px; |
| overflow: hidden; |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
| width: 100% !important; |
| } |
| body { |
| margin: 0 !important; |
| padding: 0 !important; |
| } |
| .contain { |
| max-width: none !important; |
| padding: 0 !important; |
| } |
| .example-button { |
| background: linear-gradient(135deg, #28a745 0%, #20c997 100%) !important; |
| border: none !important; |
| color: white !important; |
| } |
| """ |
| |
| with gr.Blocks(title="π² ForestAI - Tree Detection", css=css, theme=gr.themes.Soft()) as app: |
| |
| |
| gr.HTML(""" |
| <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; margin-bottom: 20px;"> |
| <h1 style="color: white; margin: 0; font-size: 2.5em;">π² ForestAI</h1> |
| <p style="color: white; margin: 10px 0 0 0; font-size: 1.2em;">Tree Detection from Satellite Imagery</p> |
| </div> |
| """) |
|
|
| with gr.Row(): |
| with gr.Column(scale=1): |
| gr.Markdown("### Upload GeoTIFF File") |
| |
| file_input = gr.File( |
| label="Select GeoTIFF File", |
| file_types=[".tif", ".tiff"], |
| type="filepath" |
| ) |
| |
| with gr.Row(): |
| analyze_btn = gr.Button( |
| "π Detect Trees", |
| variant="primary", |
| size="lg", |
| scale=2 |
| ) |
| |
| example_btn = gr.Button( |
| "π Use Example File", |
| variant="secondary", |
| size="lg", |
| scale=1, |
| elem_classes=["example-button"] |
| ) |
| |
| |
| example_status = gr.Textbox( |
| label="Example File Status", |
| value=check_example_file_exists(), |
| interactive=False, |
| lines=1 |
| ) |
| |
| gr.Markdown("### Status") |
| status_output = gr.Textbox( |
| label="Processing Status", |
| interactive=False, |
| placeholder="Upload a file and click 'Detect Trees' or use the example file...", |
| lines=3 |
| ) |
|
|
| with gr.Column(scale=2): |
| gr.Markdown("### Results Map") |
| |
| map_output = gr.HTML( |
| value=''' |
| <div style="width:100%; height:600px; border:1px solid #ddd; border-radius:8px; |
| display:flex; align-items:center; justify-content:center; |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);"> |
| <div style="text-align:center; color:#666;"> |
| <h3>π² Upload a GeoTIFF file or use example to see detected trees</h3> |
| <p>Interactive map will appear here</p> |
| </div> |
| </div> |
| ''', |
| elem_classes=["map-container"] |
| ) |
|
|
| |
| analyze_btn.click( |
| fn=process_geotiff_file, |
| inputs=[file_input], |
| outputs=[map_output, status_output], |
| show_progress=True |
| ) |
| |
| example_btn.click( |
| fn=process_example_file, |
| inputs=[], |
| outputs=[map_output, status_output], |
| show_progress=True |
| ) |
|
|
| |
| gr.Markdown(""" |
| ### How to Use: |
| 1. **Upload** a GeoTIFF satellite image file OR click "Use Example File" to try with the included sample |
| 2. **Click** "Detect Trees" to analyze your uploaded image |
| 3. **Explore** the interactive map with detected tree areas |
| 4. **Use** the split-view slider to compare base map and satellite imagery |
| |
| ### Map Controls: |
| - **Split View**: Drag the vertical slider to compare layers |
| - **Zoom**: Scroll to zoom in/out, drag to pan |
| - **Layers**: Use layer control to toggle trees on/off |
| |
| ### Example File: |
| - The example file should be named `example.tif` and placed in the same directory as this application |
| - Click "Use Example File" to quickly test the tree detection without uploading your own file |
| """) |
|
|
| return app |
|
|
| if __name__ == "__main__": |
| logger.info("π² Starting ForestAI Tree Detection") |
| app = create_gradio_interface() |
| app.launch() |