coderuday21 Cursor commited on
Commit
7a58fa9
Β·
1 Parent(s): ba4abf7

Add WORKFLOW.md: project workflow and onboarding guide

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