Views
No views yet
| # | Channel | Unit |
|---|---|---|
| 0 | DEM (elevation) | meters |
| 1 | Slope | degrees |
| 2 | HAND (Height Above Nearest Drainage) | meters |
| 3 | TWI (Topographic Wetness Index) | unitless |
| 4 | Manning's roughness | unitless |
| 5 | Soil permeability | mm/hr |
| 6 | Rainfall intensity | mm/hr |
| 7 | Storm duration | hours |
| 8 | Return period | years |
pip install torch segmentation-models-pytorch huggingface_hub rasterio numpy scipy1import torch
2import numpy as np
3import json
4import segmentation_models_pytorch as smp
5from huggingface_hub import hf_hub_download
6
7# Download model weights and normalization stats
8weights_path = hf_hub_download("peyterho/flood-depth-unet", "model.pt")
9stats_path = hf_hub_download("peyterho/flood-depth-unet", "stats.json")
10
11# Build and load model
12model = smp.Unet(
13 encoder_name="resnet34",
14 encoder_weights=None,
15 in_channels=9,
16 classes=1,
17 activation=None,
18)
19model.load_state_dict(torch.load(weights_path, map_location="cpu"))
20model.eval()
21
22# Load normalization constants
23with open(stats_path) as f:
24 stats = json.load(f)
25
26means = np.array(stats["means"], dtype=np.float32).reshape(9, 1, 1)
27stds = np.array(stats["stds"], dtype=np.float32).reshape(9, 1, 1)(9, 128, 128). Six channels come from your terrain data, three you set yourself as your climate scenario:1# === YOUR TERRAIN DATA (channels 0–5) ===
2# Each is a 2D numpy array of shape (128, 128) at ~30 m resolution
3
4dem = ... # elevation in meters (from SRTM, Copernicus DEM, or LiDAR)
5slope = ... # terrain slope in degrees
6hand = ... # Height Above Nearest Drainage in meters
7twi = ... # Topographic Wetness Index
8manning = ... # Manning's roughness (0.03=smooth, 0.06=vegetated, 0.15=dense forest)
9soil_perm = ... # soil permeability in mm/hr (5=clay, 15=loam, 50=sand)
10
11# === YOUR CLIMATE SCENARIO (channels 6–8) ===
12# These are scalar values broadcast to the full 128×128 grid
13
14rainfall_intensity = 80.0 # mm/hr — how hard it's raining
15storm_duration = 12.0 # hours — how long the storm lasts
16return_period = 100.0 # years — rarity of the event (2=common, 500=extreme)
17
18H, W = 128, 128
19rain_ch = np.full((H, W), rainfall_intensity, dtype=np.float32)
20dur_ch = np.full((H, W), storm_duration, dtype=np.float32)
21rp_ch = np.full((H, W), return_period, dtype=np.float32)
22
23# === Stack and normalize ===
24raw = np.stack([dem, slope, hand, twi, manning, soil_perm,
25 rain_ch, dur_ch, rp_ch]) # shape: (9, 128, 128)
26normalized = (raw - means) / (stds + 1e-8)
27x = torch.from_numpy(normalized).unsqueeze(0).float() # (1, 9, 128, 128)1with torch.no_grad():
2 depth_map = torch.relu(model(x)).squeeze().numpy() # (128, 128), values in meters
3
4# depth_map[i, j] = predicted water depth at that pixel
5# 0.0 = dry, 1.5 = 1.5 meters of water, etc.1scenarios = {
2 "Moderate storm": ( 40.0, 6.0, 10),
3 "Heavy storm": ( 80.0, 12.0, 50),
4 "Extreme (1-in-500)": (160.0, 24.0, 500),
5}
6
7for name, (rain, dur, rp) in scenarios.items():
8 raw[6, :, :] = rain
9 raw[7, :, :] = dur
10 raw[8, :, :] = rp
11 normed = (raw - means) / (stds + 1e-8)
12 x = torch.from_numpy(normed).unsqueeze(0).float()
13
14 with torch.no_grad():
15 depth = torch.relu(model(x)).squeeze().numpy()
16
17 print(f"{name}: max depth = {depth.max():.2f}m, "
18 f"flooded area = {(depth > 0.01).mean()*100:.1f}%")| Channel | Free source | How to compute |
|---|---|---|
| DEM | Copernicus DEM 30 m or SRTM | Download GeoTIFF for your area |
| Slope | Derived from DEM | np.degrees(np.arctan(np.sqrt(dx**2 + dy**2))) where dy, dx = np.gradient(dem, 30.0) |
| HAND | Derived from DEM | Use pysheds or whitebox (see below) |
| TWI | Derived from DEM | np.log(contributing_area / np.tan(slope_radians)) |
| Manning's n | ESA WorldCover land use → lookup table | Forest = 0.12, grass = 0.05, urban = 0.02, water = 0.03 |
| Soil perm. | SoilGrids or HWSD | Clay = 5, loam = 15, sand = 50 mm/hr |
1dy, dx = np.gradient(dem, 30.0) # 30 m pixel spacing
2slope = np.degrees(np.arctan(np.sqrt(dx**2 + dy**2)))pyshedspip install pysheds1from pysheds.grid import Grid
2
3grid = Grid.from_raster("dem.tif")
4dem = grid.read_raster("dem.tif")
5
6pit_filled = grid.fill_pits(dem)
7flooded = grid.fill_depressions(pit_filled)
8inflated = grid.resolve_flats(flooded)
9fdir = grid.flowdir(inflated)
10acc = grid.accumulation(fdir)
11hand = grid.compute_hand(fdir, dem, acc > 500) # threshold for channel cells1def predict_large_area(model, raw_input, means, stds,
2 tile_size=128, overlap=32):
3 """Predict flood depth over a large raster using tiled inference."""
4 C, H, W = raw_input.shape
5 depth_map = np.zeros((H, W), dtype=np.float32)
6 count_map = np.zeros((H, W), dtype=np.float32)
7 stride = tile_size - overlap
8
9 for y in range(0, H, stride):
10 for x in range(0, W, stride):
11 y1, y2 = y, min(y + tile_size, H)
12 x1, x2 = x, min(x + tile_size, W)
13 patch = raw_input[:, y1:y2, x1:x2]
14
15 # Pad if smaller than tile_size
16 ph, pw = patch.shape[1], patch.shape[2]
17 if ph < tile_size or pw < tile_size:
18 padded = np.zeros((C, tile_size, tile_size), dtype=np.float32)
19 padded[:, :ph, :pw] = patch
20 patch = padded
21
22 normed = (patch - means) / (stds + 1e-8)
23 t = torch.from_numpy(normed).unsqueeze(0).float()
24
25 with torch.no_grad():
26 out = torch.relu(model(t)).squeeze().numpy()
27
28 depth_map[y1:y2, x1:x2] += out[:ph, :pw]
29 count_map[y1:y2, x1:x2] += 1.0
30
31 return depth_map / np.maximum(count_map, 1.0)1import rasterio
2
3# Copy CRS and transform from your input DEM
4with rasterio.open("dem.tif") as src:
5 meta = src.meta.copy()
6
7meta.update(count=1, dtype="float32", nodata=-9999)
8
9with rasterio.open("flood_depth.tif", "w", **meta) as dst:
10 dst.write(depth_map, 1)
11
12# → open flood_depth.tif in QGIS, ArcGIS, or Google Earth Enginescripts/:generate_synthetic_data.py — standalone data generationtrain_full_pipeline.py — end-to-end: data gen → train → evaluate → push to Hublogs/training.log — full training log from the original run (50 epochs, best RMSE 0.94 m at epoch 33)