Spaces:
Sleeping
Sleeping
feat(conus): add tiled CONUS nowcast pipeline, dynamic heatmap sampling, and UI coverage option; fix fallback lat/lon and overlay threshold
Browse files
app.py
CHANGED
@@ -344,7 +344,14 @@ def fetch_real_time_radar_data(location="Toronto, ON"):
|
|
344 |
except Exception as e:
|
345 |
print(f"β Failed to fetch real-time radar data: {e}")
|
346 |
print("π Falling back to temporal patterns...")
|
347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
348 |
|
349 |
def fetch_canadian_radar_data(lat, lon):
|
350 |
"""
|
@@ -1023,6 +1030,208 @@ def fetch_iowa_nexrad_composites(lat, lon):
|
|
1023 |
print(f"β Iowa Mesonet composite fetch failed: {e}")
|
1024 |
return None
|
1025 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1026 |
def crop_iowa_composite(composite_img, target_lat, target_lon):
|
1027 |
"""
|
1028 |
Crop Iowa Mesonet composite image to region around target location
|
@@ -1289,7 +1498,7 @@ def create_radar_map(forecast_data=None, input_data=None):
|
|
1289 |
if data_source == 'Canadian_MSC_GeoMet':
|
1290 |
# Add Canadian radar WMS overlay
|
1291 |
add_canadian_radar_overlay(m, geo_metadata)
|
1292 |
-
elif data_source
|
1293 |
# Add Iowa Mesonet NEXRAD composite overlay
|
1294 |
add_iowa_mesonet_overlay(m, geo_metadata)
|
1295 |
elif data_source == 'NOAA_NEXRAD_Images':
|
@@ -1350,6 +1559,7 @@ def create_radar_map(forecast_data=None, input_data=None):
|
|
1350 |
data_source_name = {
|
1351 |
'Canadian_MSC_GeoMet': 'π¨π¦ Canadian MSC GeoMet',
|
1352 |
'Iowa_Mesonet_NEXRAD': 'π½ Iowa Mesonet NEXRAD',
|
|
|
1353 |
'NOAA_NEXRAD_Images': 'πΊπΈ NOAA NEXRAD',
|
1354 |
'RainViewer': 'π RainViewer Global',
|
1355 |
'Temporal_Patterns': 'π Weather Patterns'
|
@@ -1575,7 +1785,7 @@ def get_radar_site_coordinates(radar_site):
|
|
1575 |
# Initialize the system
|
1576 |
nowcast_system = RadarNowcastingSystem()
|
1577 |
|
1578 |
-
def run_nowcast_prediction(use_real_time_data=True, location="Toronto, ON"):
|
1579 |
"""Main function to run nowcasting prediction with real radar data"""
|
1580 |
|
1581 |
print(f"===== Nowcast Prediction Started at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} =====")
|
@@ -1583,7 +1793,50 @@ def run_nowcast_prediction(use_real_time_data=True, location="Toronto, ON"):
|
|
1583 |
if use_real_time_data:
|
1584 |
# Fetch real NEXRAD radar data
|
1585 |
print(f"π Fetching real-time NEXRAD radar data for {location}...")
|
1586 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1587 |
|
1588 |
# Get data source info from metadata
|
1589 |
geo_meta = globals().get('_current_geo_metadata', {})
|
@@ -1602,6 +1855,13 @@ def run_nowcast_prediction(use_real_time_data=True, location="Toronto, ON"):
|
|
1602 |
data_info = f"β
REAL NEXRAD radar data from {radar_site} ({location})\nπΊπΈ Level II radar processing\nπ‘ Direct AWS S3 access"
|
1603 |
elif data_source == 'RainViewer':
|
1604 |
data_info = f"β
REAL RainViewer global radar composite ({location})\nπ Multi-national radar mosaic\nπ‘ Public weather radar network"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1605 |
else:
|
1606 |
data_info = f"π Real weather-informed patterns ({location})\nβ° Generated at {datetime.now().strftime('%H:%M:%S')}\nπ€οΈ Based on current weather conditions"
|
1607 |
else:
|
@@ -1740,6 +2000,13 @@ def create_interface():
|
|
1740 |
label="Data Source",
|
1741 |
info="Real NEXRAD/RainViewer vs patterns"
|
1742 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1743 |
|
1744 |
with gr.Row():
|
1745 |
predict_btn = gr.Button("π Generate Nowcast",
|
@@ -1769,7 +2036,7 @@ def create_interface():
|
|
1769 |
|
1770 |
predict_btn.click(
|
1771 |
fn=run_nowcast_prediction,
|
1772 |
-
inputs=[data_mode, location_input],
|
1773 |
outputs=[input_plot, forecast_plot, status_text]
|
1774 |
)
|
1775 |
|
@@ -1936,12 +2203,18 @@ def add_forecast_overlay(m, forecast_data, geo_metadata):
|
|
1936 |
for t, frame in enumerate(forecast_frames):
|
1937 |
lat_range = np.linspace(bbox['south'], bbox['north'], frame.shape[0])
|
1938 |
lon_range = np.linspace(bbox['west'], bbox['east'], frame.shape[1])
|
1939 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
1940 |
# Create heatmap points for significant precipitation areas
|
1941 |
heat_data = []
|
1942 |
-
for i in range(0,
|
1943 |
-
for j in range(0,
|
1944 |
-
if frame[i, j] > 0.
|
1945 |
intensity = float(frame[i, j])
|
1946 |
heat_data.append([lat_range[i], lon_range[j], intensity])
|
1947 |
|
@@ -2025,4 +2298,4 @@ def add_forecast_overlay(m, forecast_data, geo_metadata):
|
|
2025 |
if __name__ == "__main__":
|
2026 |
demo = create_interface()
|
2027 |
demo.launch(server_name="0.0.0.0", server_port=7860, share=True)
|
2028 |
-
|
|
|
344 |
except Exception as e:
|
345 |
print(f"β Failed to fetch real-time radar data: {e}")
|
346 |
print("π Falling back to temporal patterns...")
|
347 |
+
# Ensure we have sane defaults if geocoding/requests failed before lat/lon assignment
|
348 |
+
try:
|
349 |
+
_lat = lat # may be undefined
|
350 |
+
_lon = lon
|
351 |
+
except Exception:
|
352 |
+
# Default to central US to keep map + forecast sensible
|
353 |
+
_lat, _lon = 39.0, -98.0
|
354 |
+
return create_temporal_weather_patterns(_lat, _lon)
|
355 |
|
356 |
def fetch_canadian_radar_data(lat, lon):
|
357 |
"""
|
|
|
1030 |
print(f"β Iowa Mesonet composite fetch failed: {e}")
|
1031 |
return None
|
1032 |
|
1033 |
+
def fetch_iowa_conus_composites():
|
1034 |
+
"""Fetch CONUS-wide NEXRAD composites (entire US) from Iowa Mesonet."""
|
1035 |
+
try:
|
1036 |
+
print("πΊοΈ Fetching Iowa Mesonet CONUS-wide NEXRAD composites...")
|
1037 |
+
radar_images = []
|
1038 |
+
current_time = datetime.utcnow()
|
1039 |
+
|
1040 |
+
for i in range(4):
|
1041 |
+
target_time = current_time - timedelta(minutes=i * 5)
|
1042 |
+
time_str = target_time.strftime('%Y%m%d%H%M')
|
1043 |
+
composite_urls = [
|
1044 |
+
f"https://mesonet.agron.iastate.edu/data/gis/images/4326/USCOMP/n0q_{time_str}.png",
|
1045 |
+
f"https://mesonet.agron.iastate.edu/data/gis/images/4326/USCOMP/n0r_{time_str}.png"
|
1046 |
+
]
|
1047 |
+
|
1048 |
+
got = False
|
1049 |
+
for url in composite_urls:
|
1050 |
+
try:
|
1051 |
+
headers = {
|
1052 |
+
'User-Agent': 'DeepNowcast/1.0 (Research Application)',
|
1053 |
+
'Accept': 'image/png,image/*,*/*'
|
1054 |
+
}
|
1055 |
+
resp = requests.get(url, timeout=15, headers=headers)
|
1056 |
+
if resp.status_code == 200 and len(resp.content) > 5000:
|
1057 |
+
comp = Image.open(io.BytesIO(resp.content))
|
1058 |
+
# Convert to grayscale, resize to model input size
|
1059 |
+
comp = comp.convert('L').resize((256, 256), Image.Resampling.BILINEAR)
|
1060 |
+
radar_images.append(comp)
|
1061 |
+
print(f"β
CONUS composite fetched for {target_time.strftime('%H:%M')}")
|
1062 |
+
got = True
|
1063 |
+
break
|
1064 |
+
except Exception as e:
|
1065 |
+
print(f"β οΈ Error fetching CONUS composite: {e}")
|
1066 |
+
if not got:
|
1067 |
+
print(f"β οΈ Missing CONUS composite for {target_time.strftime('%H:%M')}")
|
1068 |
+
|
1069 |
+
radar_images.reverse()
|
1070 |
+
|
1071 |
+
if len(radar_images) >= 2:
|
1072 |
+
while len(radar_images) < 4:
|
1073 |
+
radar_images.append(radar_images[-1])
|
1074 |
+
|
1075 |
+
# Set CONUS geospatial metadata
|
1076 |
+
globals()['_current_geo_metadata'] = {
|
1077 |
+
'center_lat': 39.0,
|
1078 |
+
'center_lon': -98.0,
|
1079 |
+
'data_source': 'Iowa_Mesonet_CONUS',
|
1080 |
+
'product_type': 'NEXRAD_CONUS_Composite',
|
1081 |
+
'bbox': {
|
1082 |
+
'north': 50.0,
|
1083 |
+
'south': 20.0,
|
1084 |
+
'east': -65.0,
|
1085 |
+
'west': -125.0
|
1086 |
+
},
|
1087 |
+
'resolution': '1-4km',
|
1088 |
+
'update_frequency': '5 minutes',
|
1089 |
+
'coverage': 'CONUS'
|
1090 |
+
}
|
1091 |
+
print(f"β
Successfully prepared {len(radar_images)} CONUS composites")
|
1092 |
+
return radar_images
|
1093 |
+
else:
|
1094 |
+
print("β Could not fetch sufficient CONUS composites")
|
1095 |
+
return None
|
1096 |
+
except Exception as e:
|
1097 |
+
print(f"β CONUS composite fetch failed: {e}")
|
1098 |
+
return None
|
1099 |
+
|
1100 |
+
def fetch_iowa_conus_composites_full():
|
1101 |
+
"""Fetch full-resolution CONUS NEXRAD composites without resizing (for tiling)."""
|
1102 |
+
try:
|
1103 |
+
print("πΊοΈ Fetching full-resolution Iowa Mesonet CONUS compositesβ¦")
|
1104 |
+
images = []
|
1105 |
+
current_time = datetime.utcnow()
|
1106 |
+
sizes = []
|
1107 |
+
for i in range(4):
|
1108 |
+
target_time = current_time - timedelta(minutes=i * 5)
|
1109 |
+
time_str = target_time.strftime('%Y%m%d%H%M')
|
1110 |
+
urls = [
|
1111 |
+
f"https://mesonet.agron.iastate.edu/data/gis/images/4326/USCOMP/n0q_{time_str}.png",
|
1112 |
+
f"https://mesonet.agron.iastate.edu/data/gis/images/4326/USCOMP/n0r_{time_str}.png"
|
1113 |
+
]
|
1114 |
+
got = False
|
1115 |
+
for url in urls:
|
1116 |
+
try:
|
1117 |
+
headers = {
|
1118 |
+
'User-Agent': 'DeepNowcast/1.0 (Research Application)',
|
1119 |
+
'Accept': 'image/png,image/*,*/*'
|
1120 |
+
}
|
1121 |
+
r = requests.get(url, timeout=15, headers=headers)
|
1122 |
+
if r.status_code == 200 and len(r.content) > 5000:
|
1123 |
+
img = Image.open(io.BytesIO(r.content)).convert('L')
|
1124 |
+
images.append(img)
|
1125 |
+
sizes.append(img.size)
|
1126 |
+
print(f"β
CONUS full composite {i+1}/4 at {time_str}")
|
1127 |
+
got = True
|
1128 |
+
break
|
1129 |
+
except Exception as e:
|
1130 |
+
print(f"β οΈ Error fetching CONUS full composite: {e}")
|
1131 |
+
if not got:
|
1132 |
+
print(f"β οΈ Missing CONUS full composite for {time_str}")
|
1133 |
+
|
1134 |
+
images.reverse()
|
1135 |
+
if len(images) >= 2:
|
1136 |
+
# Normalize sizes by resizing to the smallest common size if needed
|
1137 |
+
if len(set(sizes)) > 1:
|
1138 |
+
min_w = min(w for (w, h) in sizes)
|
1139 |
+
min_h = min(h for (w, h) in sizes)
|
1140 |
+
images = [im.resize((min_w, min_h), Image.Resampling.BILINEAR) for im in images]
|
1141 |
+
common_size = (min_w, min_h)
|
1142 |
+
else:
|
1143 |
+
common_size = sizes[0]
|
1144 |
+
|
1145 |
+
# Update CONUS metadata
|
1146 |
+
globals()['_current_geo_metadata'] = {
|
1147 |
+
'center_lat': 39.0,
|
1148 |
+
'center_lon': -98.0,
|
1149 |
+
'data_source': 'Iowa_Mesonet_CONUS',
|
1150 |
+
'product_type': 'NEXRAD_CONUS_Composite',
|
1151 |
+
'bbox': {
|
1152 |
+
'north': 50.0,
|
1153 |
+
'south': 20.0,
|
1154 |
+
'east': -65.0,
|
1155 |
+
'west': -125.0
|
1156 |
+
},
|
1157 |
+
'resolution': '1-4km',
|
1158 |
+
'update_frequency': '5 minutes',
|
1159 |
+
'coverage': 'CONUS',
|
1160 |
+
'conus_image_size': {'width': common_size[0], 'height': common_size[1]}
|
1161 |
+
}
|
1162 |
+
print(f"β
Prepared {len(images)} CONUS composites at size {common_size}")
|
1163 |
+
return images
|
1164 |
+
else:
|
1165 |
+
print("β Not enough CONUS composites (full) fetched")
|
1166 |
+
return None
|
1167 |
+
except Exception as e:
|
1168 |
+
print(f"β CONUS full composite fetch failed: {e}")
|
1169 |
+
return None
|
1170 |
+
|
1171 |
+
def run_conus_tiled_inference(conus_images, system: RadarNowcastingSystem, tile_size=256, stride=192):
|
1172 |
+
"""Run tiled inference over full CONUS composites and assemble a full forecast tensor.
|
1173 |
+
|
1174 |
+
Returns: (forecast_tensor_np, info_dict)
|
1175 |
+
- forecast_tensor_np shape: (1, 4, H_full, W_full) with values in [0,1]
|
1176 |
+
"""
|
1177 |
+
assert len(conus_images) >= 4
|
1178 |
+
# Use the first image to get full size
|
1179 |
+
full_w, full_h = conus_images[0].size
|
1180 |
+
print(f"π CONUS full size: {full_w}x{full_h}, tile={tile_size}, stride={stride}")
|
1181 |
+
|
1182 |
+
# Compute tile positions
|
1183 |
+
x_positions = list(range(0, max(1, full_w - tile_size + 1), stride))
|
1184 |
+
y_positions = list(range(0, max(1, full_h - tile_size + 1), stride))
|
1185 |
+
if x_positions[-1] != full_w - tile_size:
|
1186 |
+
x_positions.append(max(0, full_w - tile_size))
|
1187 |
+
if y_positions[-1] != full_h - tile_size:
|
1188 |
+
y_positions.append(max(0, full_h - tile_size))
|
1189 |
+
|
1190 |
+
# Accumulators for blending
|
1191 |
+
T = 4
|
1192 |
+
sum_arr = np.zeros((T, full_h, full_w), dtype=np.float32)
|
1193 |
+
weight = np.zeros((full_h, full_w), dtype=np.float32)
|
1194 |
+
|
1195 |
+
# Prepare overlap weighting kernel (cosine window)
|
1196 |
+
wx = np.hanning(tile_size)
|
1197 |
+
wy = np.hanning(tile_size)
|
1198 |
+
win = np.outer(wy, wx).astype(np.float32)
|
1199 |
+
win = win / (win.max() + 1e-6)
|
1200 |
+
|
1201 |
+
tiles_done = 0
|
1202 |
+
for y0 in y_positions:
|
1203 |
+
for x0 in x_positions:
|
1204 |
+
# Extract tile sequence
|
1205 |
+
tile_seq_imgs = [im.crop((x0, y0, x0 + tile_size, y0 + tile_size)) for im in conus_images[:4]]
|
1206 |
+
# Preprocess to tensor
|
1207 |
+
tile_input = system.preprocess_radar_data(tile_seq_imgs) # (1,4,256,256)
|
1208 |
+
# Predict
|
1209 |
+
tile_forecast = system.generate_forecast(tile_input) # numpy (1,4,256,256)
|
1210 |
+
tile_forecast = tile_forecast[0] # (4,256,256)
|
1211 |
+
|
1212 |
+
# Blend into full arrays
|
1213 |
+
for t in range(T):
|
1214 |
+
sum_arr[t, y0:y0+tile_size, x0:x0+tile_size] += tile_forecast[t] * win
|
1215 |
+
weight[y0:y0+tile_size, x0:x0+tile_size] += win
|
1216 |
+
|
1217 |
+
tiles_done += 1
|
1218 |
+
if tiles_done % 20 == 0:
|
1219 |
+
print(f"β¦ processed {tiles_done} tiles")
|
1220 |
+
|
1221 |
+
# Avoid divide by zero
|
1222 |
+
weight = np.clip(weight, 1e-6, None)
|
1223 |
+
assembled = sum_arr / weight[None, :, :]
|
1224 |
+
assembled = np.clip(assembled, 0.0, 1.0)
|
1225 |
+
|
1226 |
+
print(f"π§© Tiling complete: {tiles_done} tiles blended")
|
1227 |
+
# Return with batch dim
|
1228 |
+
return assembled[None, ...], {
|
1229 |
+
'num_tiles': tiles_done,
|
1230 |
+
'tile_size': tile_size,
|
1231 |
+
'stride': stride,
|
1232 |
+
'full_size': (full_h, full_w)
|
1233 |
+
}
|
1234 |
+
|
1235 |
def crop_iowa_composite(composite_img, target_lat, target_lon):
|
1236 |
"""
|
1237 |
Crop Iowa Mesonet composite image to region around target location
|
|
|
1498 |
if data_source == 'Canadian_MSC_GeoMet':
|
1499 |
# Add Canadian radar WMS overlay
|
1500 |
add_canadian_radar_overlay(m, geo_metadata)
|
1501 |
+
elif data_source in ['Iowa_Mesonet_NEXRAD', 'Iowa_Mesonet_CONUS']:
|
1502 |
# Add Iowa Mesonet NEXRAD composite overlay
|
1503 |
add_iowa_mesonet_overlay(m, geo_metadata)
|
1504 |
elif data_source == 'NOAA_NEXRAD_Images':
|
|
|
1559 |
data_source_name = {
|
1560 |
'Canadian_MSC_GeoMet': 'π¨π¦ Canadian MSC GeoMet',
|
1561 |
'Iowa_Mesonet_NEXRAD': 'π½ Iowa Mesonet NEXRAD',
|
1562 |
+
'Iowa_Mesonet_CONUS': 'π½ Iowa Mesonet CONUS',
|
1563 |
'NOAA_NEXRAD_Images': 'πΊπΈ NOAA NEXRAD',
|
1564 |
'RainViewer': 'π RainViewer Global',
|
1565 |
'Temporal_Patterns': 'π Weather Patterns'
|
|
|
1785 |
# Initialize the system
|
1786 |
nowcast_system = RadarNowcastingSystem()
|
1787 |
|
1788 |
+
def run_nowcast_prediction(use_real_time_data=True, location="Toronto, ON", coverage_mode="Local"):
|
1789 |
"""Main function to run nowcasting prediction with real radar data"""
|
1790 |
|
1791 |
print(f"===== Nowcast Prediction Started at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} =====")
|
|
|
1793 |
if use_real_time_data:
|
1794 |
# Fetch real NEXRAD radar data
|
1795 |
print(f"π Fetching real-time NEXRAD radar data for {location}...")
|
1796 |
+
if isinstance(coverage_mode, str) and coverage_mode.lower().startswith('conus'):
|
1797 |
+
print("πΊοΈ Coverage mode: CONUS (entire US)")
|
1798 |
+
# Full-resolution CONUS workflow with tiling
|
1799 |
+
conus_images = fetch_iowa_conus_composites_full()
|
1800 |
+
if conus_images and len(conus_images) >= 2:
|
1801 |
+
# Ensure we have 4 frames
|
1802 |
+
while len(conus_images) < 4:
|
1803 |
+
conus_images.append(conus_images[-1])
|
1804 |
+
|
1805 |
+
# Tile-based inference
|
1806 |
+
assembled_forecast, tiles_info = run_conus_tiled_inference(conus_images, nowcast_system)
|
1807 |
+
|
1808 |
+
# Store forecast and metadata
|
1809 |
+
forecast_tensor = torch.from_numpy(assembled_forecast)
|
1810 |
+
globals()['_current_forecast_data'] = forecast_tensor
|
1811 |
+
|
1812 |
+
geo_meta = globals().get('_current_geo_metadata', {})
|
1813 |
+
data_source = geo_meta.get('data_source', 'Iowa_Mesonet_CONUS')
|
1814 |
+
|
1815 |
+
# Prepare 256Γ256 quicklook for plots
|
1816 |
+
input_quick = [img.resize((256, 256), Image.Resampling.BILINEAR) for img in conus_images[:4]]
|
1817 |
+
input_sequence = nowcast_system.preprocess_radar_data(input_quick)
|
1818 |
+
forecast_quick = torch.nn.functional.interpolate(
|
1819 |
+
forecast_tensor, size=(256, 256), mode='bilinear', align_corners=False
|
1820 |
+
)
|
1821 |
+
|
1822 |
+
input_titles = ['T-15min', 'T-10min', 'T-5min', 'T-0min (Now)']
|
1823 |
+
forecast_titles = ['T+5min', 'T+10min', 'T+15min', 'T+20min']
|
1824 |
+
input_fig = nowcast_system.visualize_sequence(input_sequence.cpu(), input_titles, "Input Radar Sequence (Past 20 minutes)")
|
1825 |
+
forecast_fig = nowcast_system.visualize_sequence(forecast_quick, forecast_titles, "Nowcast Forecast (Next 20 minutes)")
|
1826 |
+
|
1827 |
+
success_info = (
|
1828 |
+
f"β
REAL Iowa Mesonet CONUS composites (entire US)\n"
|
1829 |
+
f"π§© Tiled inference: {tiles_info['num_tiles']} tiles, tile={tiles_info['tile_size']}, stride={tiles_info['stride']}\n"
|
1830 |
+
f"πΊοΈ Assembled forecast size: {assembled_forecast.shape[-2]}x{assembled_forecast.shape[-1]}\n"
|
1831 |
+
f"π― Model: {'DGMR' if nowcast_system.model else 'Mock'} | Device: {nowcast_system.device}"
|
1832 |
+
)
|
1833 |
+
print("π CONUS tiled nowcast completed successfully!")
|
1834 |
+
return input_fig, forecast_fig, success_info
|
1835 |
+
else:
|
1836 |
+
print("β οΈ CONUS composites unavailable, falling back to local workflowβ¦")
|
1837 |
+
radar_images = fetch_real_time_radar_data(location)
|
1838 |
+
else:
|
1839 |
+
radar_images = fetch_real_time_radar_data(location)
|
1840 |
|
1841 |
# Get data source info from metadata
|
1842 |
geo_meta = globals().get('_current_geo_metadata', {})
|
|
|
1855 |
data_info = f"β
REAL NEXRAD radar data from {radar_site} ({location})\nπΊπΈ Level II radar processing\nπ‘ Direct AWS S3 access"
|
1856 |
elif data_source == 'RainViewer':
|
1857 |
data_info = f"β
REAL RainViewer global radar composite ({location})\nπ Multi-national radar mosaic\nπ‘ Public weather radar network"
|
1858 |
+
elif data_source == 'Iowa_Mesonet_CONUS':
|
1859 |
+
data_info = (
|
1860 |
+
"β
REAL Iowa Mesonet CONUS composite (entire US)\n"
|
1861 |
+
"π½ NEXRAD US composite\n"
|
1862 |
+
"β±οΈ Updates every 5 minutes\n"
|
1863 |
+
"π‘ Country-wide coverage"
|
1864 |
+
)
|
1865 |
else:
|
1866 |
data_info = f"π Real weather-informed patterns ({location})\nβ° Generated at {datetime.now().strftime('%H:%M:%S')}\nπ€οΈ Based on current weather conditions"
|
1867 |
else:
|
|
|
2000 |
label="Data Source",
|
2001 |
info="Real NEXRAD/RainViewer vs patterns"
|
2002 |
)
|
2003 |
+
with gr.Column(scale=1):
|
2004 |
+
coverage_mode = gr.Radio(
|
2005 |
+
choices=["Local around location", "CONUS (entire US)"],
|
2006 |
+
value="Local around location",
|
2007 |
+
label="Coverage",
|
2008 |
+
info="Pick entire US to generate a country-wide nowcast"
|
2009 |
+
)
|
2010 |
|
2011 |
with gr.Row():
|
2012 |
predict_btn = gr.Button("π Generate Nowcast",
|
|
|
2036 |
|
2037 |
predict_btn.click(
|
2038 |
fn=run_nowcast_prediction,
|
2039 |
+
inputs=[data_mode, location_input, coverage_mode],
|
2040 |
outputs=[input_plot, forecast_plot, status_text]
|
2041 |
)
|
2042 |
|
|
|
2203 |
for t, frame in enumerate(forecast_frames):
|
2204 |
lat_range = np.linspace(bbox['south'], bbox['north'], frame.shape[0])
|
2205 |
lon_range = np.linspace(bbox['west'], bbox['east'], frame.shape[1])
|
2206 |
+
|
2207 |
+
# Dynamic sampling step to cap total points (~20k)
|
2208 |
+
H, W = frame.shape
|
2209 |
+
target_points = 20000
|
2210 |
+
step = int(max(4, np.sqrt((H * W) / max(1, target_points))))
|
2211 |
+
step = int(min(max(step, 4), 32))
|
2212 |
+
|
2213 |
# Create heatmap points for significant precipitation areas
|
2214 |
heat_data = []
|
2215 |
+
for i in range(0, H, step):
|
2216 |
+
for j in range(0, W, step):
|
2217 |
+
if frame[i, j] > 0.05:
|
2218 |
intensity = float(frame[i, j])
|
2219 |
heat_data.append([lat_range[i], lon_range[j], intensity])
|
2220 |
|
|
|
2298 |
if __name__ == "__main__":
|
2299 |
demo = create_interface()
|
2300 |
demo.launch(server_name="0.0.0.0", server_port=7860, share=True)
|
2301 |
+
|