Skip to content
 
 

Repository files navigation

EFDS — AI-Based Early Fire Detection System

A full-stack system that detects wildfires from satellite imagery using province-specific CNNs, and delivers location-filtered alerts to subscribers within minutes of image capture.

Course: ENG 4000 — Capstone Design Project, York University (Lassonde), 2024–2025 · Grade: A+
Team (Group 12): Eyinojuoluwa Akin-Salami · Kellan Ho · Angelique Izere · Noran Kerret · Parmoun Khalkhali Sharifi · Sara Riazi
Note: this repository is a fork of the team repo. See My Contributions for what I personally built.


My Contributions

This was a 6-person capstone. My ownership was the CNN training pipeline and the image preprocessing workflow.

Specifically, I built:

  • The training pipeline — dataset loading with per-class subsampling to correct fire/no-fire imbalance, a two-phase schedule (initial training, then fine-tuning at a lower learning rate) with EarlyStopping and ReduceLROnPlateau callbacks.
  • The model architecture — four convolutional blocks with batch normalization and ReLU, followed by two dense layers, with L1+L2 regularization and dropout for overfitting control.
  • The loss and optimization strategy — a custom focal loss to handle class imbalance directly rather than relying on resampling alone, optimized with Adam, tracking AUC / precision / recall / accuracy.
  • Threshold tuning — sweeping confidence thresholds against validation predictions and selecting the operating point on balanced accuracy between the fire and no-fire classes, rather than defaulting to 0.5.
  • The preprocessing workflow — resizing, normalization to [0, 1], 64×64 patch segmentation, and augmentation (random flips, brightness, contrast).

Backend alert logic, the Flask API, the dashboard, and the mapping layer were built by teammates.


Table of Contents


Overview

Traditional wildfire detection relies on ground sensors, aerial patrols, and manual observation — methods that fail in remote areas and create dangerous delays. EFDS replaces this with a satellite-fed CNN pipeline:

  1. Satellite imagery is fetched from NASA GIBS (MODIS/VIIRS) via WMS tile requests for a given province and date
  2. Preprocessing resizes, normalizes, and segments each image into 64×64 patches
  3. A province-specific CNN classifies each patch as fire / no-fire with a confidence score
  4. Fire patches above threshold are mapped to geographic coordinates and written to CSV
  5. The backend computes Haversine distance to every registered user and emails those within a 15 km radius, suppressing duplicates within 24 hours
  6. A dashboard renders detections on an interactive Folium map

The detection job runs on a scheduler every 10 minutes, or on demand via /trigger-fetch.

Target users: government fire agencies, environmental organizations, communities in fire-prone regions.


Architecture

  NASA GIBS (MODIS / VIIRS)
          │
          ▼
  ┌──────────────────┐
  │  Preprocessing   │  ← resize, normalize [0,1], 64×64 patch segmentation
  └────────┬─────────┘
           │
           ▼
  ┌──────────────────┐
  │  Province CNN    │  ← 4 conv blocks (BatchNorm + ReLU) → 2 dense layers
  │  (Keras / TF)    │     focal loss · L1+L2 · dropout · tuned threshold
  └────────┬─────────┘
           │  fire patches → (lat, lon, confidence) CSV
           ▼
  ┌──────────────────────────────────────────┐
  │            Flask Backend                 │
  │  - Haversine distance to each subscriber │
  │  - 15 km alert radius                    │
  │  - Flask-Mail email, 24 h suppression    │
  │  - Folium map render → fire_map.html     │
  └───────────┬──────────────────────────────┘
              │
   ┌──────────▼───────────┐
   │  Dashboard (iframe)  │
   │  live map + feed     │
   └──────────────────────┘

ML Model

Architecture

A custom CNN trained from scratch, one model per province:

Input: 64×64 RGB patch (from satellite imagery)
  │
  ├─ 4 × [ Conv2D → BatchNorm → ReLU → MaxPool ]
  ├─ Flatten
  ├─ Dense → Dropout          (L1 + L2 regularization)
  └─ Dense(1, Sigmoid) → fire probability [0, 1]

