Spaces:
Sleeping
Sleeping
File size: 7,627 Bytes
1cdffb3 e19b795 22a85f1 84dbb52 e19b795 1cdffb3 e19b795 84dbb52 1cdffb3 e19b795 22a85f1 e19b795 22a85f1 c303abd e19b795 84dbb52 22a85f1 84dbb52 e19b795 fb7cbb3 83180e3 fb7cbb3 17ca999 fb7cbb3 e19b795 fb7cbb3 e19b795 22a85f1 e19b795 c303abd e19b795 c303abd e19b795 c303abd e19b795 | 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 | from api.dependencies import get_safety_detection_model
from api.dependencies import get_detection_model, get_depth_model
import asyncio
import itertools
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends
from pandas.core.frame import nested_data_to_arrays
from ai.contracts.detector import DetectionResults
from api.routers.metrics import active_cameras, decode_duration_seconds, depth_duration_seconds, detection_duration_seconds, frame_processing_duration_seconds
from contracts.camera_metadata import CameraMetadata, DetectionMetadata
import traceback
import mlflow
from utils.experiment import log_config
import cv2 as cv
import numpy as np
import time
router = APIRouter()
@router.websocket("/stream/{camera_id}")
async def websocket_detect(
websocket: WebSocket,
camera_id:str,
detector=Depends(get_detection_model),
safety_detector=Depends(get_safety_detection_model),
depth_model=Depends(get_depth_model)
):
"""
WebSocket stream takes the frame pass it to the ai models, save it under the camera id provided in the url.
url here is: ws://127.0.0.1:8000/detectors/stream/camera_id
"""
# Yes, I asked the same questions, is using webscoket.app.state many times here is consuming. after checking, it is not performance consuming.
state = websocket.app.state
logger = state.logger
# Using Depends is important and called Inversion Of Control (IoC)/ Dependency injection, and is important for testing.
redis = state.redis
# Accepting the connection from the client
await websocket.accept()
# Logging and tracking action
active_cameras.inc()
logger.info(f"Client ID >>{camera_id}<< Connected...")
if mlflow.active_run():
mlflow.end_run()
run = mlflow.start_run(run_name=f'camera_{camera_id}', nested=True)
step_counter = itertools.count()
log_config()
loop = asyncio.get_running_loop()
# Queue removing old images in case they were being stacked
frame_queue: asyncio.Queue = asyncio.Queue(maxsize=1)
async def receive_frames():
"""Receive and Queue frames, keep the latest one"""
try:
while True:
frame_bytes = await websocket.receive_bytes()
if frame_queue.full():
try:
frame_queue.get_nowait()
logger.debug("Frame Dropped", camera_id=camera_id)
except asyncio.QueueEmpty:
pass
await frame_queue.put(frame_bytes)
except WebSocketDisconnect:
raise
async def process_frames():
try:
# What are the info you aim to collect from the camera?
# How many frames received by second.
# Frame processing time.
# Average processing time for logger.
# Model processing time.
# frame_count = itertools.count()
logger.info(f"Camera {camera_id} start sending frames...")
def decode_frame():
# Decode image
return cv.imdecode(np.frombuffer(frame_bytes, np.uint8), cv.IMREAD_COLOR)
# Keep receiving messages in a loop until disconnection.
while True:
frame_bytes = await frame_queue.get()
# Profiling
t0 = time.time()
image_array = await loop.run_in_executor(None, decode_frame)
decode_duration_seconds.labels(camera_id).observe(round(time.time() - t0, 3))
mlflow.log_metric("frame_processing_time", round(time.time() - t0, 3), next(step_counter))
detection_task = loop.run_in_executor(None, detector.detect, image_array)
safety_task = loop.run_in_executor(None, safety_detector.detect, image_array)
detections, safety_detection = await asyncio.gather(detection_task, safety_task)
detection_duration_seconds.labels(camera_id).observe(round(time.time() - t0, 3))
mlflow.log_metric("detection_duration_seconds", round(time.time() - t0, 3), next(step_counter))
# Profiling
frame_processing_duration_seconds.labels(camera_id).observe(round(time.time() - t0, 3))
logger.debug("Frame processed", camera_id=camera_id)
mlflow.log_metric("frame_processing duration time", round(time.time() - t0, 3), next(step_counter))
boxes_center = []
boxes_center_ratio = []
for box in detections.detections:
print(type(box))
xmin, ymin, xmax, ymax = box.xyxy
xcenter = (xmax + xmin) / 2
ycenter = (ymax + ymin) / 2
boxes_center.append((int(xcenter), int(ycenter)))
boxes_center_ratio.append(xcenter / image_array.shape[1])
depth_points = await loop.run_in_executor(None, depth_model.calculate_depth, image_array, boxes_center) if boxes_center else []
depth_duration_seconds.labels(camera_id).observe(round(time.time() - t0, 3))
mlflow.log_metric("depth_duration_seconds", round(time.time() - t0, 3), next(step_counter))
detection_metadata = [DetectionMetadata(depth=depth, xRatio=xRatio) for depth, xRatio in zip(depth_points, boxes_center_ratio)]
metadata = CameraMetadata(camera_id=camera_id, is_danger = True if safety_detection else False, detection_metadata=detection_metadata)
# state.camera_metadata[camera_id] = metadata.model_dump()
await redis.publish("dashboard_stream", metadata.model_dump_json())
# Even if the camera was disconnected, redis is still going to show its data, which is not accurate.
# Instead, we set expiry date for the camera data.
await redis.setex(
f"camera:{camera_id}:latest", # And this is the key, or tag
10, # in seconds
metadata.model_dump_json()
)
# Note that JSONResponse doesn't work here, as it is for HTTP
await websocket.send_json({"status": 200, "camera_id": camera_id})
except Exception as e:
logger.error(f"Processing Error: {e}", camera_id=camera_id)
raise
try:
await asyncio.gather(
receive_frames(),
process_frames()
)
except WebSocketDisconnect:
logger.warn(f"Client ID >>{camera_id}<< Disconnected Normally...")
# state.camera_metadata.pop(camera_id, None)
except Exception as e:
logger.error(f"Error in websocker, Client ID: >>{camera_id}<<: {e}")
traceback.print_exc() # This one is actually really better, it shows more details about the issue happened.
# Also work on and create the logger.exception, as it directly controls printing more details about the issue happened.
await websocket.close()
finally:
active_cameras.dec()
mlflow.end_run()
# Uncomment this when needed, It is the same but using HTTP, which is Request Response only. could be used for testing.
# from fastapi import Request, UploadFile
# @router.post("/detect")
# async def post_detection(request: Request, file: UploadFile):
# # Request here is being used to access the app.state.model
# request.app.state.model.detect(file) |