import os import json import numpy as np import cv2 import gradio as gr import tensorflow as tf from tensorflow.keras.models import load_model from groq import Groq # ========================================================= # CONFIGURATION # ========================================================= MODEL_PATH = "geoai_clean_final_best.keras" classes = ['AnnualCrop', 'Forest', 'HerbaceousVegetation', 'Highway', 'Industrial', 'Pasture', 'PermanentCrop', 'Residential', 'River', 'SeaLake'] geo_knowledge_base = {'AnnualCrop': {'land_cover_type': 'Agricultural Land', 'description': 'Land primarily used for seasonal crop cultivation and annual agricultural production.', 'characteristics': ['Regular field patterns', 'Seasonal vegetation cycles', 'Managed agricultural activity'], 'environmental_significance': 'Important for food production, crop monitoring, and agricultural land-use assessment.', 'applications': ['Crop monitoring', 'Agricultural planning', 'Land-use assessment']}, 'Forest': {'land_cover_type': 'Natural Vegetation', 'description': 'Land dominated by dense tree cover and forest vegetation.', 'characteristics': ['Dense vegetation', 'Continuous tree canopy', 'High biomass coverage'], 'environmental_significance': 'Important for biodiversity, carbon storage, and ecosystem monitoring.', 'applications': ['Deforestation monitoring', 'Biodiversity assessment', 'Environmental conservation']}, 'HerbaceousVegetation': {'land_cover_type': 'Natural Vegetation', 'description': 'Land dominated by grasses, herbs, and other non-woody vegetation.', 'characteristics': ['Low-growing vegetation', 'Grass-dominated surface', 'Limited tree cover'], 'environmental_significance': 'Important for habitat assessment, vegetation monitoring, and ecosystem analysis.', 'applications': ['Vegetation monitoring', 'Habitat assessment', 'Land-cover mapping']}, 'Highway': {'land_cover_type': 'Transportation Infrastructure', 'description': 'Land containing major road networks and associated transportation infrastructure.', 'characteristics': ['Linear road structures', 'Connected transport routes', 'Artificial surfaces'], 'environmental_significance': 'Useful for infrastructure mapping, urban expansion studies, and accessibility analysis.', 'applications': ['Road network monitoring', 'Infrastructure planning', 'Urban development analysis']}, 'Industrial': {'land_cover_type': 'Built-up Land', 'description': 'Land dominated by factories, warehouses, and large industrial infrastructure.', 'characteristics': ['Large artificial structures', 'Dense built surfaces', 'Industrial facilities'], 'environmental_significance': 'Relevant for urban growth, industrial development, and environmental impact monitoring.', 'applications': ['Industrial mapping', 'Urban planning', 'Environmental impact assessment']}, 'Pasture': {'land_cover_type': 'Agricultural Grassland', 'description': 'Grass-covered land primarily used for livestock grazing.', 'characteristics': ['Open grassland', 'Low vegetation', 'Agricultural land management'], 'environmental_significance': 'Important for livestock management and agricultural land-use monitoring.', 'applications': ['Grazing land assessment', 'Agricultural planning', 'Grassland monitoring']}, 'PermanentCrop': {'land_cover_type': 'Permanent Agricultural Land', 'description': 'Land used for long-term crops such as orchards, vineyards, and plantations.', 'characteristics': ['Persistent crop patterns', 'Organized planting structures', 'Long-term cultivation'], 'environmental_significance': 'Useful for monitoring permanent agricultural systems and long-term land management.', 'applications': ['Plantation monitoring', 'Agricultural mapping', 'Land management']}, 'Residential': {'land_cover_type': 'Built-up Land', 'description': 'Land dominated by houses, buildings, and residential infrastructure.', 'characteristics': ['Dense building patterns', 'Road networks', 'Urban housing structures'], 'environmental_significance': 'Important for urban growth analysis, population-related planning, and land-use monitoring.', 'applications': ['Urban planning', 'Settlement mapping', 'Urban expansion monitoring']}, 'River': {'land_cover_type': 'Water Body', 'description': 'A natural flowing water system visible within the landscape.', 'characteristics': ['Linear water features', 'Continuous flow patterns', 'Natural drainage structures'], 'environmental_significance': 'Important for water-resource management, flood assessment, and ecosystem monitoring.', 'applications': ['Flood monitoring', 'Water-resource assessment', 'Environmental monitoring']}, 'SeaLake': {'land_cover_type': 'Water Body', 'description': 'A large natural or artificial body of standing water.', 'characteristics': ['Large continuous water surface', 'Low surface texture', 'Distinct land-water boundaries'], 'environmental_significance': 'Important for water-resource monitoring and aquatic ecosystem assessment.', 'applications': ['Water-body monitoring', 'Environmental assessment', 'Coastal and lake analysis']}} # ========================================================= # LOAD FINAL TRAINED MODEL # ========================================================= print("Loading GeoAI model...") xai_model = load_model( MODEL_PATH, compile=False ) print("GeoAI model loaded successfully.") # ========================================================= # GROQ LLM CONNECTION # ========================================================= GROQ_API_KEY = os.getenv("GROQ_API_KEY") if not GROQ_API_KEY: print( "WARNING: GROQ_API_KEY is not configured. " "Image classification will work, but LLM features " "require the Hugging Face Space secret." ) groq_client = Groq( api_key=GROQ_API_KEY or "missing-key" ) # ========================================================= # EXACT WORKING GEOAI FUNCTIONS # ========================================================= def make_gradcam_heatmap(img_array, model, backbone_name="mobilenetv2_1.00_96"): # Get nested MobileNetV2 backbone backbone = model.get_layer(backbone_name) # Model that returns final backbone feature maps backbone_feature_model = tf.keras.Model( inputs=backbone.input, outputs=backbone.get_layer("out_relu").output ) # Get preprocessing layers from full model augmentation_layer = model.get_layer("data_augmentation") resizing_layer = model.get_layer("resizing_1") rescaling_layer = model.get_layer("rescaling_2") # Get classification head layers gap_layer = model.get_layer("global_average_pooling2d_2") dropout_1 = model.get_layer("dropout_8") dense_layer = model.get_layer("dense_12") dropout_2 = model.get_layer("dropout_9") output_layer = model.get_layer("dense_13") with tf.GradientTape() as tape: # Same preprocessing path as the final model x = augmentation_layer(img_array, training=False) x = resizing_layer(x) x = rescaling_layer(x) # Extract final convolutional feature maps conv_outputs = backbone_feature_model(x, training=False) tape.watch(conv_outputs) # Same classification path as the final model predictions = gap_layer(conv_outputs) predictions = dropout_1(predictions, training=False) predictions = dense_layer(predictions) predictions = dropout_2(predictions, training=False) predictions = output_layer(predictions) predicted_index = tf.argmax(predictions[0]) class_score = predictions[:, predicted_index] # Gradient of predicted class with respect to feature maps gradients = tape.gradient(class_score, conv_outputs) # Average gradients across spatial dimensions pooled_gradients = tf.reduce_mean( gradients, axis=(0, 1, 2) ) # Weight feature maps by importance conv_outputs = conv_outputs[0] heatmap = tf.reduce_sum( conv_outputs * pooled_gradients, axis=-1 ) # Keep positive influence only heatmap = tf.maximum(heatmap, 0) # Normalize to 0–1 max_value = tf.reduce_max(heatmap) heatmap = tf.where( max_value > 0, heatmap / max_value, heatmap ) return heatmap.numpy() def analyze_satellite_image(image_array, model=xai_model): # Ensure image is float32 and normalized to 0–1 image_array = np.asarray(image_array, dtype=np.float32) if image_array.max() > 1.0: image_array = image_array / 255.0 # Ensure correct size if image_array.shape[:2] != (64, 64): image_array = cv2.resize( image_array, (64, 64) ) # Ensure RGB if image_array.ndim == 2: image_array = cv2.cvtColor( image_array, cv2.COLOR_GRAY2RGB ) if image_array.shape[-1] == 4: image_array = image_array[:, :, :3] # Add batch dimension img_batch = np.expand_dims( image_array, axis=0 ) # Prediction probabilities = model.predict( img_batch, verbose=0 )[0] predicted_index = int( np.argmax(probabilities) ) predicted_class = classes[ predicted_index ] confidence = float( probabilities[predicted_index] * 100 ) # Top 3 predictions top3_indices = np.argsort( probabilities )[-3:][::-1] top3_predictions = [ { "class": classes[i], "confidence": float( probabilities[i] * 100 ) } for i in top3_indices ] # Generate Grad-CAM heatmap = make_gradcam_heatmap( img_batch, model ) heatmap_resized = cv2.resize( heatmap, (64, 64) ) # Colored heatmap heatmap_uint8 = np.uint8( 255 * heatmap_resized ) heatmap_colored = cv2.applyColorMap( heatmap_uint8, cv2.COLORMAP_JET ) heatmap_colored = cv2.cvtColor( heatmap_colored, cv2.COLOR_BGR2RGB ) # Original image original_uint8 = np.uint8( np.clip( image_array * 255, 0, 255 ) ) # Grad-CAM overlay overlay = cv2.addWeighted( original_uint8, 0.6, heatmap_colored, 0.4, 0 ) # Confidence interpretation if confidence >= 90: confidence_level = "High" elif confidence >= 70: confidence_level = "Moderate" else: confidence_level = "Low" # Structured GeoAI output result = { "predicted_class": predicted_class, "confidence": confidence, "confidence_level": confidence_level, "top3_predictions": top3_predictions } return result, heatmap_resized, overlay def create_structured_geo_analysis(model_result): predicted_class = model_result["predicted_class"] knowledge = geo_knowledge_base[predicted_class] # Reliability statement confidence = model_result["confidence"] if confidence >= 90: reliability = ( "The model shows strong confidence in this prediction." ) elif confidence >= 70: reliability = ( "The prediction is reasonably confident, but alternative " "land-cover classes should also be considered." ) else: reliability = ( "The prediction has low confidence and should be interpreted " "carefully alongside the alternative predictions." ) structured_analysis = { "prediction": { "land_cover_class": predicted_class, "confidence_percentage": round(confidence, 2), "confidence_level": model_result["confidence_level"] }, "geospatial_information": { "land_cover_type": knowledge["land_cover_type"], "description": knowledge["description"], "characteristics": knowledge["characteristics"], "environmental_significance": knowledge["environmental_significance"], "potential_applications": knowledge["applications"] }, "alternative_predictions": [ { "class": item["class"], "confidence_percentage": round(item["confidence"], 2) } for item in model_result["top3_predictions"][1:] ], "explainability": { "method": "Grad-CAM", "interpretation": ( "The highlighted regions in the Grad-CAM visualization " "represent areas that most influenced the model's prediction." ), "spatial_resolution_note": ( "The explanation identifies broad influential regions " "rather than exact object boundaries." ) }, "reliability_assessment": reliability } return structured_analysis def ask_geoai_llm(question, structured_output): # Convert verified GeoAI output into JSON context geo_context = json.dumps( structured_output, indent=2 ) system_prompt = """ You are the natural-language reasoning component of a GeoAI system. You receive verified structured information produced by: 1. A satellite-image classification model 2. A confidence analysis module 3. A Grad-CAM explainability module 4. A geospatial knowledge base Rules: - Answer using only the supplied GeoAI context. - Do not invent geographic locations, coordinates, objects, or facts. - Do not change the model's predicted class or confidence. - If the requested information is unavailable, clearly say so. - Treat Grad-CAM as broad model attention, not exact segmentation. - Keep answers clear and concise. """ user_prompt = f""" VERIFIED GEOAI CONTEXT: {geo_context} USER QUESTION: {question} Answer the user's question using only the verified context above. """ response = groq_client.chat.completions.create( model="openai/gpt-oss-20b", messages=[ { "role": "system", "content": system_prompt }, { "role": "user", "content": user_prompt } ], temperature=0.2, max_completion_tokens=300 ) return response.choices[0].message.content def gradio_ask_question(question, structured_state): if not question or not question.strip(): return "Please enter a question." if not structured_state: return "Please upload and analyze a satellite image first." answer = ask_geoai_llm( question, structured_state ) return answer def premium_analyze_image(uploaded_image): if uploaded_image is None: raise gr.Error( "Upload a satellite image before starting the analysis." ) image_array = np.asarray( uploaded_image, dtype=np.float32 ) if image_array.max() > 1.0: image_array = image_array / 255.0 # Core GeoAI analysis model_result, heatmap, overlay = analyze_satellite_image( image_array, model=xai_model ) # Structured geospatial intelligence structured_output = create_structured_geo_analysis( model_result ) # Detailed LLM report initial_question = ( "Explain what this image shows, how reliable the result is, " "and what the analysis can be used for." ) llm_explanation = ask_geoai_llm( initial_question, structured_output ) predicted_class = model_result["predicted_class"] confidence = model_result["confidence"] confidence_level = model_result["confidence_level"] # Clean display name display_class = ( predicted_class .replace("HerbaceousVegetation", "Herbaceous Vegetation") .replace("PermanentCrop", "Permanent Crop") .replace("AnnualCrop", "Annual Crop") .replace("SeaLake", "Sea / Lake") ) # Hero prediction card prediction_summary = f"""
PRIMARY CLASSIFICATION
{display_class}
{confidence:.2f}% confidence {confidence_level} certainty
""" # Top-3 scores top3_scores = { item["class"]: item["confidence"] / 100 for item in model_result["top3_predictions"] } # Compact model metadata analysis_meta = f"""
MODEL Fine-tuned MobileNetV2
EXPLAINABILITY Grad-CAM
CONFIDENCE {confidence_level}
""" return ( prediction_summary, uploaded_image, overlay, top3_scores, llm_explanation, analysis_meta, structured_output ) # ========================================================= # EXACT PREMIUM DESIGN SYSTEM # ========================================================= premium_css = '\n/* =========================================================\n GEO-INTELLIGENT AI — PREMIUM DESIGN SYSTEM\n ========================================================= */\n\n:root {\n --bg-0: #05080d;\n --bg-1: #081019;\n --bg-2: #0b1520;\n --surface: rgba(12, 23, 34, 0.82);\n --surface-strong: rgba(14, 27, 40, 0.96);\n --surface-soft: rgba(255, 255, 255, 0.035);\n\n --border: rgba(148, 184, 207, 0.14);\n --border-hover: rgba(77, 226, 183, 0.34);\n\n --text-1: #f4f8fb;\n --text-2: #a9bac7;\n --text-3: #708695;\n\n --accent: #4de2b7;\n --accent-2: #54b9ff;\n --accent-soft: rgba(77, 226, 183, 0.12);\n\n --shadow-lg:\n 0 30px 80px rgba(0, 0, 0, 0.38),\n 0 1px 0 rgba(255, 255, 255, 0.03) inset;\n\n --radius-xl: 28px;\n --radius-lg: 22px;\n --radius-md: 16px;\n}\n\n\n/* ---------- GLOBAL ---------- */\n\n.gradio-container {\n max-width: 1500px !important;\n margin: 0 auto !important;\n padding: 0 28px 60px !important;\n background:\n radial-gradient(\n circle at 12% 8%,\n rgba(39, 188, 152, 0.10),\n transparent 28%\n ),\n radial-gradient(\n circle at 88% 12%,\n rgba(61, 145, 218, 0.10),\n transparent 26%\n ),\n linear-gradient(\n 180deg,\n var(--bg-0) 0%,\n var(--bg-1) 48%,\n #060b11 100%\n ) !important;\n color: var(--text-1) !important;\n min-height: 100vh;\n}\n\nbody {\n background: var(--bg-0) !important;\n}\n\n\n/* ---------- TOP NAV ---------- */\n\n.top-nav {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 24px 4px 18px;\n}\n\n.brand-lockup {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.brand-mark {\n width: 42px;\n height: 42px;\n display: grid;\n place-items: center;\n border-radius: 13px;\n background:\n linear-gradient(\n 135deg,\n rgba(77, 226, 183, 0.22),\n rgba(84, 185, 255, 0.14)\n );\n border: 1px solid rgba(77, 226, 183, 0.24);\n font-size: 21px;\n box-shadow: 0 10px 30px rgba(77, 226, 183, 0.08);\n}\n\n.brand-name {\n font-size: 16px;\n font-weight: 750;\n letter-spacing: -0.02em;\n}\n\n.brand-sub {\n color: var(--text-3);\n font-size: 11px;\n margin-top: 2px;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.system-status {\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 9px 13px;\n border-radius: 999px;\n border: 1px solid var(--border);\n background: rgba(255,255,255,0.025);\n color: var(--text-2);\n font-size: 12px;\n}\n\n.status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--accent);\n box-shadow: 0 0 14px rgba(77, 226, 183, 0.75);\n}\n\n\n/* ---------- HERO ---------- */\n\n.hero {\n position: relative;\n overflow: hidden;\n padding: 74px 38px 68px;\n text-align: center;\n border: 1px solid var(--border);\n border-radius: 32px;\n background:\n linear-gradient(\n 135deg,\n rgba(77, 226, 183, 0.055),\n transparent 35%\n ),\n linear-gradient(\n 225deg,\n rgba(84, 185, 255, 0.055),\n transparent 38%\n ),\n rgba(8, 16, 25, 0.76);\n box-shadow: var(--shadow-lg);\n}\n\n.hero::before {\n content: "";\n position: absolute;\n width: 440px;\n height: 440px;\n left: 50%;\n top: -340px;\n transform: translateX(-50%);\n border-radius: 50%;\n background: rgba(77, 226, 183, 0.13);\n filter: blur(80px);\n pointer-events: none;\n}\n\n.hero-kicker {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 8px 13px;\n border-radius: 999px;\n border: 1px solid rgba(77, 226, 183, 0.18);\n background: rgba(77, 226, 183, 0.07);\n color: #8af0d2;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n}\n\n.hero h1 {\n margin: 24px auto 16px !important;\n max-width: 950px;\n color: var(--text-1) !important;\n font-size: clamp(42px, 6vw, 76px) !important;\n line-height: 0.98 !important;\n letter-spacing: -0.055em !important;\n font-weight: 800 !important;\n}\n\n.hero-gradient {\n background:\n linear-gradient(\n 90deg,\n #ffffff 0%,\n #8cf2d5 46%,\n #74c9ff 100%\n );\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n}\n\n.hero-copy {\n max-width: 730px;\n margin: 0 auto;\n color: var(--text-2);\n font-size: 17px;\n line-height: 1.75;\n}\n\n.capability-row {\n margin-top: 28px;\n display: flex;\n justify-content: center;\n gap: 10px;\n flex-wrap: wrap;\n}\n\n.capability-chip {\n padding: 9px 13px;\n border-radius: 999px;\n color: var(--text-2);\n background: rgba(255,255,255,0.035);\n border: 1px solid var(--border);\n font-size: 12px;\n}\n\n\n/* ---------- SECTION SYSTEM ---------- */\n\n.section-wrap {\n margin-top: 28px;\n}\n\n.section-heading {\n margin: 0 0 14px 4px;\n}\n\n.section-eyebrow {\n color: var(--accent);\n font-size: 11px;\n font-weight: 750;\n letter-spacing: 0.13em;\n text-transform: uppercase;\n}\n\n.section-title {\n margin-top: 7px;\n color: var(--text-1);\n font-size: 24px;\n font-weight: 750;\n letter-spacing: -0.035em;\n}\n\n.section-copy {\n margin-top: 7px;\n color: var(--text-3);\n font-size: 13px;\n}\n\n\n/* ---------- PREMIUM CARDS ---------- */\n\n.premium-card {\n border: 1px solid var(--border) !important;\n border-radius: var(--radius-xl) !important;\n background:\n linear-gradient(\n 145deg,\n rgba(255,255,255,0.025),\n transparent 42%\n ),\n var(--surface) !important;\n box-shadow: var(--shadow-lg);\n overflow: hidden;\n}\n\n.card-pad {\n padding: 22px;\n}\n\n\n/* ---------- UPLOAD ---------- */\n\n.upload-zone {\n min-height: 430px;\n}\n\n.upload-zone .image-container {\n border-radius: 20px !important;\n overflow: hidden !important;\n}\n\n.upload-zone [data-testid="image"] {\n min-height: 360px !important;\n}\n\n.upload-hint {\n margin-top: 14px;\n display: flex;\n gap: 8px;\n color: var(--text-3);\n font-size: 12px;\n}\n\n\n/* ---------- BUTTONS ---------- */\n\n.primary-action {\n margin-top: 14px !important;\n min-height: 54px !important;\n border: 0 !important;\n border-radius: 16px !important;\n background:\n linear-gradient(\n 110deg,\n #38cfa5,\n #47aef2\n ) !important;\n color: #03110d !important;\n font-weight: 800 !important;\n letter-spacing: -0.01em;\n box-shadow:\n 0 16px 34px rgba(55, 205, 163, 0.16) !important;\n transition:\n transform 180ms ease,\n box-shadow 180ms ease,\n filter 180ms ease !important;\n}\n\n.primary-action:hover {\n transform: translateY(-2px);\n filter: brightness(1.05);\n box-shadow:\n 0 20px 42px rgba(55, 205, 163, 0.23) !important;\n}\n\n.secondary-action {\n min-height: 48px !important;\n border-radius: 14px !important;\n border: 1px solid rgba(77, 226, 183, 0.24) !important;\n background: rgba(77, 226, 183, 0.08) !important;\n color: #9af4d9 !important;\n font-weight: 700 !important;\n}\n\n\n/* ---------- PREDICTION HERO ---------- */\n\n.prediction-card {\n position: relative;\n padding: 28px;\n min-height: 188px;\n border-radius: 24px;\n border: 1px solid rgba(77, 226, 183, 0.19);\n background:\n radial-gradient(\n circle at 92% 5%,\n rgba(84, 185, 255, 0.14),\n transparent 32%\n ),\n linear-gradient(\n 135deg,\n rgba(77, 226, 183, 0.10),\n rgba(255,255,255,0.018)\n );\n}\n\n.prediction-eyebrow {\n color: #83eacc;\n font-size: 10px;\n font-weight: 800;\n letter-spacing: 0.15em;\n}\n\n.prediction-name {\n margin-top: 16px;\n color: white;\n font-size: clamp(30px, 4vw, 48px);\n font-weight: 800;\n line-height: 1;\n letter-spacing: -0.05em;\n}\n\n.prediction-meta {\n margin-top: 22px;\n display: flex;\n gap: 9px;\n flex-wrap: wrap;\n}\n\n.confidence-pill,\n.level-pill {\n display: inline-flex;\n padding: 8px 11px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n}\n\n.confidence-pill {\n color: #9af4d9;\n background: rgba(77, 226, 183, 0.10);\n border: 1px solid rgba(77, 226, 183, 0.18);\n}\n\n.level-pill {\n color: #a8d8ff;\n background: rgba(84, 185, 255, 0.09);\n border: 1px solid rgba(84, 185, 255, 0.17);\n}\n\n\n/* ---------- IMAGE COMPARISON ---------- */\n\n.visual-panel [data-testid="image"] {\n min-height: 390px !important;\n}\n\n.visual-panel img {\n object-fit: contain !important;\n border-radius: 18px !important;\n}\n\n.panel-label {\n color: var(--text-2);\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.02em;\n}\n\n\n/* ---------- META GRID ---------- */\n\n.meta-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 10px;\n}\n\n.meta-item {\n min-height: 84px;\n padding: 15px;\n border-radius: 16px;\n background: rgba(255,255,255,0.025);\n border: 1px solid var(--border);\n}\n\n.meta-label {\n display: block;\n margin-bottom: 9px;\n color: var(--text-3);\n font-size: 9px;\n font-weight: 800;\n letter-spacing: 0.13em;\n}\n\n.meta-item strong {\n color: var(--text-1);\n font-size: 12px;\n}\n\n\n/* ---------- AI REPORT ---------- */\n\n.ai-report {\n padding: 10px 4px;\n}\n\n.ai-report h1,\n.ai-report h2,\n.ai-report h3 {\n color: var(--text-1) !important;\n letter-spacing: -0.03em;\n}\n\n.ai-report p,\n.ai-report li {\n color: #c1ced7 !important;\n font-size: 15px !important;\n line-height: 1.8 !important;\n}\n\n.ai-report strong {\n color: #eef8f5 !important;\n}\n\n\n/* ---------- COPILOT ---------- */\n\n.copilot-shell {\n border: 1px solid rgba(84, 185, 255, 0.16) !important;\n border-radius: 28px !important;\n background:\n radial-gradient(\n circle at 100% 0%,\n rgba(84, 185, 255, 0.08),\n transparent 32%\n ),\n var(--surface) !important;\n box-shadow: var(--shadow-lg);\n}\n\n.prompt-row {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin: 4px 0 14px;\n}\n\n.prompt-chip {\n padding: 8px 11px;\n border-radius: 999px;\n border: 1px solid var(--border);\n background: rgba(255,255,255,0.025);\n color: var(--text-2);\n font-size: 11px;\n}\n\n.copilot-input textarea {\n min-height: 72px !important;\n border-radius: 16px !important;\n}\n\n.copilot-answer {\n min-height: 100px;\n padding: 4px;\n}\n\n\n/* ---------- LABEL / CONFIDENCE PANEL ---------- */\n\n.confidence-panel {\n border-radius: 22px !important;\n}\n\n.confidence-panel .label-wrap {\n background: transparent !important;\n}\n\n\n/* ---------- FOOTER ---------- */\n\n.app-footer {\n margin-top: 34px;\n padding: 22px 4px 4px;\n display: flex;\n justify-content: space-between;\n gap: 20px;\n border-top: 1px solid var(--border);\n color: var(--text-3);\n font-size: 11px;\n}\n\n\n/* ---------- GRADIO CLEANUP ---------- */\n\nfooter {\n display: none !important;\n}\n\nbutton,\ntextarea,\ninput {\n transition: all 180ms ease !important;\n}\n\n.block {\n border-color: var(--border) !important;\n}\n\nlabel span {\n color: var(--text-2) !important;\n}\n\n\n/* ---------- RESPONSIVE ---------- */\n\n@media (max-width: 900px) {\n\n .gradio-container {\n padding: 0 14px 40px !important;\n }\n\n .hero {\n padding: 52px 20px 48px;\n border-radius: 24px;\n }\n\n .hero h1 {\n font-size: 44px !important;\n }\n\n .hero-copy {\n font-size: 15px;\n }\n\n .meta-grid {\n grid-template-columns: 1fr;\n }\n\n .top-nav {\n align-items: flex-start;\n }\n\n .brand-sub {\n display: none;\n }\n\n .visual-panel [data-testid="image"],\n .upload-zone [data-testid="image"] {\n min-height: 280px !important;\n }\n\n .app-footer {\n flex-direction: column;\n }\n}\n\n\n@media (max-width: 560px) {\n\n .system-status {\n display: none;\n }\n\n .hero h1 {\n font-size: 38px !important;\n }\n\n .hero {\n padding-left: 16px;\n padding-right: 16px;\n }\n\n .prediction-card {\n padding: 22px;\n }\n\n .prediction-name {\n font-size: 34px;\n }\n}\n\n.prompt-chip-button {\n flex: 0 0 auto !important;\n width: auto !important;\n min-width: 0 !important;\n min-height: 40px !important;\n padding: 0 16px !important;\n\n border-radius: 999px !important;\n border: 1px solid rgba(148, 184, 207, 0.18) !important;\n\n background: rgba(255, 255, 255, 0.025) !important;\n color: #b9d7ea !important;\n\n font-size: 12px !important;\n font-weight: 600 !important;\n\n transition:\n transform 180ms ease,\n border-color 180ms ease,\n background 180ms ease,\n box-shadow 180ms ease !important;\n}\n\n.prompt-chip-button:hover {\n transform: translateY(-2px) !important;\n\n border-color: rgba(77, 226, 183, 0.45) !important;\n\n background: rgba(77, 226, 183, 0.08) !important;\n color: #a3f5dd !important;\n\n box-shadow:\n 0 10px 28px rgba(77, 226, 183, 0.08) !important;\n}\n' # ========================================================= # EXACT WORKING PREMIUM INTERFACE # ========================================================= with gr.Blocks( title="Geo-Intelligent AI", css=premium_css, theme=gr.themes.Base() ) as premium_demo: # Persistent analysis context structured_state = gr.State() # ===================================================== # TOP NAVIGATION # ===================================================== gr.HTML("""
◈
Geo-Intelligent AI
Explainable Geospatial Intelligence
AI systems operational
""") # ===================================================== # HERO # ===================================================== gr.HTML("""
◉ MULTIMODAL GEOSPATIAL INTELLIGENCE

Understand Earth imagery with intelligent context.

Transform satellite imagery into explainable land-cover intelligence using deep learning, visual attribution, structured geospatial knowledge, and natural-language AI.

10 Land-Cover Classes 90.44% Test Accuracy Grad-CAM Explainability LLM-Powered Analysis
""") # ===================================================== # ANALYSIS WORKSPACE # ===================================================== gr.HTML("""
01 / ANALYZE
Satellite Intelligence Workspace
Upload an image and run the complete GeoAI analysis pipeline.
""") with gr.Row(equal_height=False): # LEFT — UPLOAD with gr.Column( scale=5, elem_classes=["premium-card", "card-pad"] ): input_image = gr.Image( type="numpy", label="Satellite Image", sources=["upload"], height=420, elem_classes=["upload-zone"] ) gr.HTML("""
◎ Best results are obtained from satellite or aerial land-cover imagery.
""") analyze_button = gr.Button( "Analyze Satellite Image →", variant="primary", elem_classes=["primary-action"] ) # RIGHT — PRIMARY RESULT with gr.Column(scale=4): prediction_summary = gr.HTML( """
AWAITING ANALYSIS
No image analyzed
Upload an image to begin
""" ) with gr.Column( elem_classes=["premium-card", "card-pad"] ): gr.HTML("""
CONFIDENCE DISTRIBUTION
""") prediction_output = gr.Label( label="Top-3 Land-Cover Predictions", num_top_classes=3, elem_classes=["confidence-panel"] ) analysis_meta = gr.HTML( """
MODEL MobileNetV2
EXPLAINABILITY Grad-CAM
STATUS Ready
""" ) # ===================================================== # VISUAL INTELLIGENCE # ===================================================== gr.HTML("""
02 / EXPLAIN
Visual Intelligence
Compare the source image with the regions that most influenced the model's prediction.
""") with gr.Row(equal_height=True): with gr.Column( elem_classes=[ "premium-card", "card-pad", "visual-panel" ] ): gr.HTML( '
ORIGINAL IMAGE
' ) original_output = gr.Image( label=None, interactive=False, height=430, show_label=False ) with gr.Column( elem_classes=[ "premium-card", "card-pad", "visual-panel" ] ): gr.HTML( '
GRAD-CAM ATTENTION
' ) gradcam_output = gr.Image( label=None, interactive=False, height=430, show_label=False ) # ===================================================== # AI INTELLIGENCE REPORT # ===================================================== gr.HTML("""
03 / INTERPRET
GeoAI Intelligence Report
A natural-language interpretation grounded in the model output and geospatial knowledge layer.
""") with gr.Column( elem_classes=["premium-card", "card-pad"] ): explanation_output = gr.Markdown( """ Upload and analyze a satellite image to generate the intelligence report. """, elem_classes=["ai-report"] ) # ===================================================== # GEOAI COPILOT # ===================================================== gr.HTML("""
04 / ASK
GeoAI Copilot
Ask follow-up questions about the currently analyzed image.
""") with gr.Column( elem_classes=["copilot-shell", "card-pad"] ): # ============================================= # CLICKABLE SUGGESTED QUESTIONS # ============================================= with gr.Row( elem_classes=["prompt-row"] ): prompt_reliability = gr.Button( "How reliable is this prediction?", elem_classes=["prompt-chip-button"] ) prompt_focus = gr.Button( "What did the model focus on?", elem_classes=["prompt-chip-button"] ) prompt_importance = gr.Button( "Why is this land cover important?", elem_classes=["prompt-chip-button"] ) prompt_alternatives = gr.Button( "What are the alternative predictions?", elem_classes=["prompt-chip-button"] ) question_input = gr.Textbox( label="Ask about this analysis", placeholder=( "Ask about the prediction, confidence, " "explainability, significance, or applications..." ), lines=2, elem_classes=["copilot-input"] ) with gr.Row(): ask_button = gr.Button( "Ask GeoAI →", variant="secondary", elem_classes=["secondary-action"] ) clear_question_button = gr.Button( "Clear Question" ) answer_output = gr.Markdown( """ Your GeoAI Copilot response will appear here. """, elem_classes=["copilot-answer"] ) # ===================================================== # FOOTER # ===================================================== gr.HTML(""" """) # ===================================================== # HELPER FUNCTION FOR SUGGESTED QUESTIONS # ===================================================== def ask_suggested_question( question, structured_state ): answer = gradio_ask_question( question, structured_state ) return question, answer # ===================================================== # EVENT WIRING # ===================================================== # Analyze satellite image analyze_button.click( fn=premium_analyze_image, inputs=input_image, outputs=[ prediction_summary, original_output, gradcam_output, prediction_output, explanation_output, analysis_meta, structured_state ] ) # Ask manually typed question ask_button.click( fn=gradio_ask_question, inputs=[ question_input, structured_state ], outputs=answer_output ) # Press Enter to ask question_input.submit( fn=gradio_ask_question, inputs=[ question_input, structured_state ], outputs=answer_output ) # Clear question and answer clear_question_button.click( fn=lambda: ( "", "Your GeoAI Copilot response will appear here." ), inputs=None, outputs=[ question_input, answer_output ] ) # ===================================================== # SUGGESTED QUESTION EVENTS # ===================================================== prompt_reliability.click( fn=lambda state: ask_suggested_question( "How reliable is this prediction?", state ), inputs=structured_state, outputs=[ question_input, answer_output ] ) prompt_focus.click( fn=lambda state: ask_suggested_question( "What did the model focus on?", state ), inputs=structured_state, outputs=[ question_input, answer_output ] ) prompt_importance.click( fn=lambda state: ask_suggested_question( "Why is this land cover important?", state ), inputs=structured_state, outputs=[ question_input, answer_output ] ) prompt_alternatives.click( fn=lambda state: ask_suggested_question( "What are the alternative predictions?", state ), inputs=structured_state, outputs=[ question_input, answer_output ] ) # ========================================================= # HUGGING FACE SPACES LAUNCH # ========================================================= if __name__ == "__main__": premium_demo.launch()