Spaces:
Sleeping
Sleeping
| # π οΈ Work Log β TrafficGuard AI | |
| Running log of what's built. Newest entries at the bottom of each phase. | |
| --- | |
| ## Phase 1 β Foundation & Setup | |
| ### β Backend skeleton (FastAPI) | |
| Project structure scaffolded and byte-compiles clean. | |
| ``` | |
| backend/ | |
| βββ requirements.txt | |
| βββ weights/ # YOLO weights (gitignored) | |
| βββ app/ | |
| βββ main.py # FastAPI app, CORS, routers, static evidence mount | |
| βββ config.py # pydantic-settings + storage dir bootstrap | |
| βββ schemas.py # API response models (ViolationOut, AnalysisResult) | |
| βββ routes/ | |
| β βββ upload.py # POST /api/upload β full analysis pipeline | |
| β βββ violations.py # GET /api/violations[, /{id}] with filters | |
| β βββ analytics.py # GET /api/analytics/summary, /by-type | |
| βββ models/ | |
| β βββ preprocessor.py # CLAHE enhance + Laplacian quality score | |
| β βββ detector.py # YOLOv8 wrapper (lazy-loaded), COCOβcategory map | |
| β βββ violation.py # rule engine β triple-riding (IoU) shipped | |
| β βββ ocr.py # EasyOCR plate reader + Indian-format regex | |
| βββ database/ | |
| β βββ db.py # SQLAlchemy engine/session, init_db | |
| β βββ models.py # Violation table | |
| βββ utils/ | |
| βββ annotator.py # draw detection/violation boxes | |
| βββ evidence.py # save uploads + annotated evidence | |
| ``` | |
| **Design notes** | |
| - Heavy ML deps (ultralytics, easyocr, cv2) are lazy-loaded so the API boots without weights present. | |
| - Pipeline: upload β preprocess (CLAHE + conditional denoise) β YOLO detect β rule-based violation analysis β annotate β persist to SQLite. | |
| - Phase 1 ships **triple-riding** detection only; helmet/seatbelt/red-light land in Phase 4. | |
| ### β Repo scaffold | |
| - `.gitignore` (Python, node, weights, data artifacts, secrets) | |
| - `data/{uploads,evidence,sample_images,annotations}`, `notebooks/`, `docs/` | |
| ### β Frontend skeleton (Vite + React) | |
| Builds clean (`npm run build`). Editorial light theme β off-white surfaces, | |
| Fraunces serif headings, single teal accent, generous whitespace. No neon. | |
| ``` | |
| frontend/src/ | |
| βββ main.jsx # BrowserRouter bootstrap | |
| βββ App.jsx # routes | |
| βββ api.js # axios client (VITE_API_URL) | |
| βββ index.css # design tokens (palette, fonts, radius) | |
| βββ App.css # component styles | |
| βββ components/ | |
| β βββ Layout.jsx # sidebar nav + content outlet | |
| β βββ StatCard.jsx | |
| β βββ ViolationCard.jsx | |
| β βββ UploadPanel.jsx # drag-and-drop upload | |
| βββ pages/ | |
| βββ Dashboard.jsx # stat cards + recent activity | |
| βββ Analyze.jsx # upload β evidence + violations | |
| βββ Violations.jsx # filterable register table | |
| βββ Analytics.jsx # recharts bar (by type) | |
| ``` | |
| ### β Environment + end-to-end smoke test | |
| - Root `.venv` (Python 3.13). Installed `backend/requirements.txt` β torch 2.12, | |
| ultralytics 8.3, easyocr, opencv 4.10, fastapi 0.115 all import clean. | |
| - Wired `yolo_weights` to `backend/weights/` (config.py); weights auto-download | |
| there on first inference (`yolov8n.pt`, 6.2 MB β). | |
| - Booted `uvicorn app.main:app` and verified the full path: | |
| - `GET /health` β `{"status":"ok"}` | |
| - `POST /api/upload` (blank test frame) β valid `AnalysisResult` | |
| (quality 0, 0 detections, evidence image generated) | |
| - `GET /evidence/<id>.jpg` β `200` | |
| - `GET /api/analytics/summary` β `{"total":0,...}` | |
| - SQLite `trafficguard.db` created at repo root with `violations` table | |
| - No errors/warnings in server log. | |
| > Run: `cd backend && ../.venv/bin/python -m uvicorn app.main:app --reload` | |
| > (must run from `backend/` for the `app.*` imports to resolve) | |
| ### β Cross-platform support (Mac + Windows team) | |
| - `run.sh` (macOS/Linux) + `run.bat` (Windows) β launch the backend via the | |
| project `.venv` regardless of the shell's default `python`/`uvicorn`. | |
| Fixes the anaconda-PATH clash that caused `ModuleNotFoundError: ultralytics`. | |
| - `config.py` SQLite URL now uses `.as_posix()` so it's valid on Windows paths. | |
| - `.gitattributes` normalizes line endings (`.sh` β LF, `.bat` β CRLF, weights/ | |
| images marked binary). | |
| - README setup rewritten with macOS/Linux **and** Windows (PowerShell) steps; | |
| venv lives at repo root. | |
| > Note: run the backend with the **`.venv`**, not anaconda. Either use the | |
| > launcher (`./run.sh` / `run.bat`) or activate the venv first. | |
| ### β Frontend redesign β editorial polish | |
| Full visual pass on the dashboard. Builds + runs clean (Vite HMR on :5173). | |
| - **Design system** (`index.css`): warm paper palette, layered soft shadows, | |
| Fraunces display + Inter body + JetBrains Mono for plates, motion tokens, | |
| `rise` entrance animation, custom scrollbar, subtle radial background. | |
| - **Layout**: refined sidebar (gradient brand mark, active-route accent bar, | |
| nav heading) + sticky blurred topbar with date + "New analysis" CTA + a | |
| **live backend status dot** (polls `/health` every 15s). | |
| - **Dashboard**: icon stat cards (hover-lift, accent variant, trend chips), | |
| a donut "violation mix" with center total + legend, a gradient "get started" | |
| panel, and a recent-activity grid. | |
| - **Analyze**: image-preview on drop, before/after compare (original β | |
| annotated), result metric chips, loader state, feature hint chips. | |
| - **Records**: live search + type filter + count pill, polished sticky-header | |
| table with severity badges and mono plate/IDs. | |
| - **Analytics**: donut + bar charts side by side. | |
| - New shared components: `PageHeader`, `EmptyState`, `StatCard`, `Donut`, | |
| refined `ViolationCard` + `UploadPanel`. | |
| - Responsive: collapses to single column under 940px. | |
| > Note: couldn't auto-screenshot from here β verified via `npm run build` | |
| > (clean) and a dev-server boot (HTTP 200, no errors). View at | |
| > http://localhost:5173. | |
| --- | |
| ## Phase 2 β Image Preprocessing Pipeline | |
| ### β Adaptive preprocessor (`preprocessor.py`) | |
| Assess the frame, then apply **only** the corrections it needs. | |
| - **Quality assessment** β `QualityReport` (0-100): overall `score` plus | |
| `sharpness` (Laplacian var), `brightness` (mean), `contrast` (std). | |
| - **CLAHE** low-light enhancement (L channel) β fires on low brightness / flat contrast. | |
| - **Denoise** (`fastNlMeansDenoisingColored`) β low-light / low overall score. | |
| - **Dehaze** β per-channel percentile contrast stretch (washed-out frames). | |
| - **Deblur** β unsharp mask when Laplacian variance < 100 (motion blur). | |
| - **`normalize()`** β aspect-preserving 640Γ640 letterbox utility (YOLO does its | |
| own resize, so kept off the main path). | |
| `preprocess()` returns `(image, QualityReport)` and records which fixes ran. | |
| ### β Wired through the stack | |
| - `schemas.py`: new `QualityReport`; `AnalysisResult.quality_score` β `quality`. | |
| - `upload.py`: passes `asdict(report)` into the response. | |
| - Frontend `Analyze.jsx`: quality score chip + **"Auto-corrected:"** chips | |
| listing applied fixes (great demo moment) β styled in `App.css`. | |
| ### β Verified | |
| - Unit test on synthetic dark / hazy / blurry / flat frames β correct | |
| corrections fire for each (e.g. dark β Denoise + CLAHE; blurry β +Sharpen). | |
| - `normalize()` β `(640, 640, 3) uint8`. | |
| - Full serialization path (`pipeline β asdict β AnalysisResult.model_dump_json`) | |
| produces the nested `quality` object. | |
| - Frontend build clean. | |
| --- | |
| ## Phase 3 β Vehicle & Person Detection | |
| ### β Structured detector (`detector.py`) | |
| - `COCO_MAP`: COCO id β `(kind, label, category)` for person, bicycle, car, | |
| motorcycle (β Two-Wheeler), bus (β Public Transport), truck (β Heavy Vehicle). | |
| - Each detection now carries `kind` / `label` / `category` / `bbox` / | |
| `confidence` / `occupants` (conf filter + NMS handled by YOLO). | |
| - **Occupant counting**: `_assign_occupants()` attributes each person to the | |
| vehicle whose box best *contains* them (intersection Γ· person area β₯ 0.2). | |
| Containment (not IoU) is used since a rider is far smaller than the vehicle. | |
| - `summarize()`: road-user counts grouped by category. | |
| ### β Wired through the stack | |
| - `violation.py`: triple-riding now reads `occupants >= 3` from detection | |
| (no more recomputing IoU). | |
| - `annotator.py`: switched `class` β `kind`; shows `xN` occupant count on | |
| vehicles with β₯ 2 riders. | |
| - `schemas.py` + `upload.py`: response gains a `road_users` breakdown. | |
| - Frontend `Analyze.jsx`: **"Road users:"** chip row (count Γ category). | |
| ### β Verified | |
| - Crafted scene (two-wheeler + 3 overlapping persons + a car) β moto | |
| `occupants = 3`, car `0`, triple-riding fires with correct description. | |
| - `summarize()` groups correctly; full `AnalysisResult` (with `road_users`) | |
| serializes; frontend build clean. | |
| --- | |
| ## Phase 4 β Violation Detection Models β CORE | |
| ### β Extensible rule framework (`models/rules/` + `violation.py`) | |
| Each rule is a self-contained module exposing `CODE` / `NAME` / `SEVERITY`, | |
| `status()` and `check(scene)`. `violation.py` registers them, runs them all via | |
| `analyze(detections, image)`, and exposes `catalog()`. Add a violation type by | |
| dropping a module in `rules/` and registering it β no other code changes. | |
| `Scene` (rules/base.py) gives each rule `vehicles` / `persons` / `signals`. | |
| | Rule | Status | How it works | | |
| |------|--------|--------------| | |
| | Triple riding | **active** | `occupants >= 3` on a Two-Wheeler | | |
| | Helmet non-compliance | needs-weight | auto-enables when `weights/helmet.pt` exists; runs a helmet YOLO on each rider crop | | |
| | Red-light running | needs-config | HSV signal-colour classify + stop-line; opt-in via `red_light_enforcement` | | |
| | Seatbelt | planned | needs a driver-region classifier | | |
| | Wrong-side driving | planned | needs per-camera lane direction | | |
| | Illegal parking | planned | needs no-parking zone polygons | | |
| ### β Supporting changes | |
| - `detector.py`: traffic lights now detected as `kind="signal"` (COCO 9); | |
| `summarize()` excludes signals. | |
| - `config.py`: `helmet_weights`, `red_light_enforcement`, `stop_line_frac`. | |
| - `GET /api/rules` (+ `RuleInfo` schema) returns the catalogue with statuses. | |
| - Frontend `Dashboard.jsx`: **"Detection coverage"** panel β all 6 rules with | |
| Active / Needs weight / Needs config / Planned tags ("N of 6 active"). | |
| ### β Verified | |
| - `catalog()` β 6 rules with correct statuses. | |
| - Triple riding fires via the coordinator (`analyze(dets, img)`). | |
| - Red-light: with enforcement on + a synthetic red signal, only the vehicle | |
| past the stop line is flagged (the one above the line is not). | |
| - Upload route runs end-to-end in-process; `/api/rules` returns 6; FE build clean. | |
| ### β Helmet rule live with a real model | |
| Wired a trained helmet weight (`helmet.pt`, classes `Plate / WithHelmet / | |
| WithoutHelmet`). Fixes made when it didn't fire: | |
| - Class-name matching generalised (`WithoutHelmet`, `head`, "no β¦ helmet"). | |
| - Runs on the **full frame** (heads sit above the bike box) and attributes each | |
| bare-head box to the nearest two-wheeler by IoU. | |
| - Runs at **imgsz=960** (config `helmet_imgsz`; heads were missed at 640), | |
| conf floor 0.3; dedupes to one helmet violation per vehicle. | |
| - Verified on a real triple-riding photo β TRIPLE_RIDING + HELMET both fire. | |
| - Note: the model also emits `Plate` boxes β reuse for Phase 5 OCR. | |
| ### To enable the model-backed rules | |
| - **Helmet**: drop a helmet-detection YOLO at `backend/weights/helmet.pt` | |
| (e.g. from Roboflow Universe) β the rule flips to *active* automatically. | |
| - **Red-light**: set `red_light_enforcement=true` (and tune `stop_line_frac`) | |
| once the camera's stop line is known. | |
| --- | |
| ## Phase 5 β License Plate Recognition | |
| ### β Plate detection + OCR (`plates.py`, `ocr.py`) | |
| - **Detection** (`PlateService`): uses a dedicated `plate.pt` if present, else | |
| **reuses the helmet model's `Plate` class** (no extra weight needed). Config: | |
| `plate_weights`, `plate_conf`. | |
| - **Preprocess** (`_prep`): upscale small crops to ~96px height + CLAHE contrast. | |
| - **OCR** (`PlateReader`): EasyOCR with an A-Z0-9 **allowlist**, keeps only | |
| fragments with conf β₯ 0.3 (no more garbage), strips the embossed "IND" tag. | |
| Dropped the old blanket Oβ0/Bβ8 correction (it corrupted the letter section). | |
| - **Matching**: each plate is attached to the violation whose **vehicle box** | |
| best contains it β `Violation.license_plate` is now populated and shown on | |
| cards / Records table. | |
| - **Evidence**: annotator draws plate boxes (teal) with the OCR text. | |
| ### β Verified | |
| - Synthetic legible plate β reads `MH12AB1234` exactly (OCR path correct). | |
| - Real wide-shot photo: plate region detected but only `02` legible at this | |
| resolution (54Γ31px) β correctly returns "No plate" instead of guessing. | |
| - False-positive plate on a shop sign is filtered (low conf + outside vehicles). | |
| - Full upload route runs end-to-end with plates wired in. | |
| > β οΈ Demo note: OCR accuracy depends on plate resolution. Use images where a | |
| > plate is reasonably close/large to show real plate numbers. Distant plates in | |
| > wide shots are too small to read (shown as "No plate", not wrong text). | |
| --- | |
| ## Phase 6 β Evidence Generation & Storage | |
| ### β Evidence provenance + packaging | |
| - `annotator.watermark()`: burns a bottom bar onto evidence β | |
| `TrafficGuard AI | <location> | <timestamp>`. | |
| - `evidence.save_metadata()` / `load_metadata()`: a JSON **evidence package** | |
| sidecar per upload (evidence_id, timestamp, location, image names, quality, | |
| road users, violations). | |
| - `upload.py`: accepts a `location` form field, sets `location` + `timestamp` | |
| on each record, watermarks the image, writes the package JSON. | |
| ### β Review workflow (pending / confirmed / dismissed) | |
| - `PATCH /api/violations/{id}` (`ViolationStatusUpdate`) β confirm/dismiss. | |
| - `GET /api/violations/{id}/evidence` β returns the evidence package. | |
| - Frontend `Records`: status tag + β/β review buttons (dismissed rows dim), | |
| new **Location** column; `Analyze` has a **camera location** input that flows | |
| through to storage + watermark. | |
| ### β Verified | |
| - Upload with `location="Dadar TT Junction"` β record stores location, evidence | |
| package JSON written with all 8 keys (2 violations inside). | |
| - `PATCH` flips status `pending β confirmed`. | |
| - Backend compiles, full route + status + evidence endpoints run, FE build clean. | |
| --- | |
| ## Phase 7 β Backend API (completed the gaps) | |
| ### β New endpoints | |
| - `POST /api/batch-upload` β runs the pipeline over many images; returns | |
| `BatchResult {processed, total_violations, results[]}`. Pipeline extracted | |
| into shared `process_image()` (used by both `/upload` and `/batch-upload`). | |
| - `GET /api/analytics/trends` β violations per day (`func.date`), oldest first. | |
| - `GET /api/analytics/top-plates?limit=` β top offenders by plate. | |
| Full API surface now: upload, batch-upload, violations (list/get/patch/evidence), | |
| rules, analytics (summary/by-type/trends/top-plates), static `/evidence`. | |
| ### β Surfaced on the Analytics page | |
| - Trend line chart (violations/day) + **Top offenders** ranked list, alongside | |
| the existing donut + bar. `api.js`: `getTrends`, `getTopPlates`. | |
| ### β Verified | |
| - Batch of 2 β processed 2, total_violations 4. | |
| - Trends groups by day correctly; top-plates returns [] here (test images have | |
| unreadable plates β expected). Backend compiles, FE build clean. | |
| --- | |
| ## Frontend polish pass (judge-ready) | |
| - **Animated count-up** on all dashboard stat numbers (`useCountUp` hook + | |
| `StatCard` with numeric value + suffix). | |
| - **Pipeline visualizer** on Analyze (`Pipeline.jsx`): the 5 ML stages | |
| (Enhance β Detect β Classify β Read plates β Evidence) animate while | |
| processing, then show β + real per-stage stats (quality, users, flags, | |
| plates, evidence). Replaces the old static hint chips. | |
| - Verified visually via Playwright screenshots of all pages β dashboard, | |
| analyze (result), analytics, records all render cleanly and cohesively. | |
| (Installed playwright `--no-save`; not in package.json. Chromium cached in | |
| ~/Library, outside the repo.) | |
| ### βοΈ Next (Phases 9-11) | |
| - Phase 9: test real sample images, edge cases, measure inference time | |
| - Phase 10: deploy (Vercel + Render/Railway) | |
| - Phase 11: pitch deck, demo video, README polish | |
| - Optional: evidence export/download, per-position OCR correction | |