Training: custom focal loss (class imbalance), Adam optimizer, two-phase schedule with fine-tuning at reduced learning rate, EarlyStopping + ReduceLROnPlateau. Metrics tracked: AUC, precision, recall, accuracy.

Threshold selection: rather than defaulting to 0.5, a range of confidence thresholds was swept against validation predictions and the operating point chosen for balanced accuracy across both classes. The selected threshold ships alongside the model in JSON.

Why province-specific models?

In the Fall semester we trained a single Canada-wide model on 128×128 patches. It failed to capture local features — fire signatures vary meaningfully between provinces, and a national model averaged those differences away. In the Winter semester we cut patch size to 64×64 and moved to per-province models. Both changes improved spatial resolution and accuracy.

Dataset

  • Source: NASA GIBS satellite imagery (MODIS/VIIRS), labelled fire / no-fire per province
  • Balancing: per-class subsampling to prevent convergence bias
  • Preprocessing: 64×64 patch extraction, normalization, flip / brightness / contrast augmentation

Results

Validated on a corpus of 115,214 samples.

Class Precision Recall F1 Support
No Fire 92% 90% 0.91 72,089
Fire 84% 87% 0.85 43,125
Weighted avg 89% 89% 0.89 115,214
Metric Target Achieved
Classification accuracy ≥ 85.00% 88.74%
Geospatial alert precision 15.00 km radius 15.00 km radius
End-to-end detection latency 1.968 – 2.289 s
Memory usage 56.899 MiB

Accuracy exceeded the 85% project requirement. Fire-class recall (87%) was deliberately prioritized over precision (84%) — a false positive is a nuisance, a missed fire is a catastrophe — and the confidence threshold was tuned accordingly.


How to Run

Prerequisites

pip install tensorflow keras flask numpy pillow opencv-python folium

Run inference

from tensorflow.keras.models import load_model
from PIL import Image
import numpy as np

model = load_model('<province_model>.h5')

def predict(image_path, threshold):
    img = Image.open(image_path).resize((64, 64))
    arr = np.expand_dims(np.array(img) / 255.0, axis=0)
    prob = model.predict(arr)[0][0]
    print(f"{'FIRE' if prob > threshold else 'NO FIRE'} (confidence: {prob:.2%})")

Run the backend

cd Backend/
pip install -r requirements.txt
python app.py          # → http://localhost:5000

Endpoints: POST /detect · GET /alerts · POST /subscribe · POST /trigger-fetch


Tools & Stack

Layer Tool / Service
ML Framework TensorFlow / Keras
Data source NASA GIBS (MODIS, VIIRS)
Preprocessing Python (NumPy, OpenCV, PIL)
Backend Flask, Flask-Mail
Frontend HTML / CSS · Folium (Leaflet.js)
Versioning Git / GitHub

Lessons Learned

Patch size is a real design parameter, not a default. Our Fall prototype used 128×128 patches and could not resolve local fire features. Dropping to 64×64 in the Winter semester measurably improved detection of finer-grained patterns. Full-image analysis was rejected outright — it carried excessive processing time, higher memory requirements, poor parallelization, and worse precision on small fires.

Regional variation broke the single-model assumption. A Canada-wide model underperformed because fire patterns differ across provinces. Splitting into province-specific models fixed it, at the cost of a training pipeline that has to produce and version many models instead of one.

Class imbalance needed more than resampling. With roughly 72k no-fire to 43k fire samples, plain resampling still biased the model. Focal loss, combined with subsampling and a tuned decision threshold, was what actually moved fire-class recall.

Recall over precision, deliberately. The costs of the two error types are not symmetric. We chose the operating point on balanced accuracy rather than raw accuracy, and accepted lower fire precision to keep recall high.

Flask is enough for an MVP, not for production. Synchronous endpoints will not survive real concurrent load; async inference behind a queue and a proper API gateway would be the next step.

About

AI wildfire detection from satellite imagery — province-specific CNNs, Flask backend, 88.74% accuracy (ENG 4000 capstone)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages