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"""
"""
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(
"""
"""
)
# =====================================================
# 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()