nakas commited on
Commit
9deff86
Β·
1 Parent(s): 85d2f0b

feat(conus): add tiled CONUS nowcast pipeline, dynamic heatmap sampling, and UI coverage option; fix fallback lat/lon and overlay threshold

Browse files
Files changed (1) hide show
  1. app.py +283 -10
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
- return create_temporal_weather_patterns(lat, lon)
 
 
 
 
 
 
 
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 == 'Iowa_Mesonet_NEXRAD':
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
- radar_images = fetch_real_time_radar_data(location)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, frame.shape[0], 4): # Sample every 4th point for performance
1943
- for j in range(0, frame.shape[1], 4):
1944
- if frame[i, j] > 0.15: # Only show significant precipitation
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
+