Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- .gradio/certificate.pem +31 -0
- README.md +2 -8
- geo-risk-space/README.md +25 -0
- geo-risk-space/app.py +245 -0
- geo-risk-space/keys/gee_service_account.json +13 -0
- geo-risk-space/keys/theta-decker-477618-q0-0f560b4d89f9.json +13 -0
- geo-risk-space/main-requirements.txt +109 -0
- geo-risk-space/model/README.txt +2 -0
- geo-risk-space/model/geo_model.pth +3 -0
- geo-risk-space/requirements.txt +12 -0
- geo-risk-space/src/__pycache__/model.cpython-311.pyc +0 -0
- geo-risk-space/src/model.py +46 -0
- t.sh +180 -0
- upload-hf.py +45 -0
.gradio/certificate.pem
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-----BEGIN CERTIFICATE-----
|
| 2 |
+
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
|
| 31 |
+
-----END CERTIFICATE-----
|
README.md
CHANGED
|
@@ -1,12 +1,6 @@
|
|
| 1 |
---
|
| 2 |
-
title: 3rd
|
| 3 |
-
|
| 4 |
-
colorFrom: blue
|
| 5 |
-
colorTo: indigo
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 5.49.1
|
| 8 |
-
app_file: app.py
|
| 9 |
-
pinned: false
|
| 10 |
---
|
| 11 |
-
|
| 12 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
+
title: 3rd-hack-nation
|
| 3 |
+
app_file: ./geo-risk-space/app.py
|
|
|
|
|
|
|
| 4 |
sdk: gradio
|
| 5 |
sdk_version: 5.49.1
|
|
|
|
|
|
|
| 6 |
---
|
|
|
|
|
|
geo-risk-space/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Geo-Risk Predictor
|
| 3 |
+
emoji: 🌍
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: blue
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: "4.44.0"
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# 🌍 Geo-Risk Predictor
|
| 13 |
+
|
| 14 |
+
Interactive Gradio app that estimates environmental and geographic risk using:
|
| 15 |
+
- **Satellite imagery** (Google Earth Engine)
|
| 16 |
+
- **Elevation data** (SRTM)
|
| 17 |
+
- **Weather data** (Open-Meteo)
|
| 18 |
+
- **Custom PyTorch model** for risk inference
|
| 19 |
+
|
| 20 |
+
## How to run locally
|
| 21 |
+
```bash
|
| 22 |
+
pip install -r requirements.txt
|
| 23 |
+
python app.py
|
| 24 |
+
```
|
| 25 |
+
-------------------------------
|
geo-risk-space/app.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
import os
|
| 3 |
+
import io
|
| 4 |
+
import json
|
| 5 |
+
import tempfile
|
| 6 |
+
import torch
|
| 7 |
+
import requests
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pandas as pd
|
| 10 |
+
import gradio as gr
|
| 11 |
+
from PIL import Image
|
| 12 |
+
import torchvision.transforms as T
|
| 13 |
+
from geopy.geocoders import Nominatim
|
| 14 |
+
|
| 15 |
+
# --- Import model ---
|
| 16 |
+
from src.model import CompactGeoEmbed
|
| 17 |
+
|
| 18 |
+
# --- Config ---
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
MODEL_PATH = "model/geo_model.pth"
|
| 22 |
+
GEE_KEY_PATH = os.environ.get("GEE_KEY_PATH", "keys/gee_service_account.json")
|
| 23 |
+
DEVICE = torch.device("cpu")
|
| 24 |
+
TF_SIZE = (120, 120)
|
| 25 |
+
|
| 26 |
+
# --- Load local model ---
|
| 27 |
+
def load_model():
|
| 28 |
+
model = CompactGeoEmbed(32, 96)
|
| 29 |
+
try:
|
| 30 |
+
state = torch.load(MODEL_PATH, map_location=DEVICE)
|
| 31 |
+
model.load_state_dict(state)
|
| 32 |
+
print("✅ Model loaded successfully from local path.")
|
| 33 |
+
except Exception as e:
|
| 34 |
+
print("⚠️ Model load failed:", e)
|
| 35 |
+
model.to(DEVICE).eval()
|
| 36 |
+
return model
|
| 37 |
+
|
| 38 |
+
MODEL = load_model()
|
| 39 |
+
tf = T.Compose([T.Resize(TF_SIZE), T.ToTensor()])
|
| 40 |
+
|
| 41 |
+
# --- Reverse geocode (city, country info) ---
|
| 42 |
+
def reverse_geocode(lat, lon):
|
| 43 |
+
try:
|
| 44 |
+
geolocator = Nominatim(user_agent="geo-risk-app", timeout=10)
|
| 45 |
+
loc = geolocator.reverse((lat, lon), language="en")
|
| 46 |
+
return loc.address if loc else "Unknown location"
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print("⚠️ Reverse geocode failed:", e)
|
| 49 |
+
return "Unknown location"
|
| 50 |
+
|
| 51 |
+
# --- Earth Engine init with service account ---
|
| 52 |
+
def init_gee():
|
| 53 |
+
try:
|
| 54 |
+
import ee
|
| 55 |
+
except Exception as e:
|
| 56 |
+
print("⚠️ earthengine-api not installed:", e)
|
| 57 |
+
return False
|
| 58 |
+
|
| 59 |
+
if not os.path.exists(GEE_KEY_PATH):
|
| 60 |
+
print(f"⚠️ GEE key not found at {GEE_KEY_PATH}")
|
| 61 |
+
return False
|
| 62 |
+
|
| 63 |
+
try:
|
| 64 |
+
with open(GEE_KEY_PATH, "r") as f:
|
| 65 |
+
svc = json.load(f)
|
| 66 |
+
credentials = ee.ServiceAccountCredentials(svc["client_email"], GEE_KEY_PATH)
|
| 67 |
+
ee.Initialize(credentials)
|
| 68 |
+
print("✅ Earth Engine initialized with service account.")
|
| 69 |
+
return True
|
| 70 |
+
except Exception as e:
|
| 71 |
+
print("⚠️ GEE initialization failed:", e)
|
| 72 |
+
return False
|
| 73 |
+
|
| 74 |
+
GEE_READY = init_gee()
|
| 75 |
+
|
| 76 |
+
# --- Fetch satellite & elevation tiles from GEE ---
|
| 77 |
+
def fetch_gee_images(lat, lon):
|
| 78 |
+
try:
|
| 79 |
+
if not GEE_READY:
|
| 80 |
+
raise RuntimeError("GEE not initialized")
|
| 81 |
+
|
| 82 |
+
import ee
|
| 83 |
+
|
| 84 |
+
p = ee.Geometry.Point([lon, lat])
|
| 85 |
+
region = p.buffer(1000).bounds()
|
| 86 |
+
|
| 87 |
+
s2 = (
|
| 88 |
+
ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED")
|
| 89 |
+
.filterBounds(p)
|
| 90 |
+
.filterDate("2024-01-01", "2024-12-31")
|
| 91 |
+
.sort("CLOUDY_PIXEL_PERCENTAGE")
|
| 92 |
+
.first()
|
| 93 |
+
.select(["B4", "B3", "B2"])
|
| 94 |
+
)
|
| 95 |
+
s2_url = s2.visualize(min=0, max=3000).getThumbURL(
|
| 96 |
+
{"region": region, "dimensions": f"{TF_SIZE[0]}x{TF_SIZE[1]}", "format": "png"}
|
| 97 |
+
)
|
| 98 |
+
s2_img = Image.open(io.BytesIO(requests.get(s2_url, timeout=10).content)).convert("RGB")
|
| 99 |
+
|
| 100 |
+
srtm = ee.Image("USGS/SRTMGL1_003")
|
| 101 |
+
elev_url = srtm.visualize(min=0, max=3000).getThumbURL(
|
| 102 |
+
{"region": region, "dimensions": f"{TF_SIZE[0]}x{TF_SIZE[1]}", "format": "png"}
|
| 103 |
+
)
|
| 104 |
+
elev_img = Image.open(io.BytesIO(requests.get(elev_url, timeout=10).content)).convert("L")
|
| 105 |
+
|
| 106 |
+
return s2_img, elev_img
|
| 107 |
+
except Exception as e:
|
| 108 |
+
print("⚠️ GEE fetch failed:", e)
|
| 109 |
+
rgb = Image.new("RGB", TF_SIZE, (127, 127, 127))
|
| 110 |
+
elev = Image.new("L", TF_SIZE, 127)
|
| 111 |
+
return rgb, elev
|
| 112 |
+
|
| 113 |
+
# --- Weather API ---
|
| 114 |
+
def fetch_weather(lat, lon):
|
| 115 |
+
try:
|
| 116 |
+
url = (
|
| 117 |
+
f"https://api.open-meteo.com/v1/forecast?"
|
| 118 |
+
f"latitude={lat}&longitude={lon}&daily=temperature_2m_max,"
|
| 119 |
+
"precipitation_sum,relative_humidity_2m_mean,wind_speed_10m_max"
|
| 120 |
+
"&forecast_days=1&timezone=UTC"
|
| 121 |
+
)
|
| 122 |
+
r = requests.get(url, timeout=8)
|
| 123 |
+
d = r.json().get("daily", {})
|
| 124 |
+
return {
|
| 125 |
+
"temp_max": float(d.get("temperature_2m_max", [None])[0]) if d else None,
|
| 126 |
+
"precip": float(d.get("precipitation_sum", [None])[0]) if d else None,
|
| 127 |
+
"humidity": float(d.get("relative_humidity_2m_mean", [None])[0]) if d else None,
|
| 128 |
+
"wind_speed": float(d.get("wind_speed_10m_max", [None])[0]) if d else None,
|
| 129 |
+
}
|
| 130 |
+
except Exception as e:
|
| 131 |
+
print("⚠️ Weather fetch failed:", e)
|
| 132 |
+
return {"temp_max": None, "precip": None, "humidity": None, "wind_speed": None}
|
| 133 |
+
|
| 134 |
+
# --- Preprocess image ---
|
| 135 |
+
def preprocess(img):
|
| 136 |
+
img = img.convert("RGB")
|
| 137 |
+
return tf(img).unsqueeze(0)
|
| 138 |
+
|
| 139 |
+
# --- IP-based location fallback ---
|
| 140 |
+
def get_ip_location():
|
| 141 |
+
try:
|
| 142 |
+
r = requests.get("https://ipapi.co/json", timeout=5)
|
| 143 |
+
data = r.json()
|
| 144 |
+
return round(float(data["latitude"]), 5), round(float(data["longitude"]), 5)
|
| 145 |
+
except Exception as e:
|
| 146 |
+
print("⚠️ IP location fetch failed:", e)
|
| 147 |
+
return 51.5072, -0.1276 # fallback to London
|
| 148 |
+
|
| 149 |
+
# --- Inference ---
|
| 150 |
+
def predict(lat, lon, img):
|
| 151 |
+
# fallback if user didn't fill lat/lon
|
| 152 |
+
if lat is None or lon is None:
|
| 153 |
+
lat, lon = get_ip_location()
|
| 154 |
+
|
| 155 |
+
lat = round(float(lat), 5)
|
| 156 |
+
lon = round(float(lon), 5)
|
| 157 |
+
|
| 158 |
+
location_str = reverse_geocode(lat, lon)
|
| 159 |
+
rgb_img, elev_img = fetch_gee_images(lat, lon)
|
| 160 |
+
|
| 161 |
+
# user-uploaded image takes priority
|
| 162 |
+
if img is not None:
|
| 163 |
+
rgb_img = img.resize(TF_SIZE)
|
| 164 |
+
|
| 165 |
+
x = preprocess(rgb_img).to(DEVICE)
|
| 166 |
+
e = torch.tensor(np.array(elev_img) / 255.0, dtype=torch.float32).unsqueeze(0).unsqueeze(0).to(DEVICE)
|
| 167 |
+
|
| 168 |
+
with torch.no_grad():
|
| 169 |
+
try:
|
| 170 |
+
_, _, r = MODEL(x, e)
|
| 171 |
+
risk_score = float(r.item())
|
| 172 |
+
except Exception as e:
|
| 173 |
+
print("⚠️ Model inference failed:", e)
|
| 174 |
+
risk_score = None
|
| 175 |
+
|
| 176 |
+
weather = fetch_weather(lat, lon)
|
| 177 |
+
result = {
|
| 178 |
+
"Location": location_str,
|
| 179 |
+
"Latitude": lat,
|
| 180 |
+
"Longitude": lon,
|
| 181 |
+
"Predicted_Risk_Score": round(risk_score, 4) if risk_score is not None else None,
|
| 182 |
+
**weather,
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
df = pd.DataFrame([result])
|
| 186 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".csv") as tmp:
|
| 187 |
+
tmp_path = tmp.name
|
| 188 |
+
df.to_csv(tmp_path, index=False)
|
| 189 |
+
|
| 190 |
+
return rgb_img, df, tmp_path
|
| 191 |
+
|
| 192 |
+
# --- Gradio UI ---
|
| 193 |
+
with gr.Blocks() as demo:
|
| 194 |
+
gr.Markdown("## 🌍 Geo-Risk Predictor (Local Model + GEE + Location Auto-Detect)")
|
| 195 |
+
|
| 196 |
+
with gr.Row():
|
| 197 |
+
lat = gr.Number(value=None, label="Latitude")
|
| 198 |
+
lon = gr.Number(value=None, label="Longitude")
|
| 199 |
+
get_loc_btn = gr.Button("📍 Use My Location")
|
| 200 |
+
|
| 201 |
+
img = gr.Image(type="pil", label=f"Optional RGB Tile (auto-resized to {TF_SIZE[0]}×{TF_SIZE[1]})")
|
| 202 |
+
run_btn = gr.Button("Run Prediction")
|
| 203 |
+
|
| 204 |
+
rgb_preview = gr.Image(label="Satellite Image Used")
|
| 205 |
+
output_df = gr.DataFrame(label="Predicted Data", interactive=False)
|
| 206 |
+
file_out = gr.File(label="Download CSV")
|
| 207 |
+
|
| 208 |
+
# main prediction button
|
| 209 |
+
run_btn.click(fn=predict, inputs=[lat, lon, img], outputs=[rgb_preview, output_df, file_out])
|
| 210 |
+
|
| 211 |
+
# JS geolocation with IP fallback
|
| 212 |
+
get_loc_btn.click(
|
| 213 |
+
None,
|
| 214 |
+
[],
|
| 215 |
+
[lat, lon],
|
| 216 |
+
js="""
|
| 217 |
+
async () => {
|
| 218 |
+
if (navigator.geolocation) {
|
| 219 |
+
try {
|
| 220 |
+
const pos = await new Promise((res, rej) =>
|
| 221 |
+
navigator.geolocation.getCurrentPosition(res, rej)
|
| 222 |
+
);
|
| 223 |
+
return [pos.coords.latitude.toFixed(5), pos.coords.longitude.toFixed(5)];
|
| 224 |
+
} catch (err) {
|
| 225 |
+
console.warn("Browser geolocation failed, fallback to IP API.");
|
| 226 |
+
const ip = await fetch("https://ipapi.co/json");
|
| 227 |
+
const data = await ip.json();
|
| 228 |
+
return [data.latitude.toFixed(5), data.longitude.toFixed(5)];
|
| 229 |
+
}
|
| 230 |
+
} else {
|
| 231 |
+
const ip = await fetch("https://ipapi.co/json");
|
| 232 |
+
const data = await ip.json();
|
| 233 |
+
return [data.latitude.toFixed(5), data.longitude.toFixed(5)];
|
| 234 |
+
}
|
| 235 |
+
}
|
| 236 |
+
""",
|
| 237 |
+
)
|
| 238 |
+
|
| 239 |
+
if __name__ == "__main__":
|
| 240 |
+
# demo.launch(server_name="0.0.0.0",
|
| 241 |
+
# server_port=int(os.environ.get("PORT", 7860)),
|
| 242 |
+
# share=True)
|
| 243 |
+
|
| 244 |
+
demo.launch(share=True)
|
| 245 |
+
|
geo-risk-space/keys/gee_service_account.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"type": "service_account",
|
| 3 |
+
"project_id": "theta-decker-477618-q0",
|
| 4 |
+
"private_key_id": "0f560b4d89f9a91880f5a4a0c995fa084a348bde",
|
| 5 |
+
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCp2IEWiwybiuz6\nfh1f23v6Cxp06ABbF2kuaBqixsAbj2FwlgfTFb9W9/2pev0ZbDKlQBpekeDVWnQW\nhAnaki8O7JSPa7WusBkUrKc2xItQU1b272vaSODIo+3OTY23MnaiW1ZKJkCeDiCU\n3cPmrApmzuCnclr9jzuzBkcdExDTj96iPbmGnylRFq7rCek4UxwgVilqI94ICtWA\nZjVJu1vh7ziSOCohMirODhICu6crJkJNBGHWTIL9QoL+uxzMbSn8MxfOHkEKkB21\n1VHXURh5aNyfj2mdcs4Zy9iRnqa/lY0Q0w8un5i4csgNmhS6GNuQE3+OxcnVM7Ps\nOwG2s65zAgMBAAECggEAEWIWyCtgFXVRDswkjoCEvlEvmoJ6MtiBC95cx4cm90sj\n8mVwlJKXvDtya0uqptTzlAUtDbvJcpyo3/sQlR28Ej91bv5boG7mij6neIwCJCz7\nbBeSBsvJsc9RUfmPBQ51pOVLPhiPKX4RqJrP54Z5JA+NX6XtGSF1PzvnwFqIsNZQ\nc5/OsWWwVNxuAwBmunbML6mz1IZA6jrD9zucqwKbuQHeiWIzUiJyQYJBTyzNUMT4\nOm9l5pLVOIMagW+JdpUllme/W+NS7G6pD1jcx9IZMsTtPZAVXmzL30TZTubSrKhk\ntVQ0Js0Tl4u9ZNZ+Rku3LfBxk3AAvVopvvLCgQnqDQKBgQDd0m87147bZwL5tEcw\nGYoJ+QwA9XrUYYXH8mUKHKtni5MAPtxkFEVysA5D961EnKXCNScoGPIpK+53BGd0\nDfMMaqwYGswS/7I1EmGalBDxELQpMD4mTz4yon5Tho8prtyWOriY8QCFnrfgkFd2\nBEqaJFNVw2tO7ckSoukGq8WLrQKBgQDEA+mnOFBgcTHOijIB0B+z2KIhp0jccXY3\nySPidvXvbbbmvFNeh+e5PqtK7P78OvH3+hvX8JUKDmHEUbnQn4oBV7FVjNyPCDrr\njtN9OVT98kpUzVPcXu9hqSiXRvA1WgmJyXRh3BkTub+Z2zEPJ+xP+bSKgxoMebsB\nHhjFcidmnwKBgAu90M99GH26lSi8hywfnfPrL9x5IfhN9TPhO8HuRJBljfFsYmQV\nwptQgGDOomhIVmnSQHFZ6K+POL8qB4PYHS5iExvvhy/WQwuWHn59KexosvCfMhr7\nBNLPURqAu+E79Ucqcoz97MYl4ZvMaCTCE2TXWWXnwy1ZXtRStTz6KKm9AoGBAL8Q\nuU7gm6iGbfBP0NLnlh9uiQuYznLivkM+cxYqsyvBnElpRTKd8wgkyD9uqFYg9v+q\n8j0ZK43z2uTMbP2opZMNcbRcbBmYAibev9QOcIRhCoeC5b2nZFuj0gczhK3cp/OB\nRUqmimMp6lQEztthJP4H/y4NAPUsK1a5iZfc3/8tAoGARPp3T9/CBPf7vF2Ef/6E\n1N4PFtQAc6/49gakkbRRG0X+Y5bVHpcM7IM0I9L49rFNovVq+gBSgsH2/VQS2kbu\nqBUh+tira+TfVBWtVILTe6g6EWsstb+xrfmBecn82URMMgyFDjdxf9qY+FOSd055\njUurfHkPNQjJzH9T1f21prU=\n-----END PRIVATE KEY-----\n",
|
| 6 |
+
"client_email": "hack-nation-gee@theta-decker-477618-q0.iam.gserviceaccount.com",
|
| 7 |
+
"client_id": "109368693327747874205",
|
| 8 |
+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
| 9 |
+
"token_uri": "https://oauth2.googleapis.com/token",
|
| 10 |
+
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
| 11 |
+
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/hack-nation-gee%40theta-decker-477618-q0.iam.gserviceaccount.com",
|
| 12 |
+
"universe_domain": "googleapis.com"
|
| 13 |
+
}
|
geo-risk-space/keys/theta-decker-477618-q0-0f560b4d89f9.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"type": "service_account",
|
| 3 |
+
"project_id": "theta-decker-477618-q0",
|
| 4 |
+
"private_key_id": "0f560b4d89f9a91880f5a4a0c995fa084a348bde",
|
| 5 |
+
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCp2IEWiwybiuz6\nfh1f23v6Cxp06ABbF2kuaBqixsAbj2FwlgfTFb9W9/2pev0ZbDKlQBpekeDVWnQW\nhAnaki8O7JSPa7WusBkUrKc2xItQU1b272vaSODIo+3OTY23MnaiW1ZKJkCeDiCU\n3cPmrApmzuCnclr9jzuzBkcdExDTj96iPbmGnylRFq7rCek4UxwgVilqI94ICtWA\nZjVJu1vh7ziSOCohMirODhICu6crJkJNBGHWTIL9QoL+uxzMbSn8MxfOHkEKkB21\n1VHXURh5aNyfj2mdcs4Zy9iRnqa/lY0Q0w8un5i4csgNmhS6GNuQE3+OxcnVM7Ps\nOwG2s65zAgMBAAECggEAEWIWyCtgFXVRDswkjoCEvlEvmoJ6MtiBC95cx4cm90sj\n8mVwlJKXvDtya0uqptTzlAUtDbvJcpyo3/sQlR28Ej91bv5boG7mij6neIwCJCz7\nbBeSBsvJsc9RUfmPBQ51pOVLPhiPKX4RqJrP54Z5JA+NX6XtGSF1PzvnwFqIsNZQ\nc5/OsWWwVNxuAwBmunbML6mz1IZA6jrD9zucqwKbuQHeiWIzUiJyQYJBTyzNUMT4\nOm9l5pLVOIMagW+JdpUllme/W+NS7G6pD1jcx9IZMsTtPZAVXmzL30TZTubSrKhk\ntVQ0Js0Tl4u9ZNZ+Rku3LfBxk3AAvVopvvLCgQnqDQKBgQDd0m87147bZwL5tEcw\nGYoJ+QwA9XrUYYXH8mUKHKtni5MAPtxkFEVysA5D961EnKXCNScoGPIpK+53BGd0\nDfMMaqwYGswS/7I1EmGalBDxELQpMD4mTz4yon5Tho8prtyWOriY8QCFnrfgkFd2\nBEqaJFNVw2tO7ckSoukGq8WLrQKBgQDEA+mnOFBgcTHOijIB0B+z2KIhp0jccXY3\nySPidvXvbbbmvFNeh+e5PqtK7P78OvH3+hvX8JUKDmHEUbnQn4oBV7FVjNyPCDrr\njtN9OVT98kpUzVPcXu9hqSiXRvA1WgmJyXRh3BkTub+Z2zEPJ+xP+bSKgxoMebsB\nHhjFcidmnwKBgAu90M99GH26lSi8hywfnfPrL9x5IfhN9TPhO8HuRJBljfFsYmQV\nwptQgGDOomhIVmnSQHFZ6K+POL8qB4PYHS5iExvvhy/WQwuWHn59KexosvCfMhr7\nBNLPURqAu+E79Ucqcoz97MYl4ZvMaCTCE2TXWWXnwy1ZXtRStTz6KKm9AoGBAL8Q\nuU7gm6iGbfBP0NLnlh9uiQuYznLivkM+cxYqsyvBnElpRTKd8wgkyD9uqFYg9v+q\n8j0ZK43z2uTMbP2opZMNcbRcbBmYAibev9QOcIRhCoeC5b2nZFuj0gczhK3cp/OB\nRUqmimMp6lQEztthJP4H/y4NAPUsK1a5iZfc3/8tAoGARPp3T9/CBPf7vF2Ef/6E\n1N4PFtQAc6/49gakkbRRG0X+Y5bVHpcM7IM0I9L49rFNovVq+gBSgsH2/VQS2kbu\nqBUh+tira+TfVBWtVILTe6g6EWsstb+xrfmBecn82URMMgyFDjdxf9qY+FOSd055\njUurfHkPNQjJzH9T1f21prU=\n-----END PRIVATE KEY-----\n",
|
| 6 |
+
"client_email": "hack-nation-gee@theta-decker-477618-q0.iam.gserviceaccount.com",
|
| 7 |
+
"client_id": "109368693327747874205",
|
| 8 |
+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
| 9 |
+
"token_uri": "https://oauth2.googleapis.com/token",
|
| 10 |
+
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
| 11 |
+
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/hack-nation-gee%40theta-decker-477618-q0.iam.gserviceaccount.com",
|
| 12 |
+
"universe_domain": "googleapis.com"
|
| 13 |
+
}
|
geo-risk-space/main-requirements.txt
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Package Version
|
| 2 |
+
------------------------- -----------
|
| 3 |
+
aiofiles 24.1.0
|
| 4 |
+
altair 5.5.0
|
| 5 |
+
annotated-doc 0.0.3
|
| 6 |
+
annotated-types 0.7.0
|
| 7 |
+
anyio 4.11.0
|
| 8 |
+
attrs 25.4.0
|
| 9 |
+
blinker 1.9.0
|
| 10 |
+
branca 0.8.2
|
| 11 |
+
brotli 1.2.0
|
| 12 |
+
cachetools 5.5.2
|
| 13 |
+
certifi 2025.10.5
|
| 14 |
+
charset-normalizer 3.4.4
|
| 15 |
+
click 8.3.0
|
| 16 |
+
earthengine-api 0.1.383
|
| 17 |
+
fastapi 0.121.1
|
| 18 |
+
ffmpy 0.6.4
|
| 19 |
+
filelock 3.20.0
|
| 20 |
+
folium 0.17.0
|
| 21 |
+
fsspec 2025.10.0
|
| 22 |
+
geographiclib 2.1
|
| 23 |
+
geopy 2.4.1
|
| 24 |
+
gitdb 4.0.12
|
| 25 |
+
GitPython 3.1.45
|
| 26 |
+
google-api-core 2.28.1
|
| 27 |
+
google-api-python-client 2.187.0
|
| 28 |
+
google-auth 2.43.0
|
| 29 |
+
google-auth-httplib2 0.2.1
|
| 30 |
+
google-cloud-core 2.5.0
|
| 31 |
+
google-cloud-storage 3.5.0
|
| 32 |
+
google-crc32c 1.7.1
|
| 33 |
+
google-resumable-media 2.7.2
|
| 34 |
+
googleapis-common-protos 1.72.0
|
| 35 |
+
gradio 5.49.1
|
| 36 |
+
gradio_client 1.13.3
|
| 37 |
+
groovy 0.1.2
|
| 38 |
+
h11 0.16.0
|
| 39 |
+
hf-xet 1.2.0
|
| 40 |
+
httpcore 1.0.9
|
| 41 |
+
httplib2 0.31.0
|
| 42 |
+
httpx 0.28.1
|
| 43 |
+
huggingface_hub 1.1.2
|
| 44 |
+
idna 3.11
|
| 45 |
+
Jinja2 3.1.6
|
| 46 |
+
jsonschema 4.25.1
|
| 47 |
+
jsonschema-specifications 2025.9.1
|
| 48 |
+
markdown-it-py 4.0.0
|
| 49 |
+
MarkupSafe 3.0.3
|
| 50 |
+
mdurl 0.1.2
|
| 51 |
+
mpmath 1.3.0
|
| 52 |
+
narwhals 2.10.2
|
| 53 |
+
networkx 3.5
|
| 54 |
+
numpy 2.3.4
|
| 55 |
+
orjson 3.11.4
|
| 56 |
+
packaging 24.2
|
| 57 |
+
pandas 2.2.2
|
| 58 |
+
pillow 10.4.0
|
| 59 |
+
pip 25.2
|
| 60 |
+
proto-plus 1.26.1
|
| 61 |
+
protobuf 5.29.5
|
| 62 |
+
pyarrow 22.0.0
|
| 63 |
+
pyasn1 0.6.1
|
| 64 |
+
pyasn1_modules 0.4.2
|
| 65 |
+
pydantic 2.11.10
|
| 66 |
+
pydantic_core 2.33.2
|
| 67 |
+
pydeck 0.9.1
|
| 68 |
+
pydub 0.25.1
|
| 69 |
+
Pygments 2.19.2
|
| 70 |
+
pyparsing 3.2.5
|
| 71 |
+
python-dateutil 2.9.0.post0
|
| 72 |
+
python-multipart 0.0.20
|
| 73 |
+
pytz 2025.2
|
| 74 |
+
PyYAML 6.0.3
|
| 75 |
+
referencing 0.37.0
|
| 76 |
+
requests 2.32.3
|
| 77 |
+
rich 13.9.4
|
| 78 |
+
rpds-py 0.28.0
|
| 79 |
+
rsa 4.9.1
|
| 80 |
+
ruff 0.14.4
|
| 81 |
+
safehttpx 0.1.7
|
| 82 |
+
semantic-version 2.10.0
|
| 83 |
+
setuptools 80.9.0
|
| 84 |
+
shellingham 1.5.4
|
| 85 |
+
six 1.17.0
|
| 86 |
+
smmap 5.0.2
|
| 87 |
+
sniffio 1.3.1
|
| 88 |
+
starlette 0.49.3
|
| 89 |
+
streamlit 1.38.0
|
| 90 |
+
streamlit_folium 0.22.0
|
| 91 |
+
sympy 1.14.0
|
| 92 |
+
tenacity 8.5.0
|
| 93 |
+
toml 0.10.2
|
| 94 |
+
tomlkit 0.13.3
|
| 95 |
+
torch 2.9.0
|
| 96 |
+
torchvision 0.24.0
|
| 97 |
+
tornado 6.5.2
|
| 98 |
+
tqdm 4.67.1
|
| 99 |
+
typer 0.20.0
|
| 100 |
+
typer-slim 0.20.0
|
| 101 |
+
typing_extensions 4.15.0
|
| 102 |
+
typing-inspection 0.4.2
|
| 103 |
+
tzdata 2025.2
|
| 104 |
+
uritemplate 4.2.0
|
| 105 |
+
urllib3 2.5.0
|
| 106 |
+
uvicorn 0.38.0
|
| 107 |
+
websockets 15.0.1
|
| 108 |
+
wheel 0.45.1
|
| 109 |
+
xyzservices 2025.10.0
|
geo-risk-space/model/README.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Place your trained model weights here as:
|
| 2 |
+
geo-risk-space/model/geo_model.pth
|
geo-risk-space/model/geo_model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:949af48e93767a75576516b19d6426a1ab5662ab9d1fd19b184d8c0b0e56cc41
|
| 3 |
+
size 9485835
|
geo-risk-space/requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio==4.44.0
|
| 2 |
+
huggingface_hub==0.23.0
|
| 3 |
+
torch
|
| 4 |
+
torchvision
|
| 5 |
+
pandas
|
| 6 |
+
|
| 7 |
+
requests
|
| 8 |
+
Pillow
|
| 9 |
+
|
| 10 |
+
numpy
|
| 11 |
+
earthengine-api
|
| 12 |
+
geopy
|
geo-risk-space/src/__pycache__/model.cpython-311.pyc
ADDED
|
Binary file (3.75 kB). View file
|
|
|
geo-risk-space/src/model.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
import torchvision.models as models
|
| 5 |
+
|
| 6 |
+
class CompactGeoEmbed(nn.Module):
|
| 7 |
+
def __init__(self, embed_c=32, proj_dim=96, pretrained=False):
|
| 8 |
+
super().__init__()
|
| 9 |
+
backbone = models.mobilenet_v2(
|
| 10 |
+
weights=None if not pretrained else models.MobileNet_V2_Weights.IMAGENET1K_V1
|
| 11 |
+
).features
|
| 12 |
+
self.backbone = backbone
|
| 13 |
+
self.reduce = nn.Conv2d(1280, embed_c, 1)
|
| 14 |
+
self.elev_conv = nn.Conv2d(1, embed_c, 3, padding=1)
|
| 15 |
+
self.conv_head = nn.Sequential(
|
| 16 |
+
nn.Conv2d(embed_c * 2, embed_c, 3, padding=1),
|
| 17 |
+
nn.ReLU(True),
|
| 18 |
+
nn.Conv2d(embed_c, embed_c, 3, padding=1),
|
| 19 |
+
nn.ReLU(True),
|
| 20 |
+
)
|
| 21 |
+
self.proj = nn.Sequential(
|
| 22 |
+
nn.AdaptiveAvgPool2d(1),
|
| 23 |
+
nn.Flatten(),
|
| 24 |
+
nn.Linear(embed_c, proj_dim),
|
| 25 |
+
nn.ReLU(),
|
| 26 |
+
nn.Linear(proj_dim, proj_dim),
|
| 27 |
+
)
|
| 28 |
+
self.risk_head = nn.Sequential(
|
| 29 |
+
nn.Linear(proj_dim, proj_dim // 2),
|
| 30 |
+
nn.ReLU(),
|
| 31 |
+
nn.Linear(proj_dim // 2, 1),
|
| 32 |
+
nn.Sigmoid(),
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
def forward(self, img, elev=None):
|
| 36 |
+
x = self.backbone(img)
|
| 37 |
+
x = self.reduce(x)
|
| 38 |
+
if elev is None:
|
| 39 |
+
elev = torch.zeros(x.size(0), 1, img.size(2), img.size(3), device=x.device)
|
| 40 |
+
elev = F.interpolate(elev, size=x.shape[2:], mode="bilinear", align_corners=False)
|
| 41 |
+
e = self.elev_conv(elev)
|
| 42 |
+
x = self.conv_head(torch.cat([x, e], 1))
|
| 43 |
+
p = self.proj(x)
|
| 44 |
+
p = F.normalize(p, dim=1)
|
| 45 |
+
risk = self.risk_head(p).squeeze(-1)
|
| 46 |
+
return x, p, risk
|
t.sh
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -e
|
| 3 |
+
|
| 4 |
+
# === Create folder structure ===
|
| 5 |
+
mkdir -p geo-risk-space/src geo-risk-space/model geo-risk-space/keys
|
| 6 |
+
|
| 7 |
+
# === model placeholder ===
|
| 8 |
+
cat > geo-risk-space/model/README.txt <<'EOF'
|
| 9 |
+
Place your trained model weights here as:
|
| 10 |
+
geo-risk-space/model/geo_model.pth
|
| 11 |
+
EOF
|
| 12 |
+
|
| 13 |
+
# === src/model.py ===
|
| 14 |
+
cat > geo-risk-space/src/model.py <<'EOF'
|
| 15 |
+
import torch
|
| 16 |
+
import torch.nn as nn
|
| 17 |
+
import torch.nn.functional as F
|
| 18 |
+
import torchvision.models as models
|
| 19 |
+
|
| 20 |
+
class CompactGeoEmbed(nn.Module):
|
| 21 |
+
def __init__(self, embed_c=32, proj_dim=96, pretrained=False):
|
| 22 |
+
super().__init__()
|
| 23 |
+
backbone = models.mobilenet_v2(
|
| 24 |
+
weights=None if not pretrained else models.MobileNet_V2_Weights.IMAGENET1K_V1
|
| 25 |
+
).features
|
| 26 |
+
self.backbone = backbone
|
| 27 |
+
self.reduce = nn.Conv2d(1280, embed_c, 1)
|
| 28 |
+
self.elev_conv = nn.Conv2d(1, embed_c, 3, padding=1)
|
| 29 |
+
self.conv_head = nn.Sequential(
|
| 30 |
+
nn.Conv2d(embed_c * 2, embed_c, 3, padding=1),
|
| 31 |
+
nn.ReLU(True),
|
| 32 |
+
nn.Conv2d(embed_c, embed_c, 3, padding=1),
|
| 33 |
+
nn.ReLU(True),
|
| 34 |
+
)
|
| 35 |
+
self.proj = nn.Sequential(
|
| 36 |
+
nn.AdaptiveAvgPool2d(1),
|
| 37 |
+
nn.Flatten(),
|
| 38 |
+
nn.Linear(embed_c, proj_dim),
|
| 39 |
+
nn.ReLU(),
|
| 40 |
+
nn.Linear(proj_dim, proj_dim),
|
| 41 |
+
)
|
| 42 |
+
self.risk_head = nn.Sequential(
|
| 43 |
+
nn.Linear(proj_dim, proj_dim // 2),
|
| 44 |
+
nn.ReLU(),
|
| 45 |
+
nn.Linear(proj_dim // 2, 1),
|
| 46 |
+
nn.Sigmoid(),
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
def forward(self, img, elev=None):
|
| 50 |
+
x = self.backbone(img)
|
| 51 |
+
x = self.reduce(x)
|
| 52 |
+
if elev is None:
|
| 53 |
+
elev = torch.zeros(x.size(0), 1, img.size(2), img.size(3), device=x.device)
|
| 54 |
+
elev = F.interpolate(elev, size=x.shape[2:], mode="bilinear", align_corners=False)
|
| 55 |
+
e = self.elev_conv(elev)
|
| 56 |
+
x = self.conv_head(torch.cat([x, e], 1))
|
| 57 |
+
p = self.proj(x)
|
| 58 |
+
p = F.normalize(p, dim=1)
|
| 59 |
+
risk = self.risk_head(p).squeeze(-1)
|
| 60 |
+
return x, p, risk
|
| 61 |
+
EOF
|
| 62 |
+
|
| 63 |
+
# === app.py ===
|
| 64 |
+
cat > geo-risk-space/app.py <<'EOF'
|
| 65 |
+
import os, json, io, torch, requests, numpy as np, gradio as gr, ee
|
| 66 |
+
from PIL import Image
|
| 67 |
+
import torchvision.transforms as T
|
| 68 |
+
from huggingface_hub import hf_hub_download
|
| 69 |
+
from src.model import CompactGeoEmbed
|
| 70 |
+
|
| 71 |
+
MODEL_LOCAL = "/app/model/geo_model.pth"
|
| 72 |
+
HF_REPO_ID = "USERNAME/geo-risk-model"
|
| 73 |
+
HF_FILENAME = "geo_model.pth"
|
| 74 |
+
GEE_KEY_PATH = "/app/keys/gee_service_account.json"
|
| 75 |
+
|
| 76 |
+
device = torch.device("cpu")
|
| 77 |
+
|
| 78 |
+
def init_gee():
|
| 79 |
+
if os.path.exists(GEE_KEY_PATH):
|
| 80 |
+
with open(GEE_KEY_PATH) as f:
|
| 81 |
+
svc = json.load(f)
|
| 82 |
+
ee.Initialize(ee.ServiceAccountCredentials(svc["client_email"], GEE_KEY_PATH))
|
| 83 |
+
print("✅ GEE initialized")
|
| 84 |
+
else:
|
| 85 |
+
print("⚠️ Missing GEE key, skipping Earth Engine init")
|
| 86 |
+
|
| 87 |
+
def load_model():
|
| 88 |
+
model = CompactGeoEmbed(32, 96)
|
| 89 |
+
state = None
|
| 90 |
+
if os.path.exists(MODEL_LOCAL):
|
| 91 |
+
state = torch.load(MODEL_LOCAL, map_location="cpu")
|
| 92 |
+
else:
|
| 93 |
+
try:
|
| 94 |
+
p = hf_hub_download(repo_id=HF_REPO_ID, filename=HF_FILENAME)
|
| 95 |
+
state = torch.load(p, map_location="cpu")
|
| 96 |
+
except Exception as e:
|
| 97 |
+
print("⚠️ No weights found:", e)
|
| 98 |
+
if state:
|
| 99 |
+
model.load_state_dict(state)
|
| 100 |
+
model.to(device).eval()
|
| 101 |
+
return model
|
| 102 |
+
|
| 103 |
+
init_gee()
|
| 104 |
+
MODEL = load_model()
|
| 105 |
+
tf = T.Compose([T.Resize((128, 128)), T.ToTensor()])
|
| 106 |
+
|
| 107 |
+
def fetch_satellite(lat, lon):
|
| 108 |
+
p = ee.Geometry.Point([lon, lat])
|
| 109 |
+
srtm = ee.Image("USGS/SRTMGL1_003")
|
| 110 |
+
ndvi = ee.ImageCollection("MODIS/061/MOD13A2").select("NDVI").mean()
|
| 111 |
+
rgb = (
|
| 112 |
+
ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED")
|
| 113 |
+
.filterBounds(p)
|
| 114 |
+
.filterDate("2023-01-01", "2024-01-01")
|
| 115 |
+
.sort("CLOUDY_PIXEL_PERCENTAGE")
|
| 116 |
+
.first()
|
| 117 |
+
)
|
| 118 |
+
region = p.buffer(1000).bounds()
|
| 119 |
+
elev_url = srtm.visualize(min=0, max=3000).getThumbURL(
|
| 120 |
+
{"region": region, "dimensions": "128x128", "format": "png"}
|
| 121 |
+
)
|
| 122 |
+
ndvi_url = ndvi.visualize(min=-2000, max=10000, palette=["white", "green"]).getThumbURL(
|
| 123 |
+
{"region": region, "dimensions": "128x128", "format": "png"}
|
| 124 |
+
)
|
| 125 |
+
elev = Image.open(io.BytesIO(requests.get(elev_url).content)).convert("L")
|
| 126 |
+
ndvi_img = Image.open(io.BytesIO(requests.get(ndvi_url).content)).convert("RGB")
|
| 127 |
+
return elev, ndvi_img
|
| 128 |
+
|
| 129 |
+
def preprocess(img):
|
| 130 |
+
if img is None:
|
| 131 |
+
img = Image.new("RGB", (128, 128), (127, 127, 127))
|
| 132 |
+
return tf(img.convert("RGB")).unsqueeze(0)
|
| 133 |
+
|
| 134 |
+
def predict(lat, lon, img):
|
| 135 |
+
lat, lon = float(lat), float(lon)
|
| 136 |
+
try:
|
| 137 |
+
elev, sat = fetch_satellite(lat, lon)
|
| 138 |
+
except Exception as e:
|
| 139 |
+
elev = Image.new("L", (128, 128), 127)
|
| 140 |
+
sat = img
|
| 141 |
+
x = preprocess(sat).to(device)
|
| 142 |
+
e = torch.tensor(np.array(elev) / 255.0, dtype=torch.float32).unsqueeze(0).unsqueeze(0).to(device)
|
| 143 |
+
with torch.no_grad():
|
| 144 |
+
_, _, r = MODEL(x, e)
|
| 145 |
+
return f"Predicted Risk Score: {float(r.item()):.4f}"
|
| 146 |
+
|
| 147 |
+
with gr.Blocks() as demo:
|
| 148 |
+
gr.Markdown("## 🌍 Geo-Risk Prediction (GEE-powered)")
|
| 149 |
+
lat = gr.Number(value=51.5072, label="Latitude")
|
| 150 |
+
lon = gr.Number(value=-0.1276, label="Longitude")
|
| 151 |
+
img = gr.Image(type="pil", label="Optional RGB (Sentinel-2 fallback)")
|
| 152 |
+
out = gr.Textbox(label="Prediction")
|
| 153 |
+
gr.Button("Run").click(fn=predict, inputs=[lat, lon, img], outputs=out)
|
| 154 |
+
|
| 155 |
+
if __name__ == "__main__":
|
| 156 |
+
demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
|
| 157 |
+
EOF
|
| 158 |
+
|
| 159 |
+
# === requirements.txt ===
|
| 160 |
+
cat > geo-risk-space/requirements.txt <<'EOF'
|
| 161 |
+
torch
|
| 162 |
+
torchvision
|
| 163 |
+
gradio
|
| 164 |
+
pillow
|
| 165 |
+
requests
|
| 166 |
+
huggingface_hub
|
| 167 |
+
earthengine-api
|
| 168 |
+
EOF
|
| 169 |
+
|
| 170 |
+
# === README.md ===
|
| 171 |
+
cat > geo-risk-space/README.md <<'EOF'
|
| 172 |
+
# 🌍 Geo-Risk Prediction (GEE + Gradio)
|
| 173 |
+
|
| 174 |
+
### Setup
|
| 175 |
+
|
| 176 |
+
```bash
|
| 177 |
+
bash setup_space.sh
|
| 178 |
+
cd geo-risk-space
|
| 179 |
+
pip install -r requirements.txt
|
| 180 |
+
python app.py
|
upload-hf.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# upload_to_space_recursive.py
|
| 2 |
+
import os
|
| 3 |
+
from huggingface_hub import HfApi, upload_file, create_repo
|
| 4 |
+
|
| 5 |
+
# === CONFIGURATION ===
|
| 6 |
+
HF_TOKEN = os.environ.get("HF_TOKEN") # export HF_TOKEN="hf_xxx..."
|
| 7 |
+
REPO_ID = "Pingsz/hack-nation-3rd1" # your Hugging Face Space
|
| 8 |
+
REPO_TYPE = "space"
|
| 9 |
+
LOCAL_DIR = "geo-risk-space" # folder to upload
|
| 10 |
+
|
| 11 |
+
api = HfApi(token=HF_TOKEN)
|
| 12 |
+
|
| 13 |
+
# Create repo if doesn't exist
|
| 14 |
+
try:
|
| 15 |
+
api.create_repo(repo_id=REPO_ID, repo_type=REPO_TYPE, private=False)
|
| 16 |
+
print(f"✅ Created Space: {REPO_ID}")
|
| 17 |
+
except Exception as e:
|
| 18 |
+
print(f"ℹ️ Space may already exist: {e}")
|
| 19 |
+
|
| 20 |
+
# Walk directory recursively
|
| 21 |
+
for root, _, files in os.walk(LOCAL_DIR):
|
| 22 |
+
for file in files:
|
| 23 |
+
# Skip cache, compiled pyc, and secret keys
|
| 24 |
+
if file.endswith(".pyc") or "gee_service_account" in file or "theta-decker" in file:
|
| 25 |
+
print(f"⏩ Skipping secret or cache file: {file}")
|
| 26 |
+
continue
|
| 27 |
+
|
| 28 |
+
local_path = os.path.join(root, file)
|
| 29 |
+
# target path inside repo: preserve folder structure under geo-risk-space/
|
| 30 |
+
rel_path = os.path.relpath(local_path, LOCAL_DIR).replace("\\", "/")
|
| 31 |
+
|
| 32 |
+
print(f"⬆️ Uploading {rel_path} ...")
|
| 33 |
+
try:
|
| 34 |
+
upload_file(
|
| 35 |
+
path_or_fileobj=local_path,
|
| 36 |
+
path_in_repo=rel_path,
|
| 37 |
+
repo_id=REPO_ID,
|
| 38 |
+
repo_type=REPO_TYPE,
|
| 39 |
+
token=HF_TOKEN,
|
| 40 |
+
)
|
| 41 |
+
except Exception as e:
|
| 42 |
+
print(f"❌ Upload failed for {file}: {e}")
|
| 43 |
+
|
| 44 |
+
print("✅ Upload complete.")
|
| 45 |
+
print(f"🔗 Check your Space at: https://huggingface.co/spaces/{REPO_ID}")
|