Spaces:
Sleeping
Sleeping
feat: mark Delhi/NCR on maps; reframe to Delhi; polish README
Browse files- visualizer: overlay Delhi star + NCR bounding box on full WRF-India domain
- app: enable mark_delhi, retitle subtitle/footer to Delhi/NCR (IIT-Delhi)
- README: restructured with badges, tables, run-local, PM2.5 guide
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- README.md +100 -41
- app.py +5 -3
- visualizer.py +32 -0
README.md
CHANGED
|
@@ -9,7 +9,7 @@ python_version: "3.12"
|
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
license: mit
|
| 12 |
-
short_description: PM2.5 forecast demo with saved predictions
|
| 13 |
datasets:
|
| 14 |
- sumit1703/pm25-forecasting-data
|
| 15 |
tags:
|
|
@@ -17,72 +17,131 @@ tags:
|
|
| 17 |
- air-quality
|
| 18 |
- pm25
|
| 19 |
- pollution-forecasting
|
|
|
|
| 20 |
- deep-learning
|
| 21 |
- data-visualization
|
| 22 |
suggested_hardware: cpu-basic
|
| 23 |
---
|
| 24 |
|
| 25 |
-
|
| 26 |
|
| 27 |
-
|
| 28 |
|
| 29 |
-
|
| 30 |
-
trained to forecast PM2.5 air pollution levels across a 140×124 spatial grid over Northern India.
|
| 31 |
|
| 32 |
-
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
| 37 |
|
| 38 |
-
|
| 39 |
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
-
|
| 45 |
-
2. Use the **Forecast Hour** slider to select how far ahead (+1h to +16h)
|
| 46 |
-
3. Compare the last known PM2.5 map (left) with the model's forecast (right)
|
| 47 |
-
4. Read the statistics below the maps
|
| 48 |
|
| 49 |
-
## Model Architecture
|
| 50 |
|
| 51 |
| Component | Details |
|
| 52 |
|-----------|---------|
|
| 53 |
-
| Encoder
|
| 54 |
-
| Spatial
|
| 55 |
-
| Decoder
|
| 56 |
-
| Input
|
| 57 |
-
| Output
|
| 58 |
-
| Training
|
|
|
|
|
|
|
| 59 |
|
| 60 |
-
## Competition Results
|
| 61 |
|
| 62 |
-
|
| 63 |
-
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
|
|
|
|
| 68 |
|
| 69 |
-
|
| 70 |
|
| 71 |
-
|
| 72 |
-
* **Final Rank:** 2
|
| 73 |
-
* **Final Score:** 0.8795
|
| 74 |
-
* **Entries:** 21
|
| 75 |
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |

