Pingsz commited on
Commit
0e585d2
·
verified ·
1 Parent(s): bb2e220

Upload folder using huggingface_hub

Browse files
.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 Hack Nation
3
- emoji: 📊
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}")