Spaces:
Sleeping
Sleeping
Commit
·
d0dda9e
1
Parent(s):
bace565
add raw online mode
Browse files- app.py +180 -107
- mapcrunch_controller.py +10 -0
app.py
CHANGED
@@ -2,6 +2,7 @@ import streamlit as st
|
|
2 |
import json
|
3 |
import os
|
4 |
import time
|
|
|
5 |
from io import BytesIO
|
6 |
from PIL import Image
|
7 |
from pathlib import Path
|
@@ -25,6 +26,24 @@ if "HF_TOKEN" in st.secrets:
|
|
25 |
os.environ["HF_TOKEN"] = st.secrets["HF_TOKEN"]
|
26 |
|
27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
def get_available_datasets():
|
29 |
datasets_dir = Path("datasets")
|
30 |
if not datasets_dir.exists():
|
@@ -60,46 +79,84 @@ st.markdown("### *The all-knowing AI that sees everything, knows everything*")
|
|
60 |
with st.sidebar:
|
61 |
st.header("Configuration")
|
62 |
|
63 |
-
#
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
"
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
with open(data_paths["golden_labels"], "r") as f:
|
83 |
-
golden_labels = json.load(f).get("samples", [])
|
84 |
|
85 |
-
|
86 |
-
|
87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
st.stop()
|
89 |
|
90 |
-
|
91 |
-
|
92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
)
|
94 |
-
st.info("💡 Available datasets: " + ", ".join(available_datasets))
|
95 |
-
st.stop()
|
96 |
-
except Exception as e:
|
97 |
-
st.error(f"❌ Error loading dataset '{dataset_choice}': {str(e)}")
|
98 |
-
st.stop()
|
99 |
-
|
100 |
-
num_samples = st.slider(
|
101 |
-
"Samples to Test", 1, len(golden_labels), min(3, len(golden_labels))
|
102 |
-
)
|
103 |
|
104 |
start_button = st.button("🚀 Start", type="primary")
|
105 |
|
@@ -109,117 +166,136 @@ if start_button:
|
|
109 |
config = MODELS_CONFIG[model_choice]
|
110 |
model_class = get_model_class(config["class"])
|
111 |
|
112 |
-
benchmark_helper = MapGuesserBenchmark(dataset_name=dataset_choice)
|
113 |
all_results = []
|
114 |
|
115 |
progress_bar = st.progress(0)
|
116 |
|
117 |
with GeoBot(
|
118 |
-
model=model_class,
|
119 |
-
model_name=config["model_name"],
|
120 |
-
headless=True,
|
121 |
-
temperature=temperature,
|
122 |
) as bot:
|
123 |
for i, sample in enumerate(test_samples):
|
124 |
st.divider()
|
125 |
st.header(f"Sample {i + 1}/{num_samples}")
|
126 |
|
127 |
-
|
128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
|
130 |
-
# Create
|
131 |
sample_container = st.container()
|
132 |
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
def ui_step_callback(step_info):
|
138 |
-
"""Callback function to update UI after each step"""
|
139 |
-
step_num = step_info["step_num"]
|
140 |
|
141 |
-
|
142 |
-
|
143 |
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
step_containers[step_num] = st.container()
|
148 |
|
149 |
-
|
150 |
-
|
|
|
151 |
|
152 |
col1, col2 = st.columns([1, 2])
|
153 |
|
154 |
with col1:
|
155 |
-
# Display screenshot
|
156 |
st.image(
|
157 |
-
|
158 |
caption=f"What AI sees - Step {step_num}",
|
159 |
use_column_width=True,
|
160 |
)
|
161 |
|
162 |
with col2:
|
163 |
-
#
|
164 |
-
|
165 |
-
|
166 |
-
json.dumps(step_info["available_actions"], indent=2)
|
167 |
)
|
|
|
|
|
168 |
|
169 |
-
# Show
|
170 |
-
|
171 |
-
|
|
|
|
|
|
|
172 |
st.write("**AI Context:**")
|
173 |
st.text_area(
|
174 |
"History",
|
175 |
history_text,
|
176 |
height=100,
|
177 |
disabled=True,
|
178 |
-
key=f"history_{i}_{
|
179 |
-
)
|
180 |
-
|
181 |
-
# Show AI reasoning and action
|
182 |
-
action = step_info.get("action_details", {}).get(
|
183 |
-
"action", "N/A"
|
184 |
)
|
185 |
|
186 |
-
|
|
|
|
|
187 |
st.warning("Max steps reached. Forcing GUESS.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
188 |
|
189 |
-
|
190 |
-
|
191 |
|
192 |
-
|
193 |
-
if action == "GUESS":
|
194 |
-
lat = step_info.get("action_details", {}).get("lat")
|
195 |
-
lon = step_info.get("action_details", {}).get("lon")
|
196 |
-
st.success(f"`{action}` - {lat:.4f}, {lon:.4f}")
|
197 |
-
else:
|
198 |
-
st.success(f"`{action}`")
|
199 |
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
"reasoning": step_info.get("reasoning"),
|
204 |
-
"action_details": step_info.get("action_details"),
|
205 |
-
"remaining_steps": step_info.get("remaining_steps"),
|
206 |
-
}
|
207 |
-
st.json(decision_data)
|
208 |
|
209 |
-
|
210 |
-
|
211 |
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
max_steps=steps_per_sample, step_callback=ui_step_callback
|
216 |
-
)
|
217 |
-
except Exception as e:
|
218 |
-
st.error(f"Error during agent execution: {e}")
|
219 |
-
final_guess = None
|
220 |
|
221 |
-
|
222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
223 |
st.subheader("Sample Result")
|
224 |
true_coords = {"lat": sample.get("lat"), "lng": sample.get("lng")}
|
225 |
distance_km = None
|
@@ -252,9 +328,6 @@ if start_button:
|
|
252 |
{
|
253 |
"sample_id": sample.get("id"),
|
254 |
"model": model_choice,
|
255 |
-
"steps_taken": len(sample_steps_data),
|
256 |
-
"max_steps": steps_per_sample,
|
257 |
-
"temperature": temperature,
|
258 |
"true_coordinates": true_coords,
|
259 |
"predicted_coordinates": final_guess,
|
260 |
"distance_km": distance_km,
|
|
|
2 |
import json
|
3 |
import os
|
4 |
import time
|
5 |
+
import re
|
6 |
from io import BytesIO
|
7 |
from PIL import Image
|
8 |
from pathlib import Path
|
|
|
26 |
os.environ["HF_TOKEN"] = st.secrets["HF_TOKEN"]
|
27 |
|
28 |
|
29 |
+
def convert_google_to_mapcrunch_url(google_url):
|
30 |
+
"""Convert Google Maps URL to MapCrunch URL format."""
|
31 |
+
try:
|
32 |
+
# Extract coordinates using regex
|
33 |
+
match = re.search(r'@(-?\d+\.\d+),(-?\d+\.\d+)', google_url)
|
34 |
+
if not match:
|
35 |
+
return None
|
36 |
+
|
37 |
+
lat, lon = match.groups()
|
38 |
+
# MapCrunch format: lat_lon_heading_pitch_zoom
|
39 |
+
# Using default values for heading (317.72), pitch (0.86), and zoom (0)
|
40 |
+
mapcrunch_url = f"http://www.mapcrunch.com/p/{lat}_{lon}_317.72_0.86_0"
|
41 |
+
return mapcrunch_url
|
42 |
+
except Exception as e:
|
43 |
+
st.error(f"Error converting URL: {str(e)}")
|
44 |
+
return None
|
45 |
+
|
46 |
+
|
47 |
def get_available_datasets():
|
48 |
datasets_dir = Path("datasets")
|
49 |
if not datasets_dir.exists():
|
|
|
79 |
with st.sidebar:
|
80 |
st.header("Configuration")
|
81 |
|
82 |
+
# Mode selection
|
83 |
+
mode = st.radio("Mode", ["Dataset Mode", "Online Mode"], index=0)
|
84 |
+
|
85 |
+
if mode == "Dataset Mode":
|
86 |
+
# Get available datasets and ensure we have a valid default
|
87 |
+
available_datasets = get_available_datasets()
|
88 |
+
default_dataset = available_datasets[0] if available_datasets else "default"
|
89 |
+
|
90 |
+
dataset_choice = st.selectbox("Dataset", available_datasets, index=0)
|
91 |
+
model_choice = st.selectbox("Model", list(MODELS_CONFIG.keys()), index=list(MODELS_CONFIG.keys()).index(DEFAULT_MODEL))
|
92 |
+
steps_per_sample = st.slider("Max Steps", 1, 20, 10)
|
93 |
+
temperature = st.slider(
|
94 |
+
"Temperature",
|
95 |
+
0.0,
|
96 |
+
2.0,
|
97 |
+
DEFAULT_TEMPERATURE,
|
98 |
+
0.1,
|
99 |
+
help="Controls randomness in AI responses. 0.0 = deterministic, higher = more creative",
|
100 |
+
)
|
|
|
|
|
101 |
|
102 |
+
# Load dataset with error handling
|
103 |
+
data_paths = get_data_paths(dataset_choice)
|
104 |
+
try:
|
105 |
+
with open(data_paths["golden_labels"], "r") as f:
|
106 |
+
golden_labels = json.load(f).get("samples", [])
|
107 |
+
|
108 |
+
st.info(f"Dataset '{dataset_choice}' has {len(golden_labels)} samples")
|
109 |
+
if len(golden_labels) == 0:
|
110 |
+
st.error(f"Dataset '{dataset_choice}' contains no samples!")
|
111 |
+
st.stop()
|
112 |
+
|
113 |
+
except FileNotFoundError:
|
114 |
+
st.error(f"❌ Dataset '{dataset_choice}' not found at {data_paths['golden_labels']}")
|
115 |
+
st.info("💡 Available datasets: " + ", ".join(available_datasets))
|
116 |
+
st.stop()
|
117 |
+
except Exception as e:
|
118 |
+
st.error(f"❌ Error loading dataset '{dataset_choice}': {str(e)}")
|
119 |
st.stop()
|
120 |
|
121 |
+
num_samples = st.slider(
|
122 |
+
"Samples to Test", 1, len(golden_labels), min(3, len(golden_labels))
|
123 |
+
)
|
124 |
+
else: # Online Mode
|
125 |
+
st.info("Enter a Google Maps URL to analyze a specific location")
|
126 |
+
google_url = st.text_input(
|
127 |
+
"Google Maps URL",
|
128 |
+
placeholder="https://www.google.com/maps/@37.5851338,-122.1519467,9z?entry=ttu"
|
129 |
+
)
|
130 |
+
|
131 |
+
if google_url:
|
132 |
+
mapcrunch_url = convert_google_to_mapcrunch_url(google_url)
|
133 |
+
if mapcrunch_url:
|
134 |
+
st.success(f"Converted to MapCrunch URL: {mapcrunch_url}")
|
135 |
+
# Create a single sample for online mode
|
136 |
+
golden_labels = [{
|
137 |
+
"id": "online",
|
138 |
+
"lat": float(re.search(r'@(-?\d+\.\d+),(-?\d+\.\d+)', google_url).group(1)),
|
139 |
+
"lng": float(re.search(r'@(-?\d+\.\d+),(-?\d+\.\d+)', google_url).group(2)),
|
140 |
+
"url": mapcrunch_url
|
141 |
+
}]
|
142 |
+
num_samples = 1
|
143 |
+
else:
|
144 |
+
st.error("Invalid Google Maps URL format")
|
145 |
+
st.stop()
|
146 |
+
else:
|
147 |
+
st.warning("Please enter a Google Maps URL")
|
148 |
+
st.stop()
|
149 |
+
|
150 |
+
model_choice = st.selectbox("Model", list(MODELS_CONFIG.keys()), index=list(MODELS_CONFIG.keys()).index(DEFAULT_MODEL))
|
151 |
+
steps_per_sample = st.slider("Max Steps", 1, 20, 10)
|
152 |
+
temperature = st.slider(
|
153 |
+
"Temperature",
|
154 |
+
0.0,
|
155 |
+
2.0,
|
156 |
+
DEFAULT_TEMPERATURE,
|
157 |
+
0.1,
|
158 |
+
help="Controls randomness in AI responses. 0.0 = deterministic, higher = more creative",
|
159 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
|
161 |
start_button = st.button("🚀 Start", type="primary")
|
162 |
|
|
|
166 |
config = MODELS_CONFIG[model_choice]
|
167 |
model_class = get_model_class(config["class"])
|
168 |
|
169 |
+
benchmark_helper = MapGuesserBenchmark(dataset_name=dataset_choice if mode == "Dataset Mode" else "online")
|
170 |
all_results = []
|
171 |
|
172 |
progress_bar = st.progress(0)
|
173 |
|
174 |
with GeoBot(
|
175 |
+
model=model_class, model_name=config["model_name"], headless=True, temperature=temperature
|
|
|
|
|
|
|
176 |
) as bot:
|
177 |
for i, sample in enumerate(test_samples):
|
178 |
st.divider()
|
179 |
st.header(f"Sample {i + 1}/{num_samples}")
|
180 |
|
181 |
+
if mode == "Online Mode":
|
182 |
+
# Load the MapCrunch URL directly
|
183 |
+
bot.controller.load_url(sample["url"])
|
184 |
+
else:
|
185 |
+
# Load from dataset as before
|
186 |
+
bot.controller.load_location_from_data(sample)
|
187 |
+
|
188 |
+
bot.controller.setup_clean_environment()
|
189 |
|
190 |
+
# Create scrollable container for this sample
|
191 |
sample_container = st.container()
|
192 |
|
193 |
+
with sample_container:
|
194 |
+
# Initialize step tracking
|
195 |
+
history = bot.init_history()
|
196 |
+
final_guess = None
|
|
|
|
|
|
|
197 |
|
198 |
+
for step in range(steps_per_sample):
|
199 |
+
step_num = step + 1
|
200 |
|
201 |
+
# Create step container
|
202 |
+
with st.container():
|
203 |
+
st.subheader(f"Step {step_num}/{steps_per_sample}")
|
|
|
204 |
|
205 |
+
# Take screenshot and show
|
206 |
+
bot.controller.label_arrows_on_screen()
|
207 |
+
screenshot_bytes = bot.controller.take_street_view_screenshot()
|
208 |
|
209 |
col1, col2 = st.columns([1, 2])
|
210 |
|
211 |
with col1:
|
|
|
212 |
st.image(
|
213 |
+
screenshot_bytes,
|
214 |
caption=f"What AI sees - Step {step_num}",
|
215 |
use_column_width=True,
|
216 |
)
|
217 |
|
218 |
with col2:
|
219 |
+
# Get current screenshot as base64
|
220 |
+
current_screenshot_b64 = bot.pil_to_base64(
|
221 |
+
Image.open(BytesIO(screenshot_bytes))
|
|
|
222 |
)
|
223 |
+
|
224 |
+
available_actions = bot.controller.get_available_actions()
|
225 |
|
226 |
+
# Show AI context
|
227 |
+
st.write("**Available Actions:**")
|
228 |
+
st.code(json.dumps(available_actions, indent=2))
|
229 |
+
|
230 |
+
# Generate and display history
|
231 |
+
history_text = bot.generate_history_text(history)
|
232 |
st.write("**AI Context:**")
|
233 |
st.text_area(
|
234 |
"History",
|
235 |
history_text,
|
236 |
height=100,
|
237 |
disabled=True,
|
238 |
+
key=f"history_{i}_{step}",
|
|
|
|
|
|
|
|
|
|
|
239 |
)
|
240 |
|
241 |
+
# Force guess on last step or get AI decision
|
242 |
+
if step_num == steps_per_sample:
|
243 |
+
action = "GUESS"
|
244 |
st.warning("Max steps reached. Forcing GUESS.")
|
245 |
+
# Create a forced decision for consistency
|
246 |
+
decision = {
|
247 |
+
"reasoning": "Maximum steps reached, forcing final guess with fallback coordinates.",
|
248 |
+
"action_details": {"action": "GUESS", "lat": 0.0, "lon": 0.0}
|
249 |
+
}
|
250 |
+
else:
|
251 |
+
# Use the bot's agent step execution
|
252 |
+
remaining_steps = steps_per_sample - step
|
253 |
+
decision = bot.execute_agent_step(
|
254 |
+
history, remaining_steps, current_screenshot_b64, available_actions
|
255 |
+
)
|
256 |
|
257 |
+
if decision is None:
|
258 |
+
raise ValueError("Failed to get AI decision")
|
259 |
|
260 |
+
action = decision["action_details"]["action"]
|
|
|
|
|
|
|
|
|
|
|
|
|
261 |
|
262 |
+
# Show AI decision
|
263 |
+
st.write("**AI Reasoning:**")
|
264 |
+
st.info(decision.get("reasoning", "N/A"))
|
|
|
|
|
|
|
|
|
|
|
265 |
|
266 |
+
st.write("**AI Action:**")
|
267 |
+
st.success(f"`{action}`")
|
268 |
|
269 |
+
# Show raw response for debugging
|
270 |
+
with st.expander("Decision Details"):
|
271 |
+
st.json(decision)
|
|
|
|
|
|
|
|
|
|
|
272 |
|
273 |
+
# Add step to history using the bot's method
|
274 |
+
bot.add_step_to_history(history, current_screenshot_b64, decision)
|
275 |
+
|
276 |
+
# Execute action
|
277 |
+
if action == "GUESS":
|
278 |
+
if step_num == steps_per_sample:
|
279 |
+
# Forced guess - use fallback coordinates
|
280 |
+
lat, lon = 0.0, 0.0
|
281 |
+
st.error("Forced guess with fallback coordinates")
|
282 |
+
else:
|
283 |
+
lat = decision.get("action_details", {}).get("lat")
|
284 |
+
lon = decision.get("action_details", {}).get("lon")
|
285 |
+
|
286 |
+
if lat is not None and lon is not None:
|
287 |
+
final_guess = (lat, lon)
|
288 |
+
st.success(f"Final Guess: {lat:.4f}, {lon:.4f}")
|
289 |
+
break
|
290 |
+
else:
|
291 |
+
# Use bot's execute_action method
|
292 |
+
bot.execute_action(action)
|
293 |
+
|
294 |
+
# Auto scroll to bottom
|
295 |
+
st.empty() # Force refresh to show latest content
|
296 |
+
time.sleep(1)
|
297 |
+
|
298 |
+
# Sample Results
|
299 |
st.subheader("Sample Result")
|
300 |
true_coords = {"lat": sample.get("lat"), "lng": sample.get("lng")}
|
301 |
distance_km = None
|
|
|
328 |
{
|
329 |
"sample_id": sample.get("id"),
|
330 |
"model": model_choice,
|
|
|
|
|
|
|
331 |
"true_coordinates": true_coords,
|
332 |
"predicted_coordinates": final_guess,
|
333 |
"distance_km": distance_km,
|
mapcrunch_controller.py
CHANGED
@@ -291,3 +291,13 @@ class MapCrunchController:
|
|
291 |
|
292 |
def __exit__(self, exc_type, exc_val, exc_tb):
|
293 |
self.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
291 |
|
292 |
def __exit__(self, exc_type, exc_val, exc_tb):
|
293 |
self.close()
|
294 |
+
|
295 |
+
def load_url(self, url):
|
296 |
+
"""Load a specific MapCrunch URL."""
|
297 |
+
try:
|
298 |
+
self.driver.get(url)
|
299 |
+
time.sleep(2) # Wait for the page to load
|
300 |
+
return True
|
301 |
+
except Exception as e:
|
302 |
+
print(f"Error loading URL: {e}")
|
303 |
+
return False
|