Spaces:
Sleeping
Sleeping
File size: 16,194 Bytes
7a58fa9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 | # Satellite Change Detection β Project Workflow Guide
A complete walkthrough of how this project is structured, how a detection request
flows through the system, and how to run it locally. Read this top-to-bottom to get
familiar with the codebase.
---
## 1. What this project does
This is a **standalone web application for satellite image change detection**. A user
uploads two images of the same place taken at different times (a **"before"** and an
**"after"**), and the app:
1. Aligns the two images (registration).
2. Normalizes their colors/brightness.
3. Runs change detection (classical computer vision + a pretrained deep-learning model).
4. Finds and classifies the changed regions (new buildings, vegetation change, water, etc.).
5. Draws the changes on an overlay image and shows statistics.
6. Saves the run to a per-user history (with login/accounts).
It is deployed as a Docker container on **Hugging Face Spaces**, but runs the same way locally.
---
## 2. Tech stack
| Layer | Technology |
|-------|-----------|
| Backend API | **FastAPI** (Python), served by **uvicorn** |
| Image processing | **OpenCV**, **NumPy**, **scikit-learn** |
| Deep learning | **PyTorch** + **Hugging Face Transformers** (AdaptFormer model) |
| Database | **SQLAlchemy** ORM over **SQLite** (default) or **PostgreSQL** |
| Auth | **JWT** tokens (python-jose) + **bcrypt** password hashing (passlib) |
| Frontend | Plain **HTML + CSS + vanilla JavaScript** (single-page app) |
| Email | HTTP email API or SMTP (optional notifications) |
| Deployment | **Docker** β Hugging Face Spaces |
There is **no build step** for the frontend β it's static files served directly.
---
## 3. Repository structure
```
change_detection_webapp/
βββ app/
β βββ main.py # FastAPI app + all HTTP routes (entry point)
β βββ detection_engine.py # CORE: preprocessing, registration, detection, classification
β βββ model_inference.py # AdaptFormer deep-learning model loader + tiled inference
β βββ cd_models/ # Extra DL building blocks
β β βββ change_model.py # Siamese U-Net architecture (optional, needs weights)
β β βββ model_utils.py # tiling, multi-scale, confidence-map helpers
β βββ auth.py # JWT create/verify, password hashing, current-user lookup
β βββ database.py # SQLAlchemy engine/session, DATA_DIR resolution
β βββ models.py # ORM models: User, DetectionRun
β βββ notifier.py # Email notification sending
βββ static/
β βββ css/style.css # All styles
β βββ js/app.js # All frontend logic (auth, upload, render results)
βββ templates/
β βββ index.html # Single-page UI shell
βββ scripts/
β βββ validate_detection.py # Sanity checks for the detection pipeline
βββ data/ # Created at runtime: SQLite DB + overlay/thumbnail images
βββ requirements.txt # Python dependencies
βββ Dockerfile # Container build (also pre-downloads the model)
βββ README.md # Short setup notes (Hugging Face front-matter on top)
βββ WORKFLOW.md # This document
```
> Note: the `app/cd_models/` folder is named `cd_models` (not `models`) on purpose β
> `app/models.py` already exists for the database models, and a folder named `models`
> would shadow it and break imports.
---
## 4. High-level architecture
```mermaid
flowchart LR
browser["Browser (index.html + app.js)"] -->|"HTTP / JSON"| api["FastAPI (main.py)"]
api --> auth["auth.py (JWT)"]
api --> engine["detection_engine.py"]
engine --> model["model_inference.py (AdaptFormer)"]
api --> db["(SQLite / Postgres via models.py)"]
api --> notifier["notifier.py (email)"]
api --> files["data/overlays (result images)"]
```
- The **browser** talks only to FastAPI over JSON + multipart form uploads.
- **main.py** is the only place that defines routes; it delegates the heavy lifting to
`detection_engine.run_detection(...)`.
- Results (overlay PNGs, thumbnails) are written to `data/overlays/` and referenced by URL.
- Run metadata is stored in the database.
---
## 5. The detection pipeline (the core workflow)
This is the most important part to understand. Everything happens inside
`run_detection()` in [`app/detection_engine.py`](app/detection_engine.py).
```mermaid
flowchart TD
start["run_detection(before, after, method, sensitivity, ...)"] --> pre["preprocess_image() -- RGB, resize, denoise"]
pre --> reg{"registration enabled?"}
reg -->|yes| align["register_images() -- SIFT/ORB + ECC align, returns quality metrics"]
reg -->|no| norm
align --> norm{"normalization enabled?"}
norm -->|yes| radio["normalize_radiometry() -- LAB color match + CLAHE"]
norm -->|no| method
radio --> method["pick detection method"]
method --> ai["AI-Based Deep Learning (default)"]
method --> diff["Image Difference"]
method --> feat["Feature-Based (KMeans)"]
method --> hyb["Hybrid / Hybrid AI"]
ai --> fuse["AdaptFormer score + classical score -> confidence-gated fusion"]
diff --> mask
feat --> mask
hyb --> fuse
fuse --> mask["binary change mask"]
mask --> regions["analyze_change_regions() -- connected components + classify + NMS"]
regions --> viz["visualize_changes() -- draw boxes on overlay"]
viz --> out["return mask, overlay, stats, regions"]
```
### Step by step
1. **Preprocess** (`preprocess_image`): convert to RGB, cap the size (default 1600px max
side) for speed, apply light Gaussian blur (and a bilateral filter only when the image
is noisy) to reduce sensor noise without destroying edges.
2. **Register / align** (`register_images`): the two screenshots rarely line up perfectly.
- First tries **SIFT + FLANN** feature matching β homography.
- Falls back to **ORB** features if SIFT is weak.
- Refines with **ECC** (sub-pixel alignment).
- Falls back to **multi-scale ECC** if feature matching fails entirely.
- Returns an `(img1, img2_aligned, registration_ok, reg_meta)` tuple. `registration_ok`
and the quality metrics (inlier ratio, NCC) are surfaced to the UI as an
**alignment warning** when the alignment is weak β important for Google Earth
screenshots that differ in zoom/crop.
3. **Radiometric normalization** (`normalize_radiometry`): match the "after" image's color
statistics to the "before" image in LAB space, plus symmetric CLAHE on the lightness
channel, so lighting/season differences don't look like real change.
4. **Detection method** (selected by the `method` argument):
- **AI-Based Deep Learning** (default): runs the **AdaptFormer** model
(`model_inference.predict_change_mask`) to get a per-pixel change probability map,
computes a **classical** multi-channel score map (color ΞE, SSIM, texture/LBP, edges,
change-vector analysis), then **fuses** them with `fuse_dl_and_classical()`. Fusion is
**confidence-gated** (not a blind union): the model drives structural changes, the
classical signal + Excess-Green index supports vegetation changes.
- **Image Difference**: LAB ΞE difference with adaptive (Otsu/MAD) thresholding.
- **Feature-Based**: KMeans clustering of per-pixel difference features.
- **Hybrid / Hybrid AI**: weighted combinations of the above.
5. **Clean the mask** (`_clean_mask`): morphological open/close, hole filling, remove tiny
or thin (shadow-like) components.
6. **Find & classify regions** (`analyze_change_regions`): connected-components on the mask
β bounding boxes β `classify_object_type()` assigns a label (New Construction/Building,
Vegetation Change, Water Body Change, Road/Pavement, Bare Land, etc.) with a confidence
and severity β **non-maximum suppression** removes overlapping/duplicate boxes.
7. **Visualize** (`visualize_changes`): draw a subtle tint on changed pixels and numbered,
color-coded boxes for the top regions.
8. **Return** `change_mask, result_image, stats, change_regions` back to `main.py`.
### Sensitivity
The `detection_sensitivity` slider (0β1, default 0.5) shifts thresholds: higher = detect
more (more recall, more false positives), lower = stricter (fewer, higher-confidence
detections).
---
## 6. The deep-learning model
- File: [`app/model_inference.py`](app/model_inference.py)
- Model: **`deepang/adaptformer-LEVIR-CD`** (a change-detection transformer trained on the
LEVIR-CD building dataset), pulled from the Hugging Face Hub.
- It is **pre-downloaded at Docker build time** (see the Dockerfile) so cold starts are fast,
and **preloaded on app startup** so the first request isn't slow.
- Inference is **tile-based**: the image is split into overlapping 256Γ256 tiles (the model's
native size), each tile is predicted, and the results are stitched back with a
raised-cosine blend to avoid visible seams.
- If PyTorch/Transformers aren't installed or the model fails to load, the engine **falls
back gracefully** to the classical-only path β the app still works.
`app/cd_models/change_model.py` contains a from-scratch **Siamese U-Net** as an alternative
DL backbone. It only activates if a trained weights file exists at
`app/cd_models/weights/siamese_unet_cd.pt` (none is shipped), so it's currently dormant.
---
## 7. Request lifecycle (auth + detect)
```mermaid
sequenceDiagram
participant U as Browser
participant A as FastAPI (main.py)
participant Au as auth.py
participant E as detection_engine.py
participant D as Database
U->>A: POST /api/auth/login {email, password}
A->>Au: verify_password + create_access_token
Au-->>A: JWT
A-->>U: token (also set as httpOnly cookie)
U->>A: POST /api/detect (before, after, method, ...) + token
A->>Au: resolve user from token/cookie
A->>E: run_detection(...)
E-->>A: mask, overlay, stats, regions
A->>D: save DetectionRun row
A->>A: write overlay + thumbnails to data/overlays/
A-->>U: JSON {statistics, regions, overlayBase64Png, ...}
U->>U: app.js renders overlay + regions table
```
---
## 8. Data model
Defined in [`app/models.py`](app/models.py):
- **User**: `id, email (unique), hashed_password, full_name, created_at`
- **DetectionRun**: `id, user_id, title, method, total_pixels, changed_pixels,
change_percentage, regions_count, overlay_path, before/after thumbnail paths, zone,
village, regions_json (serialized regions), created_at`
The database file lives at `data/satellite_app.db` (SQLite) and is created automatically on
first run. Set `DATABASE_URL` to use PostgreSQL instead.
---
## 9. API reference
All routes are in [`app/main.py`](app/main.py).
| Method & path | Purpose |
|---------------|---------|
| `POST /api/auth/register` | Create account `{email, password, full_name}` β token |
| `POST /api/auth/login` | Log in `{email, password}` β token (+ cookie) |
| `POST /api/auth/logout` | Clear auth cookie |
| `POST /api/auth/reset-password` | Set a new password `{email, new_password}` |
| `GET /api/me` | Current user (requires auth) |
| `POST /api/detect` | **Main endpoint.** multipart: `before`, `after` files + `method`, `title`, `zone`, `village`, `enable_registration`, `enable_normalization`, `detection_sensitivity`, `min_region_area`, `notify_email` |
| `GET /api/history` | List the current user's past runs |
| `POST /api/notify/test` | Send a test email |
| `GET /api/overlay/<path>` | Serve a saved overlay / thumbnail image |
| `GET /health` | Lightweight health check (no DB) |
| `GET /` | Serves the single-page UI |
`POST /api/detect` returns: `statistics` (pixels, change %, threshold debug, alignment
warning), `regions` (list with type/confidence/severity/bbox), and the overlay as base64 PNG.
---
## 10. Frontend
- [`templates/index.html`](templates/index.html) is the shell: login/register forms, the
upload form, history table, and the result modal.
- [`static/js/app.js`](static/js/app.js) handles everything client-side: calling the API,
storing the token, submitting the detect form, and rendering results (the before/after
slider, the regions table, alignment warnings, and fusion stats).
- [`static/css/style.css`](static/css/style.css) holds all styling.
- Cache-busting: the `?v=NN` suffixes on the CSS/JS `<link>`/`<script>` tags are bumped when
those files change so browsers don't serve stale copies.
---
## 11. Running locally
### Option A β Python virtual environment (recommended for development)
```bash
cd change_detection_webapp
# 1. Create and activate a virtual environment
python -m venv venv
# Windows (PowerShell):
venv\Scripts\Activate.ps1
# macOS / Linux:
source venv/bin/activate
# 2. Install dependencies (this pulls CPU PyTorch + Transformers, ~couple GB)
pip install -r requirements.txt
# 3. Run the dev server (auto-reload on code changes)
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```
Open **http://localhost:8000**, register an account, then upload a before/after pair.
> First detection run downloads the AdaptFormer model from Hugging Face (one time).
> If you don't have PyTorch installed or want a lighter setup, the app still runs using
> the classical detection path only.
### Option B β Docker (mirrors production)
```bash
cd change_detection_webapp
docker build -t change-detection .
docker run -p 7860:7860 change-detection
```
Open **http://localhost:7860**. The Docker build pre-downloads the model, so the first
request is fast.
### Validate the pipeline
```bash
python scripts/validate_detection.py
```
This runs registration/fusion/end-to-end sanity checks on synthetic images.
---
## 12. Configuration (environment variables)
| Variable | Default | Purpose |
|----------|---------|---------|
| `DATABASE_URL` | `sqlite:///data/satellite_app.db` | Use PostgreSQL by setting this |
| `SECRET_KEY` | (in `auth.py`) | JWT signing key β **set this in production** |
| `HF_HOME` | `/app/.hf_cache` (Docker) | Where the model is cached |
| `EMAIL_API_URL` | manager's API | Email backend; empty + `SMTP_USER`/`SMTP_PASS` for SMTP |
| `PORT` | `7860` | Port uvicorn binds to in the container |
| `SPACE_ID` | (set by HF) | When present, data is written to `~/data` |
---
## 13. Deployment (Hugging Face Spaces)
- The Space is a **Docker** Space. Pushing to its git remote triggers a rebuild.
- Hugging Face builds from the **`main`** branch. (This repo's working branch is `master`,
so deploys push `master` β both `master` and `main` on the HF remote.)
- The `ARG APP_BUILD=NN` line in the Dockerfile is a cache-buster: bumping it forces pip to
reinstall and the model to re-download on the next build.
- The YAML front-matter at the top of `README.md` (title, emoji, `sdk: docker`,
`app_port: 7860`) is what Hugging Face reads to configure the Space.
---
## 14. Common tasks & gotchas
- **"It's detecting too much / too little"** β adjust the sensitivity slider, and make sure
the before/after images are the **same location, zoom, and crop**. Weak alignment shows an
alignment warning in the result panel.
- **Changed CSS/JS but nothing updates** β bump the `?v=NN` query string in `index.html`.
- **Adding a DB column** β update `app/models.py`; the app creates tables on startup
(there's lightweight migration handling in `main.py`).
- **Don't create a folder named `app/models/`** β it shadows `app/models.py`.
- **Model not loading** β the app logs a warning and continues with classical detection;
check the logs for the AdaptFormer load message.
---
## 15. Where to start reading the code
1. [`app/main.py`](app/main.py) β see the routes, especially `POST /api/detect`.
2. [`app/detection_engine.py`](app/detection_engine.py) β start at `run_detection()` at the
bottom and follow the calls upward.
3. [`app/model_inference.py`](app/model_inference.py) β how the DL model is loaded and run.
4. [`static/js/app.js`](static/js/app.js) β how the frontend calls the API and renders results.
Welcome to the project!
|