""" Gradio App: Air Quality Prediction System using ANN Predicts next 24 hours of AQI for tier-2 Indian cities. """ import gradio as gr import numpy as np import torch import torch.nn as nn import pickle import json from datetime import datetime, timedelta # Model architecture class AirQualityANN(nn.Module): def __init__(self, input_window, num_features, forecast_horizon): super().__init__() input_dim = input_window * num_features self.net = nn.Sequential( nn.Flatten(), nn.Linear(input_dim, 64), nn.ReLU(), nn.Dropout(0.2), nn.Linear(64, 32), nn.ReLU(), nn.Dropout(0.2), nn.Linear(32, 16), nn.ReLU(), nn.Linear(16, forecast_horizon) ) def forward(self, x): return self.net(x) # Load artifacts with open('scaler_X.pkl', 'rb') as f: scaler_X = pickle.load(f) with open('scaler_y.pkl', 'rb') as f: scaler_y = pickle.load(f) with open('feature_names.json', 'r') as f: feature_names = json.load(f) num_features = len(feature_names) input_window = 72 forecast_horizon = 24 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = AirQualityANN(input_window, num_features, forecast_horizon) model.load_state_dict(torch.load('model.pt', map_location=device)) model.to(device) model.eval() cities = ['Lucknow', 'Noida', 'Bengaluru', 'Jaipur', 'Mumbai', 'Srinagar'] city_dummy_cols = [c for c in feature_names if c.startswith('city_')] city_stats = { 'Lucknow': {'mean': 167, 'std': 80, 'min': 14, 'max': 500}, 'Noida': {'mean': 211, 'std': 90, 'min': 10, 'max': 740}, 'Bengaluru': {'mean': 75, 'std': 40, 'min': 15, 'max': 426}, 'Jaipur': {'mean': 146, 'std': 70, 'min': 2, 'max': 500}, 'Mumbai': {'mean': 118, 'std': 60, 'min': 5, 'max': 500}, 'Srinagar': {'mean': 82, 'std': 35, 'min': 4, 'max': 500}, } def aqi_category(aqi): if aqi <= 50: return 'Good', '#009900' elif aqi <= 100: return 'Satisfactory', '#99CC00' elif aqi <= 200: return 'Moderate', '#FFCC00' elif aqi <= 300: return 'Poor', '#FF6600' elif aqi <= 400: return 'Very Poor', '#CC0000' else: return 'Severe', '#660033' def build_features(aqi_series, city, start_datetime): features = [] for i in range(len(aqi_series)): dt = start_datetime + timedelta(hours=i) hour = dt.hour dow = dt.weekday() month = dt.month row = { 'AQI': aqi_series[i], 'hour_sin': np.sin(2 * np.pi * hour / 24), 'hour_cos': np.cos(2 * np.pi * hour / 24), 'dow_sin': np.sin(2 * np.pi * dow / 7), 'dow_cos': np.cos(2 * np.pi * dow / 7), 'month_sin': np.sin(2 * np.pi * month / 12), 'month_cos': np.cos(2 * np.pi * month / 12), 'Active_Stations': 1, } for c in city_dummy_cols: row[c] = 1 if c == f'city_{city}' else 0 features.append([row.get(f, 0.0) for f in feature_names]) return np.array(features, dtype=np.float32) def generate_synthetic_aqi(city, hours=72): stats = city_stats.get(city, {'mean': 150, 'std': 80, 'min': 10, 'max': 500}) t = np.arange(hours) diurnal = stats['std'] * 0.3 * np.sin(2 * np.pi * t / 24 - np.pi/4) noise = np.random.normal(0, stats['std'] * 0.2, hours) base = np.random.normal(stats['mean'], stats['std'] * 0.5, hours) aqi = base + diurnal + noise return np.clip(aqi, stats['min'], stats['max']).tolist() def predict_aqi(city, aqi_input): if not aqi_input or len(aqi_input.strip()) == 0: aqi_values = generate_synthetic_aqi(city, 72) else: try: aqi_values = [float(v.strip()) for v in aqi_input.split(',')] if len(aqi_values) != 72: return f"Error: Please provide exactly 72 hourly AQI values (comma-separated). You provided {len(aqi_values)}.", None except ValueError: return "Error: Invalid input. Please provide comma-separated numbers.", None start_dt = datetime.now() - timedelta(hours=72) features = build_features(aqi_values, city, start_dt) features_scaled = scaler_X.transform(features).reshape(1, input_window, num_features) with torch.no_grad(): X = torch.tensor(features_scaled, dtype=torch.float32).to(device) pred_scaled = model(X).cpu().numpy() pred = scaler_y.inverse_transform(pred_scaled)[0] forecast_times = [start_dt + timedelta(hours=input_window + i) for i in range(forecast_horizon)] # Build text output lines = [f"### 24-Hour AQI Forecast for {city}", ""] lines.append("| Hour | Predicted AQI | Category |") lines.append("|------|--------------|----------|") for t, aqi in zip(forecast_times, pred): cat, _ = aqi_category(aqi) lines.append(f"| {t.strftime('%a %H:%M')} | **{aqi:.1f}** | {cat} |") # Build plot data hours = [f"+{i+1}h" for i in range(forecast_horizon)] import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(10, 4)) ax.plot(hours, pred, marker='o', linewidth=2, markersize=4, color='#2E86C1') ax.axhline(y=50, color='green', linestyle='--', alpha=0.5, label='Good') ax.axhline(y=100, color='yellowgreen', linestyle='--', alpha=0.5, label='Satisfactory') ax.axhline(y=200, color='orange', linestyle='--', alpha=0.5, label='Moderate') ax.axhline(y=300, color='red', linestyle='--', alpha=0.5, label='Poor') ax.fill_between(hours, 0, 50, color='green', alpha=0.1) ax.fill_between(hours, 50, 100, color='yellowgreen', alpha=0.1) ax.fill_between(hours, 100, 200, color='orange', alpha=0.1) ax.fill_between(hours, 200, 300, color='red', alpha=0.1) ax.fill_between(hours, 300, 500, color='purple', alpha=0.1) ax.set_title(f'Predicted AQI - Next 24 Hours ({city})') ax.set_xlabel('Forecast Hour') ax.set_ylabel('AQI') ax.set_ylim(0, 500) ax.legend(loc='upper right', fontsize=8) ax.grid(True, alpha=0.3) plt.xticks(rotation=45) plt.tight_layout() fig.savefig('forecast_plot.png', dpi=150) plt.close() return "\n".join(lines), 'forecast_plot.png' # Gradio interface with gr.Blocks(title="Air Quality Prediction - Indian Tier-2 Cities") as demo: gr.Markdown(""" # 🌫️ Air Quality Prediction System (ANN) Predicts **next 24 hours of AQI** for tier-2 Indian cities using a trained Artificial Neural Network. **Cities supported:** Lucknow, Noida, Bengaluru, Jaipur, Mumbai, Srinagar **Model:** 4-layer MLP trained on CPCB historical AQI data (2017-2023) **Features:** AQI + temporal cyclical encoding + city one-hot encoding **Input:** Past 72 hours of hourly AQI values """) with gr.Row(): city_dropdown = gr.Dropdown(choices=cities, value='Lucknow', label="Select City") aqi_text = gr.Textbox( label="Past 72h AQI (comma-separated, optional)", placeholder="Leave empty for auto-generated realistic data. Or paste 72 comma-separated AQI values.", lines=2 ) predict_btn = gr.Button("Predict Next 24 Hours", variant="primary") with gr.Row(): output_text = gr.Markdown(label="Forecast Results") with gr.Row(): output_plot = gr.Image(label="Forecast Chart") predict_btn.click(fn=predict_aqi, inputs=[city_dropdown, aqi_text], outputs=[output_text, output_plot]) gr.Markdown(""" --- **AQI Categories:** Good (0-50) | Satisfactory (51-100) | Moderate (101-200) | Poor (201-300) | Very Poor (301-400) | Severe (401-500) """) if __name__ == '__main__': demo.launch()