# πŸ› οΈ 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/.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 | | `. - `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