Spaces:
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). Installedbackend/requirements.txtβ torch 2.12, ultralytics 8.3, easyocr, opencv 4.10, fastapi 0.115 all import clean. - Wired
yolo_weightstobackend/weights/(config.py); weights auto-download there on first inference (yolov8n.pt, 6.2 MB β). - Booted
uvicorn app.main:appand verified the full path:GET /healthβ{"status":"ok"}POST /api/upload(blank test frame) β validAnalysisResult(quality 0, 0 detections, evidence image generated)GET /evidence/<id>.jpgβ200GET /api/analytics/summaryβ{"total":0,...}- SQLite
trafficguard.dbcreated at repo root withviolationstable
- No errors/warnings in server log.
Run:
cd backend && ../.venv/bin/python -m uvicorn app.main:app --reload(must run frombackend/for theapp.*imports to resolve)
β Cross-platform support (Mac + Windows team)
run.sh(macOS/Linux) +run.bat(Windows) β launch the backend via the project.venvregardless of the shell's defaultpython/uvicorn. Fixes the anaconda-PATH clash that causedModuleNotFoundError: ultralytics.config.pySQLite URL now uses.as_posix()so it's valid on Windows paths..gitattributesnormalizes 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,riseentrance 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
/healthevery 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, refinedViolationCard+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): overallscoreplussharpness(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: newQualityReport;AnalysisResult.quality_scoreβquality.upload.py: passesasdict(report)into the response.- Frontend
Analyze.jsx: quality score chip + "Auto-corrected:" chips listing applied fixes (great demo moment) β styled inApp.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 nestedqualityobject. - 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 readsoccupants >= 3from detection (no more recomputing IoU).annotator.py: switchedclassβkind; showsxNoccupant count on vehicles with β₯ 2 riders.schemas.py+upload.py: response gains aroad_usersbreakdown.- Frontend
Analyze.jsx: "Road users:" chip row (count Γ category).
β Verified
- Crafted scene (two-wheeler + 3 overlapping persons + a car) β moto
occupants = 3, car0, triple-riding fires with correct description. summarize()groups correctly; fullAnalysisResult(withroad_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 askind="signal"(COCO 9);summarize()excludes signals.config.py:helmet_weights,red_light_enforcement,stop_line_frac.GET /api/rules(+RuleInfoschema) 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/rulesreturns 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
Plateboxes β 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 tunestop_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 dedicatedplate.ptif present, else reuses the helmet model'sPlateclass (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_plateis now populated and shown on cards / Records table. - Evidence: annotator draws plate boxes (teal) with the OCR text.
β Verified
- Synthetic legible plate β reads
MH12AB1234exactly (OCR path correct). - Real wide-shot photo: plate region detected but only
02legible 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 alocationform field, setslocation+timestampon 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;Analyzehas 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). PATCHflips statuspending β 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; returnsBatchResult {processed, total_violations, results[]}. Pipeline extracted into sharedprocess_image()(used by both/uploadand/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 (
useCountUphook +StatCardwith 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