Spaces:
Sleeping
Sleeping
File size: 7,422 Bytes
ac28b6c 6822058 ac28b6c 6822058 ac28b6c 6822058 1faa8b6 79540f7 6822058 79540f7 6822058 1faa8b6 79540f7 6822058 1faa8b6 6822058 108875a 6822058 ac28b6c 6822058 ac28b6c 6822058 ac28b6c c3b8d1d 6dffb88 4dbd17e 6822058 9fa1ad1 2c9a7b3 c3b8d1d 6822058 9fa1ad1 6822058 9fa1ad1 d346d55 6dffb88 d346d55 6dffb88 9fa1ad1 c3b8d1d 9fa1ad1 c3b8d1d 9fa1ad1 c3b8d1d 4dbd17e 6822058 5f83972 d346d55 c3b8d1d 9fa1ad1 5f83972 11faaa7 b9974f1 6dffb88 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | import gradio as gr
from google import genai
import os
import re
from PIL import Image, ImageDraw
import requests
from io import BytesIO
# --- 1. Custom CSS ---
custom_css = """
.improving { background-color: #dcf2dc; color: #155724; padding: 12px; border-radius: 8px; font-weight: bold; font-size: 1.2em; text-align: center; border: 1px solid #c3e6cb;}
.declining { background-color: #f8d7da; color: #721c24; padding: 12px; border-radius: 8px; font-weight: bold; font-size: 1.2em; text-align: center; border: 1px solid #f5c6cb;}
.stable { background-color: #fff3cd; color: #856404; padding: 12px; border-radius: 8px; font-weight: bold; font-size: 1.2em; text-align: center; border: 1px solid #ffeeba;}
"""
# --- 2. Google Maps API Fetcher ---
def fetch_streetview(address):
"""Geocodes an address and fetches the most recent Street View image."""
gmaps_api_key = os.environ.get("GMAPS_API_KEY")
if not gmaps_api_key:
raise gr.Error("ERROR: GMAPS_API_KEY not found in Secrets.")
if not address:
raise gr.Error("Please enter a valid address.")
gr.Info("Geocoding address...")
# 1. Geocoding API
geo_url = "https://maps.googleapis.com/maps/api/geocode/json"
geo_params = {"address": address, "key": gmaps_api_key}
geo_data = requests.get(geo_url, params=geo_params).json()
if geo_data['status'] != 'OK':
error_detail = geo_data.get('error_message', geo_data['status'])
raise gr.Error(f"Maps API Error: {error_detail}")
lat = geo_data['results'][0]['geometry']['location']['lat']
lng = geo_data['results'][0]['geometry']['location']['lng']
gr.Info("Fetching Street View imagery...")
# 2. Street View Static API
sv_url = "https://maps.googleapis.com/maps/api/streetview"
sv_params = {
"size": "640x640",
"location": f"{lat},{lng}",
"fov": "80",
"pitch": "0",
"key": gmaps_api_key
}
sv_res = requests.get(sv_url, params=sv_params)
# --- ALIGNED IF/ELSE BLOCK ---
if sv_res.status_code == 200:
gr.Info("Image successfully loaded!")
return Image.open(BytesIO(sv_res.content))
else:
error_msg = sv_res.text if sv_res.text else "No reason provided."
raise gr.Error(f"Maps 403 Error: {error_msg}")
# --- 3. Spatial Drawing Engine ---
def draw_forensic_targets(image, report_text):
annotated_img = image.copy()
draw = ImageDraw.Draw(annotated_img)
width, height = annotated_img.size
coords = re.findall(r'\[(\d+),\s*(\d+)\]', report_text)
for y_str, x_str in coords:
y = int((int(y_str) / 1000) * height)
x = int((int(x_str) / 1000) * width)
r = 15
draw.ellipse((x - r, y - r, x + r, y + r), outline="red", width=5)
return annotated_img
# --- 4. Robotics-ER Core Engine ---
def ground_truth_engine(past_img, present_img, audit_level, progress=gr.Progress()):
progress(0, desc="Waking Spatial Sentinel...")
api_key = os.environ.get("GOOGLE_API_KEY")
if not api_key:
return None, "<div class='declining'>ERROR: API Key Missing</div>", "Set GOOGLE_API_KEY in Secrets."
client = genai.Client(api_key=api_key)
if past_img is None or present_img is None:
return None, "<div class='stable'>Missing Images</div>", "Please upload both images."
audit_directives = {
"Standard Audit": "Perform a structural comparison of roofing, siding, and landscaping.",
"Deep Forensic": """Perform an exhaustive spatial audit.
Identify subtle wear (shingles, foundation, paint) and biological encroachment.
MANDATORY: Return center points for top 3 changes as [y, x] normalized coordinates (0-1000)."""
}
directive = audit_directives.get(audit_level)
progress(0.2, desc=f"Running {audit_level}...")
prompt = f"""
SYSTEM INSTRUCTION: You are a structural forensic agent.
DIRECTIVE: {directive}
TASK: Compare 'Past Condition' vs 'Current Condition'.
Return a structured report. YOU MUST CONCLUDE with exactly one of these phrases:
'Maintenance Trajectory: IMPROVING', 'Maintenance Trajectory: STABLE', or 'Maintenance Trajectory: DECLINING'.
"""
try:
progress(0.5, desc="Robotics-ER is scanning for spatial anomalies...")
response = client.models.generate_content(
model="gemini-robotics-er-1.5-preview",
contents=[prompt, past_img, present_img]
)
text_out = response.text
badge_html = "<div class='stable'>โ Trajectory: STABLE</div>"
if "IMPROVING" in text_out.upper():
badge_html = "<div class='improving'>๐ Trajectory: IMPROVING</div>"
elif "DECLINING" in text_out.upper():
badge_html = "<div class='declining'>๐ Trajectory: DECLINING</div>"
progress(0.8, desc="Annotating image...")
final_present_img = present_img
if "Deep Forensic" in audit_level:
final_present_img = draw_forensic_targets(present_img, text_out)
progress(1.0, desc="Audit Finalized.")
return (past_img, final_present_img), badge_html, text_out
except Exception as e:
return None, "<div class='declining'>Analysis Failed</div>", str(e)
# --- UI LAYOUT ---
with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
gr.Markdown("# ๐ GroundTruth AI: Spatial Property Sentinel")
# --- NEW: Address Search Bar ---
with gr.Row():
address_input = gr.Textbox(label="Property Address", placeholder="e.g., 1600 Pennsylvania Avenue NW, Washington, DC", scale=4)
fetch_btn = gr.Button("๐ Fetch Current Street View", variant="secondary", scale=1)
# --- TOP ROW: The "Input Console" ---
with gr.Row():
with gr.Column(scale=1):
p_img = gr.Image(label="Past Condition (Manual Upload)", type="pil")
with gr.Column(scale=1):
c_img = gr.Image(label="Current Condition (Auto-Fetched)", type="pil")
with gr.Column(scale=1):
audit_mode = gr.Radio(
choices=["Standard Audit", "Deep Forensic"],
value="Standard Audit",
label="Select Audit Precision"
)
submit = gr.Button("Analyze Property Trajectory", variant="primary", size="lg")
# --- MIDDLE ROW: The "Hero Output" ---
with gr.Row():
with gr.Column(scale=1):
status_badge = gr.HTML()
slider_out = gr.ImageSlider(label="Spatial Delta Comparison", type="pil", height=600)
# --- BOTTOM ROW: The "Forensic Data" ---
with gr.Row():
with gr.Column(scale=1):
with gr.Accordion("View Detailed Forensic Data", open=False):
report_out = gr.Markdown()
# --- EVENTS ---
# 1. Wire the Fetch Button to the Maps API and output to the Current Image box
fetch_btn.click(
fn=fetch_streetview,
inputs=[address_input],
outputs=[c_img]
)
# 2. Wire the Analyze Button to the Robotics-ER Engine
submit.click(
fn=ground_truth_engine,
inputs=[p_img, c_img, audit_mode],
outputs=[slider_out, status_badge, report_out],
show_progress="minimal"
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860) |