seriffic commited on
Commit
3f563ac
·
1 Parent(s): 47b27e6

Prithvi-EO 2.0 baked Ida 2021 satellite segmentation

Browse files

NASA/IBM Prithvi-EO 2.0 600M flood-segmentation head run offline over
HLS scenes pre/post Ida 2021, then diffed and shipped as polygons.
This is the 'satellite saw water' layer — independent of the Sandy
extent and the USGS HWM points, comes from a foundation model that
was fine-tuned on labelled flood imagery.

Pre-computed (the model itself isn't in the runtime image): the
specialist just answers 'is this point inside any polygon, what's
the nearest, how confident'. The full live-segmentation pipeline
lands in a later phase as an experiment.

app/flood_layers/prithvi_water.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Prithvi-EO 2.0 (Sen1Floods11) satellite flood inundation specialist.
2
+
3
+ The 300M-parameter Prithvi-EO foundation model (NASA/IBM, Apache-2.0)
4
+ was run twice offline on Hurricane Ida 2021 pre/post HLS Sentinel-2
5
+ scenes over central NYC:
6
+
7
+ pre : HLS.S30.T18TWK.2021237T153809 (2021-08-25, 3% cloud)
8
+ post: HLS.S30.T18TWK.2021245T154911 (2021-09-02, 1% cloud,
9
+ ~12 hours after peak rainfall)
10
+
11
+ The diff (post-water minus pre-water, filtered to ≥3-cell polygons)
12
+ isolates surface water present 12 hours after Ida that wasn't present
13
+ the prior week — i.e., candidate Ida-attributable inundation. We ship
14
+ the resulting polygons as a flood-layer specialist; per query we
15
+ compute proximity from the address to the nearest such polygon.
16
+
17
+ Honest scope:
18
+ - Sub-surface flooding (subway entrances, basement apartments — the
19
+ dominant Ida damage mode in NYC) is not visible to optical satellites.
20
+ - Pluvial street water had largely drained by the Sep 2 16:02Z pass,
21
+ so the residual Prithvi signal mostly captures marsh ponding,
22
+ riverside spillover, and low-lying park inundation.
23
+ - The model fired on Ida itself (a real flood event), not a synthetic
24
+ fallback — that's the architectural value.
25
+ """
26
+ from __future__ import annotations
27
+
28
+ import json
29
+ import math
30
+ from dataclasses import dataclass
31
+ from functools import lru_cache
32
+ from pathlib import Path
33
+
34
+ DATA_DIR = Path(__file__).resolve().parent.parent.parent / "data"
35
+ DOC_ID = "prithvi_water"
36
+ CITATION = ("Prithvi-EO-2.0-300M-TL-Sen1Floods11 (NASA/IBM, Apache-2.0, via "
37
+ "TerraTorch). Hurricane Ida pre/post diff: pre HLS T18TWK "
38
+ "2021-08-25 (3% cloud), post HLS T18TWK 2021-09-02 (1% cloud, "
39
+ "~12h after peak rainfall).")
40
+
41
+
42
+ @dataclass
43
+ class PrithviSummary:
44
+ inside_water_polygon: bool
45
+ nearest_distance_m: float | None
46
+ n_polygons_within_500m: int
47
+ scene_id: str
48
+ scene_date: str
49
+
50
+
51
+ def _haversine_m(lat1, lon1, lat2, lon2):
52
+ R = 6371000.0
53
+ p1, p2 = math.radians(lat1), math.radians(lat2)
54
+ dp = math.radians(lat2 - lat1); dl = math.radians(lon2 - lon1)
55
+ a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2
56
+ return 2 * R * math.asin(math.sqrt(a))
57
+
58
+
59
+ @lru_cache(maxsize=1)
60
+ def _load():
61
+ """Load the merged Prithvi water mask (combined across NYC MGRS tiles)
62
+ as a GeoDataFrame in NYC state plane (EPSG:2263) for fast metric
63
+ distance queries."""
64
+ import geopandas as gpd
65
+ # Prefer the Ida flood-event diff (real flood-attribution signal);
66
+ # fall back to clear-day permanent-water masks if the Ida file is absent.
67
+ candidates = [
68
+ DATA_DIR / "prithvi_ida_2021.geojson",
69
+ DATA_DIR / "prithvi_flood_nyc.geojson",
70
+ ]
71
+ candidates += sorted(DATA_DIR.glob("prithvi_flood_*.geojson"), reverse=True)
72
+ path = next((p for p in candidates if p.exists()), None)
73
+ if path is None:
74
+ return None, None
75
+ with open(path) as f:
76
+ meta = json.load(f)
77
+ g = gpd.read_file(path)
78
+ if g.crs is None:
79
+ g.set_crs("EPSG:4326", inplace=True)
80
+ g = g.to_crs("EPSG:2263")
81
+ return g, meta
82
+
83
+
84
+ def warm() -> None:
85
+ _load()
86
+
87
+
88
+ def summary_for_point(lat: float, lon: float) -> PrithviSummary | None:
89
+ import geopandas as gpd
90
+ from shapely.geometry import Point
91
+ g, meta = _load()
92
+ if g is None:
93
+ return None
94
+ pt_wgs = gpd.GeoSeries([Point(lon, lat)], crs="EPSG:4326")
95
+ pt_2263 = pt_wgs.to_crs("EPSG:2263").iloc[0]
96
+ inside = bool(g.contains(pt_2263).any())
97
+
98
+ # nearest distance (feet -> metres)
99
+ distances_ft = g.geometry.distance(pt_2263)
100
+ nearest_ft = float(distances_ft.min()) if len(distances_ft) else None
101
+ nearest_m = round(nearest_ft / 3.281, 1) if nearest_ft is not None else None
102
+
103
+ within_500m = int((distances_ft <= 500 * 3.281).sum())
104
+
105
+ # The Ida pre/post artifact carries pre_/post_ scene info; the clear-day
106
+ # artifact carries scene_ids[]. Format compactly for either case.
107
+ if "post_scene_id" in meta:
108
+ sid = f"pre {meta['pre_scene_id']} | post {meta['post_scene_id']}"
109
+ sdate = f"pre {meta['pre_scene_date']}, post {meta['post_scene_date']}"
110
+ else:
111
+ sid = meta.get("scene_id") or ", ".join(meta.get("scene_ids", []) or ["unknown"])
112
+ sdate = meta.get("scene_date") or ", ".join(meta.get("scene_dates", []) or ["unknown"])
113
+
114
+ return PrithviSummary(
115
+ inside_water_polygon=inside,
116
+ nearest_distance_m=nearest_m,
117
+ n_polygons_within_500m=within_500m,
118
+ scene_id=sid,
119
+ scene_date=sdate,
120
+ )
data/prithvi_ida_2021.geojson ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9ee784aa4f72a76baa1c09094f4a7bcc757860beb6e031a7656a3a91062a17b6
3
+ size 2046423