TMS / api /main.py
astein11004's picture
Sync from GitHub via hub-sync
6fce012 verified
Raw
History Blame Contribute Delete
11.4 kB
import sys
import os
import base64
import cv2
import numpy as np
from fastapi import FastAPI, Form, UploadFile, File, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from typing import List
import os
from dotenv import load_dotenv
from groq import Groq
import google.generativeai as genai
import nltk
from nltk.tokenize import word_tokenize
from google.oauth2 import id_token
from google.auth.transport import requests as google_requests
# Initialize NLTK
try:
nltk.download('punkt', quiet=True)
nltk.download('punkt_tab', quiet=True)
except Exception as e:
print(f"NLTK Download Error: {e}")
# Load environment variables
load_dotenv()
try:
from api.vehicle_detection import VehicleDetector
from api.signal_time import TrafficSignalController
except ImportError:
from vehicle_detection import VehicleDetector
from signal_time import TrafficSignalController
app = FastAPI(title="AI Smart Traffic API")
# Enable CORS for React development
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Middleware for Security Headers (Required for Google Auth Popups)
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
response = await call_next(request)
response.headers["Cross-Origin-Opener-Policy"] = "same-origin-allow-popups"
return response
detector = VehicleDetector()
signal_controller = TrafficSignalController()
# AI Clients Initialization
GROQ_KEY = os.getenv("GROQ_API_KEY")
GEMINI_KEY = os.getenv("GEMINI_API_KEY")
GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID") or "1043940495048-f5m5fviaivrdob4pdu0oe169fu81ivbe.apps.googleusercontent.com"
if not GROQ_KEY or not GEMINI_KEY:
print("WARNING: API keys not found in environment variables!")
groq_client = Groq(api_key=GROQ_KEY)
genai.configure(api_key=GEMINI_KEY)
gemini_model = genai.GenerativeModel("gemini-1.5-flash")
@app.post("/auth/google")
async def google_auth(request: Request):
"""
Verify Google ID Token from frontend.
"""
try:
body = await request.json()
token = body.get("token")
if not token:
return {"error": "Token missing"}
# Verify the ID token
idinfo = id_token.verify_oauth2_token(token, google_requests.Request(), GOOGLE_CLIENT_ID)
# ID token is valid. Get the user's Google Account ID from the decoded token.
userid = idinfo['sub']
email = idinfo.get('email')
name = idinfo.get('name')
picture = idinfo.get('picture')
return {
"success": True,
"user": {
"id": userid,
"email": email,
"name": name,
"picture": picture
}
}
except ValueError as e:
# Invalid token
return {"error": str(e)}
except Exception as e:
return {"error": f"Internal error: {str(e)}"}
@app.post("/narrate")
async def narrate_traffic(request: Request):
"""
AI Narration Endpoint with Fallback Logic (Groq -> Gemini).
"""
try:
body = await request.json()
count = body.get("count", 0)
timings = body.get("timings", {})
prompt = (
"You are the Lead AI Traffic Controller of a city command center. Analyze this real-time data and explain your "
"reasoning as if you are managing the intersection right now.\n"
f"DETECTED TRAFFIC: {count} vehicles.\n"
f"SIGNAL RECOMMENDATION: Green: {timings.get('green')}s, Red: {timings.get('red')}s, Yellow: {timings.get('yellow')}s.\n\n"
"LOGIC RULES YOU USED:\n"
"- Green Time: 10s base + 2s for every vehicle to ensure the queue clears.\n"
"- Red Time: Dynamic duration to manage cross-junction pressure.\n"
"- Yellow Time: Safety margin that increases as density rises.\n\n"
"Explain the situation professionally. Don't just list numbers; explain HOW you are optimizing "
"the flow for this specific vehicle count. Speak as if you are in control."
)
# Primary: Groq (Llama 3.3 70B)
try:
completion = groq_client.chat.completions.create(
model="llama-3.3-70b-versatile",
messages=[{"role": "user", "content": prompt}],
max_tokens=500
)
narrative = completion.choices[0].message.content
return {"narrative": narrative, "provider": "groq"}
except Exception as e:
print(f"Groq Error: {e}. Falling back to Gemini...")
# Fallback: Gemini
response = gemini_model.generate_content(prompt)
return {"narrative": response.text, "provider": "gemini"}
except Exception as e:
return {"error": str(e)}
@app.post("/chat")
async def chat_with_traffic_ai(request: Request):
"""
Smart Chatbot Endpoint. Logic consistent with user provided snippet + AI Fallback.
"""
try:
data = await request.json()
query = data.get("query", "").lower()
vc = data.get("count", 0)
timings = data.get("timings", {"green": 0, "red": 0, "yellow": 0})
tokens = word_tokenize(query)
level = "LOW" if vc < 20 else "HIGH"
if any(word in tokens for word in ["vehicle", "cars", "count", "many"]):
return {"response": f"There are currently {vc} vehicles detected."}
elif any(word in tokens for word in ["traffic", "busy", "congestion", "jam"]):
return {"response": f"Traffic flow is currently {level}. The AI detector sees {vc} vehicles."}
elif any(word in tokens for word in ["signal", "time", "green", "red"]):
return {"response": f"The signal is currently optimized: Green for {timings.get('green')}s, Red for {timings.get('red')}s. This is calculated as a base 10s plus 2s per vehicle."}
elif any(word in tokens for word in ["suggest", "improve", "optimize", "better"]):
if vc > 40:
return {"response": "Traffic is heavy. I have already increased the green signal duration to clear the queue faster."}
elif vc < 10:
return {"response": "Traffic is light. I've reduced the green cycle to minimize unnecessary waiting for other lanes."}
else:
return {"response": "Traffic is moderate. Current timing is optimal for the current vehicle flow."}
elif any(word in tokens for word in ["status", "situation", "overview", "update"]):
return {"response": f"Current Status: {level} traffic density with {vc} vehicles. Signal timing: {timings.get('green')}s Green / {timings.get('red')}s Red."}
else:
prompt = (
"You are the AI Traffic Controller Chatbot. Answer the following user question about this intersection.\n"
f"CURRENT DATA: {vc} vehicles, timings: {timings}.\n"
f"USER QUESTION: {query}\n"
"Be professional, concise, and helpful. Use the data provided."
)
try:
completion = groq_client.chat.completions.create(
model="llama-3.3-70b-versatile",
messages=[{"role": "user", "content": prompt}],
max_tokens=150
)
return {"response": completion.choices[0].message.content}
except:
response = gemini_model.generate_content(prompt)
return {"response": response.text}
except Exception as e:
return {"error": str(e)}
@app.post("/detect/frame")
async def detect_frame(image: str = Form(...)):
"""
Endpoint for real-time webcam frames (Base64).
"""
try:
# Decode base64 string
if "," in image:
image = image.split(",")[1]
data = base64.b64decode(image)
nparr = np.frombuffer(data, np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
if img is None:
return {"error": "Invalid image data"}
# Run detection
processed_img, count = detector.detect(img)
# Calculate signal timings
signal_controller.update_signal_timings(count)
timings = {
"green": signal_controller.green_time,
"red": signal_controller.red_time,
"yellow": signal_controller.yellow_time
}
# Encode result back to base64
_, buffer = cv2.imencode('.jpg', processed_img)
processed_base64 = base64.b64encode(buffer).decode('utf-8')
return {
"image": f"data:image/jpeg;base64,{processed_base64}",
"count": count,
"timings": timings
}
except Exception as e:
return {"error": str(e)}
@app.post("/detect/upload")
async def detect_upload(file: UploadFile = File(...)):
"""
Endpoint for static image uploads.
"""
try:
contents = await file.read()
nparr = np.frombuffer(contents, np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
if img is None:
return {"error": "Invalid image format"}
# Run detection
processed_img, count = detector.detect(img)
# Calculate signal timings
signal_controller.update_signal_timings(count)
timings = {
"green": signal_controller.green_time,
"red": signal_controller.red_time,
"yellow": signal_controller.yellow_time
}
# Encode result back to base64 for preview
_, buffer = cv2.imencode('.jpg', processed_img)
processed_base64 = base64.b64encode(buffer).decode('utf-8')
return {
"image": f"data:image/jpeg;base64,{processed_base64}",
"count": count,
"filename": file.filename,
"timings": timings
}
except Exception as e:
return {"error": str(e)}
@app.get("/health")
async def health():
return {"status": "ok", "message": "AI Smart Traffic System API is running"}
# Serve frontend
if os.path.exists("static"):
# Mount the assets directory specifically
assets_path = os.path.join("static", "assets")
if os.path.exists(assets_path):
app.mount("/assets", StaticFiles(directory=assets_path), name="assets")
# Mount any other static folders if needed (like public files)
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/")
async def serve_index():
return FileResponse("static/index.html")
@app.get("/{rest_of_path:path}")
async def serve_spa(request: Request, rest_of_path: str):
# If it's an API route that wasn't found, don't serve HTML
if rest_of_path.startswith("auth/") or rest_of_path.startswith("detect/"):
return {"error": "Not Found"}
return FileResponse("static/index.html")
else:
print("WARNING: 'static' directory not found.")
@app.get("/")
async def root():
return {"message": "AI Smart Traffic System API is running (Frontend missing)"}