Spaces:
Sleeping
Sleeping
File size: 20,052 Bytes
bef097a fc163de bef097a fed1ce6 7eb52be 2875890 29c6836 bef097a 2875890 bef097a fed1ce6 bef097a 96ad65a bef097a 96ad65a bef097a 6a21db2 bef097a 00457fc bef097a 96ad65a bef097a 865b4b9 bef097a 865b4b9 bef097a 865b4b9 bef097a 865b4b9 bef097a cbb8b23 f240301 eeb1a77 11a0e5f cbb8b23 f240301 6a21db2 f240301 cbc9f66 f240301 865e5ad f240301 6a21db2 2891bd6 48c3317 f240301 6a21db2 f240301 48c3317 f240301 6a21db2 f240301 6a21db2 865e5ad 5619a6d 94e3af6 51ebf6e f240301 6a21db2 f240301 0800129 f240301 764ea7f 0673439 764ea7f 0673439 764ea7f 0673439 764ea7f 00457fc fc163de aaf6140 fc163de 882b73b df0ebd8 0b95e11 df0ebd8 882b73b 7eb52be 2875890 5127585 33c271e 5127585 7eb52be 4f95521 e8b090c 4f95521 882b73b 00457fc 767e866 00457fc 2891bd6 58d7125 2891bd6 49624d5 764ea7f 2891bd6 764ea7f 2891bd6 00457fc cbb8b23 00457fc c88fc82 00457fc 5f8b621 00457fc 29c6836 a067e03 b8220a5 00457fc a3a7660 00457fc 29c6836 cbb8b23 00457fc db93a8f 00457fc ce33c2b 6a21db2 ce33c2b 6a21db2 8335916 939e172 f240301 cbb8b23 f240301 41ef522 f240301 262166a bd8261f c706c8f 41d7a51 c423934 bd8261f 11a0e5f bd8261f c423934 9fe1062 cbb8b23 f240301 7dedd16 7a91f9b 7dedd16 7a91f9b 8335916 7dedd16 8335916 7dedd16 8335916 a4728a9 03c9a65 e1f1397 03c9a65 6a21db2 03c9a65 7dedd16 a4728a9 fc163de 767e866 8335916 7dedd16 8335916 96ad65a 58d7125 7dedd16 ebe7da2 4f7b289 ebe7da2 bef097a ebe7da2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 | from supabase import create_client, Client
import os
import uuid
from fractions import Fraction
import exifread
import uvicorn
from fastapi import FastAPI, UploadFile, File, HTTPException, Form, BackgroundTasks, Body, Query
from fastapi.middleware.cors import CORSMiddleware
from dotenv import load_dotenv
from ultralytics import YOLO
import cv2
import numpy as np
import tempfile
from datetime import datetime, date, timedelta, timezone
import logging
import random
logger = logging.getLogger("uvicorn.error")
load_dotenv()
model = YOLO('best.pt')
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)
SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
if not SUPABASE_URL or not SUPABASE_KEY:
raise ValueError("Supabase credentials not found.")
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
# --- Helper Functions ---
def convert_to_decimal(coord):
if coord and len(coord.values) >= 3:
deg = float(coord.values[0])
min = float(coord.values[1]) / 60
sec = float(Fraction(str(coord.values[2]))) / 3600
return deg + min + sec
return 0.0
def get_gps_location(file_stream):
tags = exifread.process_file(file_stream)
gps_latitude = tags.get("GPS GPSLatitude")
print(f"GPS GPSLatitude: {gps_latitude}")
gps_latitude_ref = tags.get("GPS GPSLatitudeRef")
print(f"GPS GPSLatitudeRef: {gps_latitude_ref}")
gps_longitude = tags.get("GPS GPSLongitude")
print(f"GPS GPSLongitude: {gps_longitude}")
gps_longitude_ref = tags.get("GPS GPSLongitudeRef")
print(f"GPS GPSLongitudeRef: {gps_longitude_ref}")
if gps_latitude and gps_latitude_ref and gps_longitude and gps_longitude_ref:
lat = convert_to_decimal(gps_latitude)
lon = convert_to_decimal(gps_longitude)
if gps_latitude_ref.values[0] != 'N': lat = -lat
if gps_longitude_ref.values[0] != 'E': lon = -lon
return lat, lon
return None, None
def process_video_in_background(video_data: bytes, video_guid: str, filename: str, location_point: str , location: str , is_approved: int , getvariancevalue: str, category: str, city: str):
bucket_name = 'pothole-images'
try:
supabase.table("videos").insert({"guid": video_guid, "video_name": filename, "status": "processing",
"location": location_point, "location_text": location,
"category": category, "approved": is_approved, "variance": getvariancevalue, "city": city}).execute()
except Exception as e:
print(f"DB Error on video insert: {str(e)}")
return
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as temp_video_file:
temp_video_file.write(video_data)
temp_path = temp_video_file.name
cap = cv2.VideoCapture(temp_path)
frame_number = 0
first_frame_saved = False
while cap.isOpened():
ret, frame = cap.read()
if not ret: break
if frame_number % 90 == 0:
results = model(frame, device='cpu')
if len(results[0].boxes) > 0:
annotated_frame = results[0].plot()
is_success, buffer = cv2.imencode(".jpg", annotated_frame)
if is_success:
first_frame_set = {}
frame_bytes = buffer.tobytes()
frame_path = f"{video_guid}/frame_{frame_number}.jpg"
try:
supabase.storage.from_(bucket_name).upload(path=frame_path, file=frame_bytes, file_options={"content-type": "image/jpeg"})
frame_url = supabase.storage.from_(bucket_name).get_public_url(frame_path)
supabase.table("detected_frames").insert({"video_guid": video_guid, "frame_image_url": frame_url, "frame_number": frame_number, "status": "In Progress"}).execute()
if not first_frame_saved:
supabase.table("videos").update({"image_url": frame_url
}).eq("guid", video_guid).execute()
first_frame_saved = True
except Exception as e:
print(f"Error saving frame {frame_number}: {str(e)}")
frame_number += 1
cap.release()
os.remove(temp_path)
supabase.table("videos").update({"status": "complete"}).eq("guid", video_guid).execute()
def safe_date_converter(date_string):
if not date_string:
return datetime.min.replace(tzinfo=timezone.utc)
try:
dt = datetime.fromisoformat(date_string)
if dt.tzinfo is None:
return dt.replace(tzinfo=timezone.utc)
return dt
except (ValueError, TypeError):
return datetime.min.replace(tzinfo=timezone.utc)
# --- API Endpoints ---
@app.get("/")
async def root():
return {"message": "API is running"}
@app.get("/detailed_report_data")
async def get_detailed_report_data(category: str = Query(...)):
try:
# Fetch potholes reports for the specified category
response_potholes = supabase.table("potholes").select("*").eq("category", category).order("created_at", desc=True).execute()
# Fetch videos reports for the specified category
response_videos = supabase.table("videos").select("*").eq("category", category).order("created_at", desc=True).execute()
return response_potholes.data + response_videos.data
except Exception as e:
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
@app.get("/get_pothole_reports")
async def get_pothole_reports():
try:
# Fetch all pothole reports
reports_potholes_response = supabase.table("potholes").select("*").eq("category", "potholes").execute()
# Fetch all videos reports
reports_videos_response = supabase.table("videos").select("*").eq("category", "potholes").execute()
return reports_potholes_response.data + reports_videos_response.data
except Exception as e:
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
# Today's date range in UTC
@app.get("/get_today_processed_reports")
async def get_today_processed_reports():
try:
today = date.today()
start_of_day = datetime.combine(today, datetime.min.time()).replace(tzinfo=timezone.utc)
end_of_day = start_of_day + timedelta(days=1)
# Fetch all pothole reports
reports_response = supabase.table("potholes") \
.select("*") \
.gte("created_at", start_of_day.isoformat()) \
.lt("created_at", end_of_day.isoformat()) \
.execute()
# Fetch all videos reports
reports_videos_response = supabase.table("videos") \
.select("*") \
.gte("created_at", start_of_day.isoformat()) \
.lt("created_at", end_of_day.isoformat()) \
.execute()
return reports_response.data + reports_videos_response.data
except Exception as e:
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
@app.get("/get_total_processed_reports")
async def get_total_processed_reports():
try:
reports_potholes_response = supabase.table("potholes").select("*").order("created_at", desc=True).execute()
reports_videos_response = supabase.table("videos").select("*").order("created_at", desc=True).execute()
print(f"Total reports potholes fetched: {len(reports_potholes_response.data)}")
print(f"Total reports videos fetched: {len(reports_videos_response.data)}")
print(f"potholes Data : {reports_potholes_response.data}")
print(f"Videos Data : {reports_videos_response.data}")
# Extract data
potholes_data = reports_potholes_response.data or []
videos_data = reports_videos_response.data or []
# Combine and sort by created_at
all_data = potholes_data + videos_data
all_data_sorted = sorted(all_data, key=lambda x: datetime.fromisoformat(x["created_at"].replace("Z", "")), reverse=True)
# Take latest 4 records
latest_4 = all_data_sorted[:4]
return latest_4
except Exception as e:
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
@app.get("/report_summary")
async def get_report_summary():
try:
reports_response = supabase.table("potholes").select("category, approved").execute()
all_reports = reports_response.data
summary = {
"billboard": {"total": 0, "approved": 0, "unapproved": 0, "damage": 0},
"guardrails": {"total": 0, "missing": 0, "damaged": 0},
"construction": {"total": 0},
"potholes": {"total": 0, "approved": 0, "unapproved": 0}
}
for report in all_reports:
category = report.get("category")
is_approved = report.get("approved")
if category in summary:
summary[category]["total"] += 1
if is_approved == 1:
summary[category]["approved"] += 1
summary[category]["unapproved"] = summary[category]["total"] - summary[category]["approved"]
return summary
except Exception as e:
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
@app.get("/latest_reports")
async def get_latest_reports():
try:
videos_response = supabase.table("videos").select("guid, video_name, created_at, status").order("created_at", desc=True).limit(30).execute()
videos = videos_response.data
for video in videos:
video['type'] = 'video'
first_frame_response = supabase.table("detected_frames").select("frame_image_url").eq("video_guid", video["guid"]).order("frame_number").limit(1).execute()
video["thumbnail_url"] = first_frame_response.data[0]["frame_image_url"] if first_frame_response.data else None
images_response = supabase.table("potholes").select("guid, image_name, image_url, created_at, status, category").order("created_at", desc=True).limit(30).execute()
images = images_response.data
for image in images:
image['type'] = 'image'
image['thumbnail_url'] = image.get('image_url')
all_reports = videos + images
combined_reports = sorted(all_reports, key=lambda x: safe_date_converter(x.get('created_at')), reverse=True)
return combined_reports[:30]
except Exception as e:
raise HTTPException(status_code=500, detail=f"A severe database error occurred: {str(e)}")
@app.post("/upload")
async def upload_image(
file: UploadFile = File(...),
location: str = Form(None),
category: str = Form(...),
city: str = Form(...)
):
image_data = await file.read()
with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as temp_image_file:
temp_image_file.write(image_data)
temp_path = temp_image_file.name
results = model(temp_path, device='cpu')
annotated_image_array = results[0].plot()
os.remove(temp_path)
is_success, buffer = cv2.imencode(".jpg", annotated_image_array)
if not is_success: raise HTTPException(status_code=500, detail="Failed to encode annotated image.")
annotated_image_bytes = buffer.tobytes()
bucket_name = 'pothole-images'
uuid_path = f"annotated_{uuid.uuid4()}.jpg"
try:
supabase.storage.from_(bucket_name).upload(path=uuid_path, file=annotated_image_bytes, file_options={"content-type": "image/jpeg"})
url = supabase.storage.from_(bucket_name).get_public_url(uuid_path)
except Exception as e: raise HTTPException(status_code=500, detail=f"Storage Error: {str(e)}")
await file.seek(0)
lat, lon = get_gps_location(file.file)
location_point = f"Point({lon:.6f} {lat:.6f})" if lat and lon else None
guid = str(uuid.uuid4())
variance_levels = ["Low", "Mid", "High"]
getvariancevalue = random.choice(variance_levels) if category == "potholes" else None
try:
validation_response = supabase.table("validation").select("location_text").execute()
approved_locations = {item['location_text'] for item in validation_response.data}
is_approved = 1 if location in approved_locations else 0
insert_data = {
"guid": guid,
"image_name": file.filename,
"image_url": url,
"location": location_point,
"location_text": f"{lon:.6f} {lat:.6f}" if lat and lon else location or None,
"status": "In Progress",
"category": category,
"approved": is_approved,
"variance": getvariancevalue,
"city": city
}
response = supabase.table("potholes").insert(insert_data).execute()
except Exception as e:
raise HTTPException(status_code=500, detail=f"Database Error: {str(e)}")
return {"message": "Upload successful", "guid": guid, "image_url": url, "db_response": response.data}
@app.patch("/report/{guid}/status")
async def update_report_status(guid: str, payload: dict = Body(...)):
new_status = payload.get("status")
if not new_status:
raise HTTPException(status_code=400, detail="Status not provided.")
try:
report_res = supabase.table("potholes").select("status").eq("guid", guid).single().execute()
current_status = report_res.data.get("status") if report_res.data else None
final_status = 'In Progress' if current_status == new_status else new_status
response = supabase.table("potholes").update({"status": final_status}).eq("guid", guid).execute()
return {"message": "Status updated successfully", "data": response.data}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Database Error: {str(e)}")
@app.patch("/frame/{frame_id}/status")
async def update_frame_status(frame_id: int, payload: dict = Body(...)):
new_status = payload.get("status")
if not new_status:
raise HTTPException(status_code=400, detail="Status not provided.")
try:
frame_res = supabase.table("detected_frames").select("status").eq("id", frame_id).single().execute()
current_status = frame_res.data.get("status") if frame_res.data else None
final_status = 'In Progress' if current_status == new_status else new_status
response = supabase.table("detected_frames").update({"status": final_status}).eq("id", frame_id).execute()
return {"message": "Status updated successfully", "data": response.data}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Database Error: {str(e)}")
@app.post("/upload_video")
async def upload_video(background_tasks: BackgroundTasks, location: str = Form(None), category: str = Form(...), city: str = Form(...), file: UploadFile = File(...)):
if not file.content_type.startswith('video/'):
raise HTTPException(status_code=400, detail="File is not a video.")
video_data = await file.read()
video_guid = str(uuid.uuid4())
lat, lon = get_gps_location(file.file)
location_point = f"Point({lon:.6f} {lat:.6f})" if lat and lon else None
validation_response = supabase.table("validation").select("location_text").execute()
approved_locations = {item['location_text'] for item in validation_response.data}
is_approved = 1 if location in approved_locations else 0
variance_levels = ["Low", "Mid", "High"]
getvariancevalue = random.choice(variance_levels) if category == "potholes" else None
location =f"{lon:.6f} {lat:.6f}" if lat and lon else location or None
print(f"Step1")
print(f"location_point: {location_point}")
print(f"location text: {location}")
print(f"is approved : {is_approved}")
print(f"variancevalue: {getvariancevalue}")
print(f"category: {category}")
print(f"Step2")
print(f"city: {city}")
background_tasks.add_task(process_video_in_background, video_data, video_guid, file.filename, location_point, location, is_approved, getvariancevalue, category, city)
return {"message": "Video upload successful. Processing has started.", "video_guid": video_guid}
@app.get("/video_report/{guid}")
async def get_video_report(guid: str):
try:
video_response = supabase.table("videos").select("*").eq("guid", guid).single().execute()
if not video_response.data: raise HTTPException(status_code=404, detail="Video report not found")
frames_response = supabase.table("detected_frames").select("*").eq("video_guid", guid).order("frame_number", desc=True).limit(15).execute()
return { "video_info": video_response.data, "detected_frames": frames_response.data }
except Exception as e: raise HTTPException(status_code=500, detail=f"Database Error: {str(e)}")
@app.get("/frame/{frame_id}")
async def get_frame(frame_id: int):
try:
response = supabase.table("detected_frames").select("*, videos(*), status").eq("id", frame_id).single().execute()
if not response.data: raise HTTPException(status_code=404, detail="Frame not found")
return response.data
except Exception as e: raise HTTPException(status_code=500, detail=f"Database Error: {str(e)}")
@app.get("/videos")
async def get_videos():
try:
videos_response = supabase.table("videos").select("*").order("created_at", desc=True).limit(30).execute()
videos = videos_response.data
video_count = len(videos)
for video in videos:
first_frame_response = supabase.table("detected_frames").select("frame_image_url").eq("video_guid", video["guid"]).order("frame_number").limit(1).execute()
video["thumbnail_url"] = first_frame_response.data[0]["frame_image_url"] if first_frame_response.data else None
return videos
except Exception as e: raise HTTPException(status_code=500, detail=f"Database Error: {str(e)}")
@app.get("/images")
async def get_images():
try:
response = supabase.table("potholes").select("id, guid, image_name, image_url, created_at, status, location_text").order("created_at", desc=True).execute()
return response.data
except Exception as e:
raise HTTPException(status_code=500, detail=f"Database Error: {str(e)}")
@app.get("/reports")
async def get_reports():
try:
response = supabase.table("potholes").select("id, guid, image_name, image_url, created_at, status").order("created_at", desc=True).limit(30).execute()
return response.data
except Exception as e: raise HTTPException(status_code=500, detail=f"Database Error: {str(e)}")
@app.get("/report/{guid}")
async def get_report(guid: str):
try:
response = supabase.table("potholes").select("guid, image_name, image_url, location, created_at, location_text, status, category").eq("guid", guid).single().execute()
if not response.data: raise HTTPException(status_code=404, detail=f"Report with guid {guid} not found.")
return response.data
except Exception as e: raise HTTPException(status_code=500, detail=f"Database Error: {str(e)}")
@app.get("/get_billboard_stats")
async def get_billboard_stats():
try:
# Call the RPC function
response = supabase.rpc("get_billboard_stats").execute()
print("Response : billboard stats:", response.data)
return response.data
except Exception as e: raise HTTPException(status_code=500, detail=f"Database Error: {str(e)}")
@app.get("/get_pothole_stats")
async def get_pothole_stats():
try:
# Call the RPC function
response = supabase.rpc("get_pothole_stats").execute()
print("Response : pothole stats:", response.data)
return response.data
except Exception as e: raise HTTPException(status_code=500, detail=f"Database Error: {str(e)}")
if __name__ == "__main__":
uvicorn.run(app, port=8000, host="127.0.0.1")
|