# utils.py import os, json, io from pathlib import Path from datetime import datetime from PIL import Image import exifread import folium from folium import plugins from config import HISTORY_FILE # Global debug messages for Gradio debug_messages = [] def add_debug(msg): """Add debug message that can be shown in Gradio""" global debug_messages debug_messages.append(msg) print(msg) # Also print to console/logs def get_debug_messages(): """Get and clear debug messages""" global debug_messages msgs = "\n".join(debug_messages) debug_messages = [] return msgs # ---------------- GPS Extraction ---------------- def extract_gps_from_image(image): """Extract GPS info from PIL image or file path using exifread""" try: add_debug("π Starting GPS extraction...") # Case 1: If path given if isinstance(image, (str, bytes, os.PathLike)): with open(image, "rb") as f: tags = exifread.process_file(f, details=False) # Case 2: If PIL Image elif isinstance(image, Image.Image): import io buf = io.BytesIO() # Save with EXIF data if available if "exif" in image.info: image.save(buf, format="JPEG", exif=image.info["exif"]) else: image.save(buf, format="JPEG") buf.seek(0) tags = exifread.process_file(buf, details=False) else: add_debug(f"β Unsupported type: {type(image)}") return {"coords": None, "address": None} # Debug: show GPS tags gps_tags = {k: str(v) for k, v in tags.items() if "GPS" in str(k)} if gps_tags: add_debug(f"π Found {len(gps_tags)} GPS tags") for key in list(gps_tags.keys())[:3]: add_debug(f" β’ {key}") else: add_debug("β No GPS tags found") return {"coords": None, "address": None} # Extract coordinates lat = _to_decimal(tags.get("GPS GPSLatitude"), tags.get("GPS GPSLatitudeRef")) lon = _to_decimal(tags.get("GPS GPSLongitude"), tags.get("GPS GPSLongitudeRef")) if lat is not None and lon is not None: add_debug(f"β GPS converted: {lat:.6f}, {lon:.6f}") add_debug(f"πΊοΈ https://maps.google.com/?q={lat},{lon}") return {"coords": (lat, lon), "address": None} else: add_debug("β Could not convert GPS to decimal") except Exception as e: add_debug(f"β GPS extraction error: {e}") return {"coords": None, "address": None} def _to_decimal(dms_data, ref): """Convert DMS coordinates to decimal degrees""" if not dms_data: return None try: dms = [] # exifread IfdTag has .values values = getattr(dms_data, "values", dms_data) for val in values: if hasattr(val, "num") and hasattr(val, "den"): # Ratio dms.append(float(val.num) / float(val.den) if val.den != 0 else 0) elif isinstance(val, str) and "/" in val: # "136974/3125" num, den = val.split("/") den = float(den) if float(den) != 0 else 1 dms.append(float(num) / den) else: dms.append(float(val)) if len(dms) < 2: return None degrees, minutes = dms[0], dms[1] seconds = dms[2] if len(dms) > 2 else 0 decimal = degrees + (minutes / 60.0) + (seconds / 3600.0) if ref and str(ref).upper() in ["S", "W"]: decimal = -decimal return decimal except Exception as e: add_debug(f"β Coordinate conversion error: {e}") return None # ---------------- History ---------------- def save_detection_to_history(detection): """Save detection to persistent history file""" try: Path("data").mkdir(exist_ok=True) history = [] if os.path.exists(HISTORY_FILE): try: with open(HISTORY_FILE, "r") as f: history = json.load(f) except json.JSONDecodeError: add_debug("β History file corrupted, starting fresh") history = [] # Ensure GPS data is serializable if detection.get("gps") and detection["gps"].get("coords"): coords = detection["gps"]["coords"] # Convert tuple to list for JSON detection["gps"]["coords"] = [coords[0], coords[1]] history.append(detection) # Keep only last 100 detections if len(history) > 100: history = history[-100:] with open(HISTORY_FILE, "w") as f: json.dump(history, f, indent=2) add_debug(f"πΎ Saved detection #{len(history)} to history") except Exception as e: add_debug(f"β Failed to save history: {str(e)}") def load_detection_history(): """Load detection history, converting GPS coords back to tuples""" try: if os.path.exists(HISTORY_FILE): with open(HISTORY_FILE, "r") as f: history = json.load(f) # Convert GPS coords from list back to tuple for detection in history: if detection.get("gps") and detection["gps"].get("coords"): coords = detection["gps"]["coords"] if isinstance(coords, list) and len(coords) == 2: detection["gps"]["coords"] = tuple(coords) return history except Exception as e: add_debug(f"β Failed to load history: {str(e)}") return [] # ---------------- Map ---------------- def create_detection_map(detections): """Create Folium map with detection markers""" if not detections: return "