FirenetCNN / app.py
OpelSpeedster's picture
Upload 2 files
519c713 verified
Raw
History Blame Contribute Delete
11.8 kB
"""
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(),
)