Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import os | |
| import pandas as pd | |
| from PIL import Image | |
| import folium | |
| from src.rainbolt_parody import RainboltParody | |
| from src.map_gen import MapGenerator | |
| from src.geo_info import GeoInfo | |
| # --- Constants and System Prompt --- | |
| SYSTEM_PROMPT = """ | |
| # ROLE | |
| You are a world-class GeoGuessr expert. Your task is to pinpoint the location of the provided image with extreme precision. | |
| # METHODOLOGY | |
| You will follow a strict, three-stage analytical process. | |
| ## STAGE 1: OBSERVATION & CLUE EXTRACTION | |
| Internally, create a structured list of all visual evidence. Do not output this list. | |
| - **Road & Infrastructure:** Road Lines, Bollards, Poles, Signage (Language, Script, Style) | |
| - **Environment & Nature:** Sun Position & Climate, Vegetation & Trees, Soil & Topography | |
| - **Human & Cultural Markers:** Architecture Style, Vehicle Models & License Plates | |
| - **Meta Clues:** Google Car Generation/Antenna, Image Quality/Season | |
| ## STAGE 2: DEDUCTION & SYNTHESIS | |
| Internally, reason through the clues from Stage 1. | |
| 1. **Broad Localization:** Use major clues (driving side, language, sun) to determine the country or large region. | |
| 2. **Narrowing Down:** Use secondary clues (bollards, architecture, area codes) to narrow it down to a specific state, province, or city. | |
| 3. **Verification:** Cross-reference your findings. Do all clues point to the same place? | |
| 4. **Confidence Assessment:** Based on the consistency of the evidence, determine a confidence score. | |
| ## STAGE 3: FINAL OUTPUT | |
| After completing your internal analysis, provide your answer *only* in the following JSON format. The reasoning section must be a concise summary of your Stage 2 synthesis. | |
| { | |
| "continent": "Your final guess", | |
| "country": "Your final guess", | |
| "region": "Your final guess (e.g., state, province, county)", | |
| "city": "Your final guess (or nearest town if rural)", | |
| "coordinates": "Your best estimate for latitude and longitude (e.g., 40.7128, -74.0060)", | |
| "confidence_score": "A score from 0 to 100", | |
| "reasoning": "Summarize your deduction process. Mention the key 3-5 clues and how they led to your conclusion." | |
| } | |
| """ | |
| # --- Backend Functions --- | |
| def handle_error(message, e=None): | |
| """Helper to format and raise Gradio errors.""" | |
| if e: | |
| print(f"Error: {message} - {e}") | |
| raise gr.Error(f"{message}. Details: {str(e)}") | |
| else: | |
| print(f"Error: {message}") | |
| raise gr.Error(message) | |
| def pinpoint_location(api_key, uploaded_image): | |
| """ | |
| Takes an API key and an uploaded image, returns location data. | |
| """ | |
| if not api_key: | |
| handle_error("Google AI API Key is required.") | |
| if uploaded_image is None: | |
| handle_error("Please upload an image.") | |
| try: | |
| rp = RainboltParody(title="Location Analysis", api_key=api_key) | |
| response, _, _ = rp.get_info(SYSTEM_PROMPT=SYSTEM_PROMPT, img=uploaded_image) | |
| info_data = [[key, value] for key, value in response.items()] | |
| info_df = pd.DataFrame(info_data, columns=["Attribute", "Value"]) | |
| reasoning = response.get('reasoning', 'No reasoning provided.') | |
| coords_str = response.get('coordinates', '0,0') | |
| mgk = MapGenerator() | |
| single_point_map = mgk.get_map(location1=coords_str) | |
| map_html_output = single_point_map._repr_html_() | |
| # --- FIX IS HERE --- | |
| # The return statement must have exactly 6 values to match the 6 outputs. | |
| return ( | |
| info_df, # 1. for info_output_df | |
| reasoning, # 2. for reasoning_output | |
| map_html_output, # 3. for map_output_single | |
| response, # 4. for prediction_state | |
| gr.Accordion(visible=True), # 5. for error_accordion | |
| gr.Accordion(visible=True) # 6. for save_accordion | |
| ) | |
| except Exception as e: | |
| handle_error("Failed to process the image with the AI model", e) | |
| def calculate_error_distance(prediction_state, url, manual_coords_str): | |
| """ | |
| Calculates the distance between the predicted point and an actual point. | |
| """ | |
| if not prediction_state: | |
| handle_error("You must pinpoint a location first.") | |
| if not url and not manual_coords_str: | |
| handle_error("Please provide either a Google Maps URL or manual coordinates for the actual location.") | |
| try: | |
| if url: | |
| gi = GeoInfo(response=prediction_state, url=url) | |
| else: | |
| gi = GeoInfo(response=prediction_state, coordinates=manual_coords_str) | |
| error_km, lat_actual, lon_actual, _, _ = gi.calculate_error() | |
| bearing = gi.calculate_bearing() | |
| direction = gi.get_direction(angle=bearing) | |
| info_text = gi.combine_info(error=error_km, direction=direction) | |
| actual_coords = f"{lat_actual},{lon_actual}" | |
| mgk = MapGenerator() | |
| map_with_line = mgk.get_map( | |
| location1=prediction_state['coordinates'], | |
| location2=actual_coords, | |
| error=error_km, | |
| direction=direction | |
| ) | |
| map_html = map_with_line._repr_html_() | |
| return info_text, gr.HTML(value=map_html, visible=True), error_km, direction, url if url else "" | |
| except Exception as e: | |
| handle_error("Failed to calculate the distance", e) | |
| def save_and_download(prediction_state, error_km, direction, original_url, filename): | |
| """ | |
| Saves the combined results to a CSV file and provides it for download. | |
| """ | |
| if not prediction_state: | |
| handle_error("No prediction data to save.") | |
| if not filename: | |
| handle_error("Please provide a filename for the CSV.") | |
| if not filename.endswith('.csv'): | |
| filename += '.csv' | |
| try: | |
| data_to_add = prediction_state.copy() | |
| data_to_add['error'] = f"{error_km:.2f}" if error_km is not None else 'N/A' | |
| data_to_add['direction'] = direction if direction else 'N/A' | |
| data_to_add['actual_location_url'] = original_url if original_url else 'N/A' | |
| new_df = pd.DataFrame([data_to_add]) | |
| if os.path.exists(filename): | |
| existing_df = pd.read_csv(filename) | |
| final_df = pd.concat([existing_df, new_df], ignore_index=True) | |
| else: | |
| final_df = new_df | |
| final_df.to_csv(filename, index=False) | |
| return gr.DataFrame(value=final_df, visible=True), gr.File(value=filename, label="Download CSV", visible=True) | |
| except Exception as e: | |
| handle_error("Failed to save data to CSV", e) | |
| # --- Gradio UI --- | |
| with gr.Blocks(theme=gr.themes.Soft(), title="GeoGuessr AI") as app: | |
| gr.Markdown("# GeoGuessr AI π") | |
| gr.Markdown("Pinpoint the location of any Google Street View image.") | |
| prediction_state = gr.State(None) | |
| error_state = gr.State(None) | |
| direction_state = gr.State(None) | |
| url_state = gr.State(None) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("## 1. Pinpoint Location") | |
| api_key_input = gr.Textbox(label="Google AI API Key", placeholder="Enter your API key here...", type="password") | |
| image_input = gr.Image(type="pil", label="Upload Street View Image") | |
| pinpoint_btn = gr.Button("Pinpoint Location", variant="primary") | |
| gr.Markdown("### Prediction Results") | |
| info_output_df = gr.DataFrame(label="Location Information", headers=["Attribute", "Value"]) | |
| reasoning_output = gr.Textbox(label="AI Reasoning", lines=5, interactive=False) | |
| with gr.Column(scale=2): | |
| gr.Markdown("### Map View") | |
| map_output_single = gr.HTML(label="Predicted Location") | |
| map_output_comparison = gr.HTML(visible=False) | |
| with gr.Accordion("2. Calculate Error (Optional)", open=False, visible=False) as error_accordion: | |
| with gr.Row(): | |
| url_input = gr.Textbox(label="Google Maps URL of Actual Location", placeholder="Paste URL here...") | |
| manual_coords_input = gr.Textbox(label="Or, Enter Manual Coordinates (lat, lon)", placeholder="e.g., 51.5074, -0.1278") | |
| calculate_btn = gr.Button("Calculate Distance", variant="secondary") | |
| distance_output = gr.Textbox(label="Result", interactive=False) | |
| with gr.Accordion("3. Save Results (Optional)", open=False, visible=False) as save_accordion: | |
| filename_input = gr.Textbox(label="CSV Filename", value="geo_results.csv") | |
| save_btn = gr.Button("Save to CSV & Prepare Download", variant="secondary") | |
| with gr.Row(): | |
| csv_preview_df = gr.DataFrame(label="CSV Content Preview", visible=False) | |
| download_file = gr.File(label="Download CSV", visible=False) | |
| # --- Event Handlers --- | |
| pinpoint_btn.click( | |
| fn=pinpoint_location, | |
| inputs=[api_key_input, image_input], | |
| outputs=[info_output_df, reasoning_output, map_output_single, prediction_state, error_accordion, save_accordion] | |
| ) | |
| calculate_btn.click( | |
| fn=calculate_error_distance, | |
| inputs=[prediction_state, url_input, manual_coords_input], | |
| outputs=[distance_output, map_output_comparison, error_state, direction_state, url_state] | |
| ) | |
| save_btn.click( | |
| fn=save_and_download, | |
| inputs=[prediction_state, error_state, direction_state, url_state, filename_input], | |
| outputs=[csv_preview_df, download_file] | |
| ) | |
| # gr.Examples( | |
| # examples=[ | |
| # [os.path.join(REPO_DIR, "examples/example_1.png")], | |
| # [os.path.join(REPO_DIR, "examples/example_2.png")], | |
| # [os.path.join(REPO_DIR, "examples/example_3.png")], | |
| # ], | |
| # inputs=image_input, | |
| # label="Example Images" | |
| # ) | |
| if __name__ == "__main__": | |
| app.launch(debug=True) |