""" app.py - Gradio Web Application for Forest Fire Detection Provides a web interface for image classification, video analysis, and model info. """ # `spaces` must be imported before any CUDA-touching library (tensorflow is # imported transitively below via `src.inference`) for Hugging Face ZeroGPU. import spaces import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # Fix invalid SSL_CERT_FILE on Windows (points to non-existent path) ssl_cert = os.environ.get('SSL_CERT_FILE', '') if ssl_cert and not os.path.exists(ssl_cert): del os.environ['SSL_CERT_FILE'] import tempfile import time from pathlib import Path import cv2 import gradio as gr import numpy as np from PIL import Image from src.inference import FireNetInference from src.model import FireNetModel # Default model path (override with MODEL_PATH env var, e.g. as a Space secret) DEFAULT_MODEL = os.environ.get('MODEL_PATH', 'models/FirenetCNN1.h5') # Class descriptions CLASS_INFO = { 'fire': { 'color': 'Red', 'description': 'Active fire detected with high confidence. Immediate attention required.', 'icon': '🔥' }, 'no_fire': { 'color': 'Green', 'description': 'No fire detected. Scene appears safe.', 'icon': '✅' }, 'smoke': { 'color': 'Orange', 'description': 'Smoke detected. May indicate early-stage fire or controlled burn.', 'icon': '💨' } } _engine = None def get_inference_engine() -> FireNetInference: """Get or create the cached inference engine (loaded once per process).""" global _engine if _engine is None: model_path = DEFAULT_MODEL if not os.path.exists(model_path): # Try alternative paths alternatives = [ 'models/FirenetCNN.keras', 'models/FirenetCNN.h5', 'models/firenet_model.h5', 'FirenetCNN1.h5', 'FirenetCNN.h5', ] for alt in alternatives: if os.path.exists(alt): model_path = alt break _engine = FireNetInference(model_path) return _engine @spaces.GPU(duration=30) def predict_image(image, apply_gradcam=True): """Predict on a single image with optional Grad-CAM.""" if image is None: return None, "Please upload an image.", None, None try: engine = get_inference_engine() # Convert PIL to temp file for inference with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp: if isinstance(image, np.ndarray): cv2.imwrite(tmp.name, cv2.cvtColor(image, cv2.COLOR_RGB2BGR)) else: image.save(tmp.name) tmp_path = tmp.name result = engine.predict_image(tmp_path, apply_gradcam=apply_gradcam) # Cleanup os.unlink(tmp_path) # Prepare outputs label = result['label'] confidence = result['confidence'] probs = result['probability_array'] # Create annotated image if result.get('annotated_image') is not None: annotated = cv2.cvtColor(result['annotated_image'], cv2.COLOR_BGR2RGB) elif result.get('heatmap') is not None and result['has_gradcam']: original = cv2.imread(tmp_path) if os.path.exists(tmp_path) else None if original is not None: rgb_orig = cv2.cvtColor(original, cv2.COLOR_BGR2RGB) heatmap = np.array(result['heatmap']) annotated = engine.gradcam.overlay_heatmap(rgb_orig, heatmap, alpha=0.5) else: annotated = np.array(image) else: annotated = np.array(image) # Create probability chart prob_dict = {cls: float(probs[i]) for i, cls in enumerate(FireNetModel.CLASS_LABELS)} # Format result text info = CLASS_INFO.get(label, {}) result_text = f"## {info.get('icon', '')} Prediction: **{label.upper()}**\n" result_text += f"**Confidence:** {confidence*100:.2f}%\n\n" result_text += f"**Details:** {info.get('description', 'N/A')}\n\n" result_text += "### Class Probabilities\n" for cls, prob in prob_dict.items(): bar = '█' * int(prob * 20) result_text += f"- **{cls}**: {prob*100:.1f}% {bar}\n" return annotated, result_text, prob_dict, None except Exception as e: return None, f"Error: {str(e)}", None, None @spaces.GPU(duration=120) def predict_video(video_path, skip_frames=5, apply_gradcam=True): """Process a video file frame-by-frame.""" if video_path is None: return None, "Please upload a video.", None try: engine = get_inference_engine() # Create output path output_path = tempfile.mktemp(suffix='.mp4') stats = engine.predict_video( video_path, output_path=output_path, skip_frames=skip_frames, apply_gradcam=apply_gradcam ) # Format stats result_text = f"## Video Analysis Complete\n\n" result_text += f"**Total Frames:** {stats['total_frames']}\n" result_text += f"**Processed Frames:** {stats['processed_frames']}\n" result_text += f"**Processing Time:** {stats['processing_time_seconds']:.2f}s\n" result_text += f"**FPS:** {stats['processed_frames']/max(stats['processing_time_seconds'],0.001):.1f}\n\n" # Class distribution labels = [f['label'] for f in stats['frame_by_frame'] if f['processed']] if labels: from collections import Counter counts = Counter(labels) result_text += "### Detection Summary\n" for cls, count in counts.most_common(): pct = count / len(labels) * 100 result_text += f"- **{cls}**: {count} frames ({pct:.1f}%)\n" return output_path, result_text, stats except Exception as e: return None, f"Error: {str(e)}", None def get_model_info(): """Get model information and statistics.""" try: engine = get_inference_engine() config = FireNetModel.get_model_config() info_text = f"## Model Information\n\n" info_text += f"**Architecture:** {config['architecture']}\n" info_text += f"**Input Shape:** {config['input_shape']}\n" info_text += f"**Number of Classes:** {config['num_classes']}\n" info_text += f"**Class Labels:** {', '.join(config['class_labels'])}\n" info_text += f"**Learning Rate:** {config['learning_rate']}\n" info_text += f"**Image Size:** {config['image_size']}\n" info_text += f"**Batch Size:** {config['batch_size']}\n" info_text += f"**Grad-CAM Layer:** {config['last_conv_layer']}\n\n" info_text += "### Model Files\n" models_dir = Path('models') if models_dir.exists(): for f in models_dir.glob('*'): if f.suffix in ('.h5', '.keras'): size_mb = f.stat().st_size / (1024 * 1024) info_text += f"- **{f.name}**: {size_mb:.1f} MB\n" info_text += "\n### Class Reference\n" for cls, details in CLASS_INFO.items(): info_text += f"- {details['icon']} **{cls}**: {details['description']}\n" return info_text except Exception as e: return f"Error loading model info: {str(e)}" # Build Gradio interface with gr.Blocks(title="Forest Fire Detection - FirenetCNN") as demo: gr.Markdown( """ # 🔥 Forest Fire Detection using FirenetCNN and XAI Techniques Detect and classify forest fires from images and videos using deep learning with explainable AI (Grad-CAM). **Classes:** `fire` | `no_fire` | `smoke` """ ) with gr.Tabs(): # Tab 1: Image Classification with gr.Tab("📷 Image Classification"): with gr.Row(): with gr.Column(scale=1): image_input = gr.Image(type="pil", label="Upload Image") gradcam_check = gr.Checkbox(label="Apply Grad-CAM", value=True) predict_btn = gr.Button("Predict", variant="primary") with gr.Column(scale=1): image_output = gr.Image(label="Annotated Result") result_text = gr.Markdown(label="Prediction") prob_chart = gr.Label(label="Probabilities") predict_btn.click( fn=predict_image, inputs=[image_input, gradcam_check], outputs=[image_output, result_text, prob_chart, gr.State()] ) # Tab 2: Video Analysis with gr.Tab("🎥 Video Analysis"): with gr.Row(): with gr.Column(scale=1): video_input = gr.Video(label="Upload Video") skip_frames = gr.Slider( minimum=1, maximum=30, value=5, step=1, label="Process every Nth frame" ) video_gradcam = gr.Checkbox(label="Apply Grad-CAM", value=True) video_btn = gr.Button("Analyze Video", variant="primary") with gr.Column(scale=1): video_output = gr.Video(label="Processed Video") video_stats = gr.Markdown(label="Statistics") video_btn.click( fn=predict_video, inputs=[video_input, skip_frames, video_gradcam], outputs=[video_output, video_stats, gr.State()] ) # Tab 3: Webcam Inference with gr.Tab("📹 Webcam Inference"): gr.Markdown( """ ### Live Webcam Detection Click **Start Camera** to begin real-time fire/smoke detection. **Note:** Webcam inference runs locally in your browser. """ ) webcam_input = gr.Image(label="Webcam Feed") webcam_output = gr.Image(label="Detection Result") # Webcam processing would need real-time streaming # For now, provide a static image upload alternative gr.Markdown("*For live webcam detection, use the Python API directly:*") gr.Markdown( "```python\n" "from src.inference import FireNetInference\n" "engine = FireNetInference('models/FirenetCNN1.h5')\n" "engine.predict_webcam()\n" "```" ) # Tab 4: Model Information with gr.Tab("📊 Model Information"): model_info = gr.Markdown(value=get_model_info) gr.Markdown( """ ### Evaluation Metrics (Test Set) | Class | Precision | Recall | F1-Score | Support | |-------|-----------|--------|----------|---------| | fire | 0.92 | 0.81 | 0.86 | 121 | | no_fire | 0.76 | 0.98 | 0.86 | 146 | | smoke | 0.84 | 0.67 | 0.75 | 138 | | **accuracy** | | | **0.82** | **405** | | macro avg | 0.84 | 0.82 | 0.82 | 405 | | weighted avg | 0.83 | 0.82 | 0.82 | 405 | """ ) gr.Markdown( """ --- *Built with FirenetCNN (MobileNetV2) + Grad-CAM | [GitHub](https://github.com/OpelSpeedster/Forest-Fire-Detection-Using-FirenetCNN-and-XAI-Techniques)* """ ) if __name__ == '__main__': demo.queue().launch( server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)), share=False, theme=gr.themes.Soft(), )