Agent4EO / app /main.py
unknown
cleaning commit
059a062
Raw
History Blame Contribute Delete
8.4 kB
"""Gradio UI for Agent4EO."""
import sys, os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import shutil
import json
import gradio as gr
from pathlib import Path
from typing import Dict, Any, Tuple, Optional
from agent.schema import SampleSpec
from agent.router import route_query
from agent.interpreter import interpret_results
from agent.tools import run_ndvi, run_lst_landsat, run_dnbr_s2pair, load_sample_registry
from huggingface_hub import snapshot_download
snapshot_download(
repo_id="Th-Olive/Agent4EO-samples",
repo_type="dataset",
local_dir="./data"
)
def format_metrics_table(metrics: Dict[str, float]) -> str:
"""Format metrics as HTML table."""
rows = []
for key, value in metrics.items():
if isinstance(value, float):
rows.append(f"<tr><td><b>{key}</b></td><td>{value:.4f}</td></tr>")
else:
rows.append(f"<tr><td><b>{key}</b></td><td>{value}</td></tr>")
return f"<table style='width:100%; border-collapse: collapse;'>{''.join(rows)}</table>"
def format_interpretation(headline: str, bullets: list, caveats: list) -> str:
"""Format interpretation as HTML."""
html = f"<h3>{headline}</h3>"
if bullets:
html += "<ul>"
for bullet in bullets:
html += f"<li>{bullet}</li>"
html += "</ul>"
if caveats:
html += "<p><b>Caveats:</b></p><ul>"
for caveat in caveats:
html += f"<li><i>{caveat}</i></li>"
html += "</ul>"
return html
def run_analysis(
sample_title: str,
user_query: Optional[str] = ""
) -> Tuple[Optional[str], Optional[str], Optional[str], str, str]:
"""
Execute EO analysis pipeline.
Args:
sample_title: Selected sample title
user_query: User's natural language query
Returns:
Tuple of (preview_path, histogram_path, metrics_html, interpretation_html, error_msg)
"""
try:
# Load sample
samples = load_sample_registry()
if sample_title not in samples:
return None, None, None, "", f"❌ Sample '{sample_title}' not found in registry."
sample = samples[sample_title]
# Route query
tool_name, error_msg = route_query(sample, user_query)
if error_msg:
print('error msg')
return None, None, None, "", f"❌ {error_msg}"
# Execute tool
tool_map = {
"run_ndvi": run_ndvi,
"run_lst_landsat": run_lst_landsat,
"run_dnbr_s2pair": run_dnbr_s2pair
}
tool_func = tool_map.get(tool_name)
if not tool_func:
return None, None, None, "", f"❌ Unknown tool: {tool_name}"
# Run tool
result_dict = tool_func.invoke(sample_title)
if result_dict.get("histogram_png") is None:
hist_out = gr.update(value=None, visible=False)
else:
hist_out = gr.update(value=result_dict.get("histogram_png"), visible=True)
# Generate interpretation
interpretation = interpret_results(
tool_name=tool_name,
sample_title=sample.title,
metrics=result_dict["metrics"],
extras=result_dict.get("extras", {})
)
# Format outputs
metrics_html = format_metrics_table(result_dict["metrics"])
interpretation_html = format_interpretation(
interpretation.headline,
interpretation.bullets,
interpretation.caveats
)
return (
result_dict["preview_png"],
hist_out,
metrics_html,
interpretation_html,
"✅ Analysis completed successfully!"
)
except Exception as e:
return None, None, None, "", f"❌ Error: {str(e)}"
about_md = """
---
## Challenge
- **Decision-makers** don’t need raw imagery; they need clear, **operational signals**, still many Earth Observation (EO) use-cases fail to cross the gap from geospatial experts to end-users, **from research to deployment**.
- Staying current with a **fast-moving field** while making **geospatial reasoning** explicit : As a **curiosity-driven AI Research Engineer**, I had to **investigate agentic workflows** and the pipeline structures behind groundbreaking initiatives capturing attention: [Google Earth AI](https://ai.google/earth-ai/), [Axion Planetary MCP](https://github.com/Dhenenjay/axion-planetary-mcp), [Ageospatial](https://ageospatial.com/), [GeoRetina](https://www.georetina.com/)
## Solution
- Curated samples are fed to a **routing agent** that selects the appropriate analysis, like a geospatial expert would.
- Implemented tools: **Vegetation condition analysis** (NDVI), **Urban Heat Island monitoring** (LST), and **burn scars charcterization** (dNBR).
- **OpenStreetMap context** (API call) is attached to computed metrics to anchor results to places.
- A second **LLM** pass turns numbers + context into a concise operational narrative.
- The interface displays a **quicklook**, **histogram**, quantitative **metrics**, and a short, number-grounded **explanation**.
- The agent is powered by **Ministral-3B** for efficiency; orchestration uses **LangChain**. EO I/O uses **Rasterio** with proper CRS/transform and not-relevant data handling.
## Results
- A **live, accessible project** : I believe in public demos over private perfection.
- **Clear visual and textual outputs** understandable by non-experts, revealing Earth Observation's added value.
- A concrete **exploration of agentic orchestration** for EO that other practitioners can reuse as a reference pattern.
## Possible extensions
- Move to true **prompt-driven analysis**: users specify topic, period, and region; the agent fetches data on demand (from a way larger range of sources) and selects tools accordingly.
- **Richer narratives** tailored to roles (territorial planning, insurers...) with domain-aware capacity.
- Expand the **toolset**: multi-temporal change maps, flood risk, landslide tracking, building characterization, oil-spill detection, SAR flood mapping, and embeddings-based heads with integration of **Foundation Models**
- Enable **on-the-fly tool drafting** (code synthesis) when safe and auditable.
- Be **deliberate with LLMs**: for such a small range of tools, selection via LLM isn’t necessary, and with such low context models can hallucinate. Agents should be used where they add real leverage, beyond conventional programming capacities.
"""
def create_ui():
"""Create Gradio interface."""
samples = load_sample_registry()
sample_choices = [s.title for s in samples.values()]
with gr.Blocks(title="Agent4EO") as demo:
gr.Markdown("# 🛰️ Agent4EO - A demo by Thomas OLIVE")
with gr.Accordion("Agent4EO is a demo that investigates and demystifies geospatial reasoning by using a lightweight agent to turn curated satellite samples into clear, operational insights with concise narratives.",
open=True):
gr.Markdown(about_md)
with gr.Row():
with gr.Column(scale=1):
sample_dropdown = gr.Dropdown(
choices=sample_choices,
label="Select Sample",
value=sample_choices[0][1] if sample_choices else None
)
run_button = gr.Button("Run Analysis", variant="primary")
with gr.Column(scale=2):
status_msg = gr.Markdown("")
preview_img = gr.Image(label="Preview", type="filepath")
histogram_img = gr.Image(label="Histogram", type="filepath", visible=False)
metrics_html = gr.HTML(label="Metrics")
interpretation_html = gr.HTML(label="Interpretation")
run_button.click(
fn=run_analysis,
inputs=[sample_dropdown],
outputs=[preview_img, histogram_img, metrics_html, interpretation_html, status_msg]
)
for foldername in os.listdir("outputs"):
shutil.rmtree(os.path.join("outputs", foldername))
return demo
if __name__ == "__main__":
# Ensure output directory exists
Path("outputs").mkdir(exist_ok=True)
demo = create_ui()
demo.launch(share=True)
# demo.launch(server_name="127.0.0.1", server_port=7860)