|
| 79 |
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
-
|
| 83 |
|
| 84 |
-
|
| 85 |
-
APRIL_16, JULY_16, OCT_16, DEC_16. Features include PM2.5, wind components,
|
| 86 |
-
temperature, PBLH, and various emission tracers.
|
| 87 |
|
| 88 |
-
>
|
|
|
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
license: mit
|
| 12 |
+
short_description: Delhi / NCR PM2.5 forecast demo with saved predictions
|
| 13 |
datasets:
|
| 14 |
- sumit1703/pm25-forecasting-data
|
| 15 |
tags:
|
|
|
|
| 17 |
- air-quality
|
| 18 |
- pm25
|
| 19 |
- pollution-forecasting
|
| 20 |
+
- delhi
|
| 21 |
- deep-learning
|
| 22 |
- data-visualization
|
| 23 |
suggested_hardware: cpu-basic
|
| 24 |
---
|
| 25 |
|
| 26 |
+
<div align="center">
|
| 27 |
|
| 28 |
+
# 🌬️ PM2.5 Pollution Forecasting — Delhi / NCR
|
| 29 |
|
| 30 |
+
**Deep-learning air-quality forecasting for Delhi and the National Capital Region**
|
|
|
|
| 31 |
|
| 32 |
+
_ANRF · AISEHack Phase 2 · Theme 2 — Pollution Forecasting · **IIT Delhi**_
|
| 33 |
|
| 34 |
+
[](https://huggingface.co/spaces/sumit1703/pm25-forecasting)
|
| 35 |
+
[](https://huggingface.co/datasets/sumit1703/pm25-forecasting-data)
|
| 36 |
+
[](https://github.com/sumitjadhav1703/pm25-forecasting-demo)
|
| 37 |
+
[](https://www.kaggle.com/competitions/anrf-aise-hack-phase-2-theme-2-pollution-forecasting-iitd)
|
| 38 |
|
| 39 |
+
</div>
|
| 40 |
|
| 41 |
+
---
|
| 42 |
+
|
| 43 |
+
## Overview
|
| 44 |
+
|
| 45 |
+
A **ConvLSTM + Fourier Neural Operator (FNO)** hybrid model forecasts PM2.5 up to **16 hours ahead**.
|
| 46 |
+
Predictions run on a WRF simulation grid (140 × 124, ~25 km) covering India, with **Delhi and the
|
| 47 |
+
surrounding NCR highlighted** on every map — the focus region of the IIT-Delhi competition.
|
| 48 |
+
|
| 49 |
+
> **Note:** This Space visualizes precomputed predictions saved from the Kaggle GPU run.
|
| 50 |
+
> It does **not** run live model inference, training, or `torch` at runtime.
|
| 51 |
+
|
| 52 |
+
---
|
| 53 |
+
|
| 54 |
+
## 🖱️ How to Use
|
| 55 |
|
| 56 |
+
| Step | Action |
|
| 57 |
+
|------|--------|
|
| 58 |
+
| 1 | Drag the **Test Window** slider to pick a time period from the test set |
|
| 59 |
+
| 2 | Drag the **Forecast Hour** slider to choose how far ahead (+1h → +16h) |
|
| 60 |
+
| 3 | Compare the last-known map (left) with the model's forecast (right) — **Delhi / NCR is boxed and starred** |
|
| 61 |
+
| 4 | Read the forecast statistics below the maps |
|
| 62 |
|
| 63 |
+
---
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
+
## 🧠 Model Architecture
|
| 66 |
|
| 67 |
| Component | Details |
|
| 68 |
|-----------|---------|
|
| 69 |
+
| **Encoder** | Stacked ConvLSTM (2 layers) |
|
| 70 |
+
| **Spatial operator** | Fourier Neural Operator (FNO) |
|
| 71 |
+
| **Decoder** | UNet with SE blocks |
|
| 72 |
+
| **Input** | 10 hours × 20 atmospheric features × 140 × 124 grid |
|
| 73 |
+
| **Output** | 16-hour PM2.5 forecast |
|
| 74 |
+
| **Training** | Kaggle T4 GPU · ~8 hours |
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
|
| 78 |
+
## 🏆 Competition Results
|
| 79 |
|
| 80 |
+
| | |
|
| 81 |
+
|---|---|
|
| 82 |
+
| **Competition** | ANRF · AISEHack Phase 2 · Theme 2 — Pollution Forecasting (IIT Delhi) |
|
| 83 |
+
| **Team** | Binary Bombers |
|
| 84 |
+
| **Phase 2 Rank** | 🥈 **2** of 21 |
|
| 85 |
+
| **Final Score** | **0.8795** (sMAPE-based) |
|
| 86 |
|
| 87 |
+
<details>
|
| 88 |
+
<summary><strong>📊 Kaggle leaderboard proof</strong></summary>
|
| 89 |
|
| 90 |
+
<br>
|
| 91 |
|
| 92 |
+
Final private leaderboard for **ANRF – AISEHack – Phase 2 – Theme 2 – Pollution Forecasting (IITD)**:
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
+
- **Team:** Binary Bombers
|
| 95 |
+
- **Final Rank:** 2
|
| 96 |
+
- **Final Score:** 0.8795
|
| 97 |
+
- **Entries:** 21
|
| 98 |
|
| 99 |

|
| 100 |
|
| 101 |
+
_Kaggle competition pages may require login to view the leaderboard._
|
| 102 |
+
[View competition →](https://www.kaggle.com/competitions/anrf-aise-hack-phase-2-theme-2-pollution-forecasting-iitd)
|
| 103 |
+
|
| 104 |
+
</details>
|
| 105 |
+
|
| 106 |
+
---
|
| 107 |
+
|
| 108 |
+
## 📦 Dataset
|
| 109 |
+
|
| 110 |
+
4 months of WRF-simulated atmospheric data — **APRIL_16, JULY_16, OCT_16, DEC_16** —
|
| 111 |
+
covering the India domain with a focus on Delhi / NCR air quality. Features include
|
| 112 |
+
PM2.5, wind components, temperature, PBLH, and various emission tracers.
|
| 113 |
+
|
| 114 |
+
Hosted at 🤗 [`sumit1703/pm25-forecasting-data`](https://huggingface.co/datasets/sumit1703/pm25-forecasting-data).
|
| 115 |
+
|
| 116 |
+
---
|
| 117 |
+
|
| 118 |
+
## 🚀 Run Locally
|
| 119 |
+
|
| 120 |
+
```bash
|
| 121 |
+
git clone https://github.com/sumitjadhav1703/pm25-forecasting-demo
|
| 122 |
+
cd pm25-forecasting-demo
|
| 123 |
+
pip install -r requirements.txt
|
| 124 |
+
python app.py # serves on http://0.0.0.0:7860
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
Predictions download automatically from the HF dataset on first launch (cached to `/tmp`).
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## 🗺️ PM2.5 Guide
|
| 132 |
+
|
| 133 |
+
| Range (μg/m³) | Level |
|
| 134 |
+
|---|---|
|
| 135 |
+
| 0 – 15 | 🟢 Good |
|
| 136 |
+
| 15 – 35 | 🟡 Moderate |
|
| 137 |
+
| 35 – 55 | 🟠 Sensitive |
|
| 138 |
+
| 55 – 150 | 🔴 Unhealthy |
|
| 139 |
+
| 150+ | 🟣 Hazardous |
|
| 140 |
+
|
| 141 |
+
---
|
| 142 |
|
| 143 |
+
<div align="center">
|
| 144 |
|
| 145 |
+
Built by **Sumit** — B.Tech AI & Data Science, JNEC MGM University
|
|
|
|
|
|
|
| 146 |
|
| 147 |
+
</div>
|
app.py
CHANGED
|
@@ -50,6 +50,7 @@ def update(window_slider: int, hour_slider: int):
|
|
| 50 |
title=f"Input PM2.5 — Last Known Hour\n(Test window {original_window})",
|
| 51 |
vmin=vmin, vmax=vmax,
|
| 52 |
lat=LAT, lon=LON,
|
|
|
|
| 53 |
)
|
| 54 |
|
| 55 |
pred_img = make_heatmap(
|
|
@@ -57,6 +58,7 @@ def update(window_slider: int, hour_slider: int):
|
|
| 57 |
title=f"Predicted PM2.5 — +{h + 1}h Forecast\n(Test window {original_window})",
|
| 58 |
vmin=vmin, vmax=vmax,
|
| 59 |
lat=LAT, lon=LON,
|
|
|
|
| 60 |
)
|
| 61 |
|
| 62 |
stats = compute_stats(pred_frame, input_frame)
|
|
@@ -71,8 +73,8 @@ with gr.Blocks(
|
|
| 71 |
) as demo:
|
| 72 |
|
| 73 |
gr.Markdown("# 🌬️ PM2.5 Pollution Forecasting")
|
| 74 |
-
gr.HTML('<p class="subtitle">ANRF AISEHack Phase 2 — Deep Learning
|
| 75 |
-
gr.Markdown("This demo visualizes precomputed predictions from saved `.npy` files. It does not run live model inference.")
|
| 76 |
|
| 77 |
with gr.Row():
|
| 78 |
with gr.Column(scale=1):
|
|
@@ -112,7 +114,7 @@ with gr.Blocks(
|
|
| 112 |
---
|
| 113 |
**Model:** ConvLSTM encoder + Fourier Neural Operator (FNO) hybrid
|
| 114 |
**Training:** Kaggle T4 GPU · ANRF competition dataset · 4 months × 16 atmospheric features
|
| 115 |
-
**Grid:** 140 × 124 spatial points ·
|
| 116 |
**Input:** 10 hours of atmospheric data → **Output:** 16-hour PM2.5 forecast
|
| 117 |
**Competition Rank:** 2 · Final Score: 0.8795 (sMAPE-based)
|
| 118 |
""")
|
|
|
|
| 50 |
title=f"Input PM2.5 — Last Known Hour\n(Test window {original_window})",
|
| 51 |
vmin=vmin, vmax=vmax,
|
| 52 |
lat=LAT, lon=LON,
|
| 53 |
+
mark_delhi=True,
|
| 54 |
)
|
| 55 |
|
| 56 |
pred_img = make_heatmap(
|
|
|
|
| 58 |
title=f"Predicted PM2.5 — +{h + 1}h Forecast\n(Test window {original_window})",
|
| 59 |
vmin=vmin, vmax=vmax,
|
| 60 |
lat=LAT, lon=LON,
|
| 61 |
+
mark_delhi=True,
|
| 62 |
)
|
| 63 |
|
| 64 |
stats = compute_stats(pred_frame, input_frame)
|
|
|
|
| 73 |
) as demo:
|
| 74 |
|
| 75 |
gr.Markdown("# 🌬️ PM2.5 Pollution Forecasting")
|
| 76 |
+
gr.HTML('<p class="subtitle">ANRF AISEHack Phase 2 (IIT Delhi) — Deep Learning PM2.5 Forecast · Delhi / NCR</p>')
|
| 77 |
+
gr.Markdown("This demo visualizes precomputed predictions from saved `.npy` files. It does not run live model inference. **Delhi and the surrounding NCR are marked on each map.**")
|
| 78 |
|
| 79 |
with gr.Row():
|
| 80 |
with gr.Column(scale=1):
|
|
|
|
| 114 |
---
|
| 115 |
**Model:** ConvLSTM encoder + Fourier Neural Operator (FNO) hybrid
|
| 116 |
**Training:** Kaggle T4 GPU · ANRF competition dataset · 4 months × 16 atmospheric features
|
| 117 |
+
**Grid:** 140 × 124 spatial points · WRF domain over India · **Delhi / NCR highlighted**
|
| 118 |
**Input:** 10 hours of atmospheric data → **Output:** 16-hour PM2.5 forecast
|
| 119 |
**Competition Rank:** 2 · Final Score: 0.8795 (sMAPE-based)
|
| 120 |
""")
|
visualizer.py
CHANGED
|
@@ -5,8 +5,14 @@ import matplotlib
|
|
| 5 |
matplotlib.use("Agg") # non-interactive backend — required for server/Gradio
|
| 6 |
import matplotlib.pyplot as plt
|
| 7 |
import matplotlib.colors as mcolors
|
|
|
|
| 8 |
from PIL import Image
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
# WHO/AEQ PM2.5 breakpoints (μg/m³) for color bands annotation
|
| 11 |
PM25_LEVELS = [
|
| 12 |
(0, 15, "Good", "#00e400"),
|
|
@@ -26,6 +32,7 @@ def make_heatmap(
|
|
| 26 |
lon: np.ndarray = None,
|
| 27 |
figsize: tuple = (6, 5),
|
| 28 |
dpi: int = 110,
|
|
|
|
| 29 |
) -> Image.Image:
|
| 30 |
"""
|
| 31 |
Render a PM2.5 spatial heatmap.
|
|
@@ -83,6 +90,31 @@ def make_heatmap(
|
|
| 83 |
for spine in ax.spines.values():
|
| 84 |
spine.set_edgecolor("#444444")
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
ax.set_title(title, color="white", fontsize=9, pad=8, fontweight="bold")
|
| 87 |
|
| 88 |
plt.tight_layout()
|
|
|
|
| 5 |
matplotlib.use("Agg") # non-interactive backend — required for server/Gradio
|
| 6 |
import matplotlib.pyplot as plt
|
| 7 |
import matplotlib.colors as mcolors
|
| 8 |
+
from matplotlib.patches import Rectangle
|
| 9 |
from PIL import Image
|
| 10 |
|
| 11 |
+
# Delhi / NCR reference geometry (used to annotate the full-domain map)
|
| 12 |
+
DELHI_LON, DELHI_LAT = 77.21, 28.61 # Delhi city centre
|
| 13 |
+
NCR_LON = (74.0, 80.0) # National Capital Region bounds
|
| 14 |
+
NCR_LAT = (26.0, 31.0)
|
| 15 |
+
|
| 16 |
# WHO/AEQ PM2.5 breakpoints (μg/m³) for color bands annotation
|
| 17 |
PM25_LEVELS = [
|
| 18 |
(0, 15, "Good", "#00e400"),
|
|
|
|
| 32 |
lon: np.ndarray = None,
|
| 33 |
figsize: tuple = (6, 5),
|
| 34 |
dpi: int = 110,
|
| 35 |
+
mark_delhi: bool = False,
|
| 36 |
) -> Image.Image:
|
| 37 |
"""
|
| 38 |
Render a PM2.5 spatial heatmap.
|
|
|
|
| 90 |
for spine in ax.spines.values():
|
| 91 |
spine.set_edgecolor("#444444")
|
| 92 |
|
| 93 |
+
# Delhi / NCR annotation on the full geographic domain
|
| 94 |
+
if mark_delhi and lat is not None and lon is not None:
|
| 95 |
+
ax.add_patch(Rectangle(
|
| 96 |
+
(NCR_LON[0], NCR_LAT[0]),
|
| 97 |
+
NCR_LON[1] - NCR_LON[0],
|
| 98 |
+
NCR_LAT[1] - NCR_LAT[0],
|
| 99 |
+
fill=False, edgecolor="#00e5ff", linewidth=1.4,
|
| 100 |
+
linestyle="--", zorder=5,
|
| 101 |
+
))
|
| 102 |
+
ax.plot(
|
| 103 |
+
DELHI_LON, DELHI_LAT,
|
| 104 |
+
marker="*", markersize=12,
|
| 105 |
+
markerfacecolor="#00e5ff", markeredgecolor="black",
|
| 106 |
+
markeredgewidth=0.6, zorder=6, label="Delhi (NCR)",
|
| 107 |
+
)
|
| 108 |
+
ax.annotate(
|
| 109 |
+
"Delhi",
|
| 110 |
+
xy=(DELHI_LON, DELHI_LAT),
|
| 111 |
+
xytext=(DELHI_LON + 1.6, DELHI_LAT + 1.4),
|
| 112 |
+
color="#00e5ff", fontsize=8, fontweight="bold", zorder=6,
|
| 113 |
+
)
|
| 114 |
+
leg = ax.legend(loc="lower left", fontsize=7, framealpha=0.35)
|
| 115 |
+
for txt in leg.get_texts():
|
| 116 |
+
txt.set_color("white")
|
| 117 |
+
|
| 118 |
ax.set_title(title, color="white", fontsize=9, pad=8, fontweight="bold")
|
| 119 |
|
| 120 |
plt.tight_layout()
|