Spaces:
Sleeping
Sleeping
feat: Add redis to backend
Browse files- api/__pycache__/dependencies.cpython-311.pyc +0 -0
- api/dependencies.py +14 -0
- api/routers/__pycache__/camera_stream.cpython-311.pyc +0 -0
- api/routers/__pycache__/dashboard_stream.cpython-311.pyc +0 -0
- api/routers/camera_stream.py +20 -21
- api/routers/dashboard_stream.py +16 -6
- main.py +20 -10
- requirements.txt +3 -1
api/__pycache__/dependencies.cpython-311.pyc
ADDED
|
Binary file (1.13 kB). View file
|
|
|
api/dependencies.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Here exists function to use instead of using app.state directly in the main.py
|
| 2 |
+
from fastapi.requests import HTTPConnection
|
| 3 |
+
|
| 4 |
+
def get_detection_model(request: HTTPConnection):
|
| 5 |
+
return request.app.state.detection_model
|
| 6 |
+
|
| 7 |
+
def get_depth_model(request: HTTPConnection):
|
| 8 |
+
return request.app.state.depth_model
|
| 9 |
+
|
| 10 |
+
def get_safety_detection_model(request: HTTPConnection):
|
| 11 |
+
return request.app.state.safety_detection_model
|
| 12 |
+
|
| 13 |
+
def get_redis(request: HTTPConnection):
|
| 14 |
+
return request.app.state.redis
|
api/routers/__pycache__/camera_stream.cpython-311.pyc
CHANGED
|
Binary files a/api/routers/__pycache__/camera_stream.cpython-311.pyc and b/api/routers/__pycache__/camera_stream.cpython-311.pyc differ
|
|
|
api/routers/__pycache__/dashboard_stream.cpython-311.pyc
CHANGED
|
Binary files a/api/routers/__pycache__/dashboard_stream.cpython-311.pyc and b/api/routers/__pycache__/dashboard_stream.cpython-311.pyc differ
|
|
|
api/routers/camera_stream.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
|
|
|
|
|
| 1 |
import asyncio
|
| 2 |
import itertools
|
| 3 |
-
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
| 4 |
from pandas.core.frame import nested_data_to_arrays
|
| 5 |
from ai.contracts.detector import DetectionResults
|
| 6 |
from backend.api.routers.metrics import active_cameras, decode_duration_seconds, depth_duration_seconds, detection_duration_seconds, frame_processing_duration_seconds
|
|
@@ -16,7 +18,13 @@ import time
|
|
| 16 |
router = APIRouter()
|
| 17 |
|
| 18 |
@router.websocket("/stream/{camera_id}")
|
| 19 |
-
async def websocket_detect(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
"""
|
| 21 |
WebSocket stream takes the frame pass it to the ai models, save it under the camera id provided in the url.
|
| 22 |
|
|
@@ -25,10 +33,8 @@ async def websocket_detect(websocket: WebSocket, camera_id:str):
|
|
| 25 |
# Yes, I asked the same questions, is using webscoket.app.state many times here is consuming. after checking, it is not performance consuming.
|
| 26 |
state = websocket.app.state
|
| 27 |
logger = state.logger
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
depth_model = state.depth_model
|
| 31 |
-
|
| 32 |
|
| 33 |
# Accepting the connection from the client
|
| 34 |
await websocket.accept()
|
|
@@ -38,10 +44,10 @@ async def websocket_detect(websocket: WebSocket, camera_id:str):
|
|
| 38 |
logger.info(f"Client ID >>{camera_id}<< Connected...")
|
| 39 |
|
| 40 |
loop = asyncio.get_running_loop()
|
| 41 |
-
step_counter = itertools.count()
|
| 42 |
if mlflow.active_run():
|
| 43 |
mlflow.end_run()
|
| 44 |
run = mlflow.start_run(run_name=f'camera_{camera_id}', nested=True)
|
|
|
|
| 45 |
log_config()
|
| 46 |
|
| 47 |
try:
|
|
@@ -58,15 +64,6 @@ async def websocket_detect(websocket: WebSocket, camera_id:str):
|
|
| 58 |
def decode_frame():
|
| 59 |
# Decode image
|
| 60 |
return cv.imdecode(np.frombuffer(frame_bytes, np.uint8), cv.IMREAD_COLOR)
|
| 61 |
-
|
| 62 |
-
def run_detection(frame) -> DetectionResults:
|
| 63 |
-
return detector.detect(frame)
|
| 64 |
-
|
| 65 |
-
def run_safety(frame) -> DetectionResults:
|
| 66 |
-
return safety_detector.detect(frame)
|
| 67 |
-
|
| 68 |
-
def run_depth(frame, points):
|
| 69 |
-
return depth_model.calculate_depth(frame, points)
|
| 70 |
|
| 71 |
# Keep receiving messages in a loop until disconnection.
|
| 72 |
while True:
|
|
@@ -80,8 +77,8 @@ async def websocket_detect(websocket: WebSocket, camera_id:str):
|
|
| 80 |
decode_duration_seconds.labels(camera_id).observe(round(time.time() - t0, 3))
|
| 81 |
mlflow.log_metric("frame_processing_time", round(time.time() - t0, 3), next(step_counter))
|
| 82 |
|
| 83 |
-
detection_task = loop.run_in_executor(None,
|
| 84 |
-
safety_task = loop.run_in_executor(None,
|
| 85 |
|
| 86 |
detections, safety_detection = await asyncio.gather(detection_task, safety_task)
|
| 87 |
detection_duration_seconds.labels(camera_id).observe(round(time.time() - t0, 3))
|
|
@@ -102,20 +99,22 @@ async def websocket_detect(websocket: WebSocket, camera_id:str):
|
|
| 102 |
boxes_center.append((int(xcenter), int(ycenter)))
|
| 103 |
boxes_center_ratio.append(xcenter / image_array.shape[1])
|
| 104 |
|
| 105 |
-
depth_points = await loop.run_in_executor(None,
|
| 106 |
depth_duration_seconds.labels(camera_id).observe(round(time.time() - t0, 3))
|
| 107 |
mlflow.log_metric("depth_duration_seconds", round(time.time() - t0, 3), next(step_counter))
|
| 108 |
|
| 109 |
detection_metadata = [DetectionMetadata(depth=depth, xRatio=xRatio) for depth, xRatio in zip(depth_points, boxes_center_ratio)]
|
| 110 |
metadata = CameraMetadata(camera_id=camera_id, is_danger = True if safety_detection else False, detection_metadata=detection_metadata)
|
| 111 |
-
|
|
|
|
|
|
|
| 112 |
|
| 113 |
# Note that JSONResponse doesn't work here, as it is for HTTP
|
| 114 |
await websocket.send_json({"status": 200, "camera_id": camera_id})
|
| 115 |
|
| 116 |
except WebSocketDisconnect:
|
| 117 |
logger.warn(f"Client ID >>{camera_id}<< Disconnected Normally...")
|
| 118 |
-
state.camera_metadata.pop(camera_id, None)
|
| 119 |
|
| 120 |
except Exception as e:
|
| 121 |
logger.error(f"Error in websocker, Client ID: >>{camera_id}<<: {e}")
|
|
|
|
| 1 |
+
from backend.api.dependencies import get_safety_detection_model
|
| 2 |
+
from backend.api.dependencies import get_detection_model, get_depth_model
|
| 3 |
import asyncio
|
| 4 |
import itertools
|
| 5 |
+
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends
|
| 6 |
from pandas.core.frame import nested_data_to_arrays
|
| 7 |
from ai.contracts.detector import DetectionResults
|
| 8 |
from backend.api.routers.metrics import active_cameras, decode_duration_seconds, depth_duration_seconds, detection_duration_seconds, frame_processing_duration_seconds
|
|
|
|
| 18 |
router = APIRouter()
|
| 19 |
|
| 20 |
@router.websocket("/stream/{camera_id}")
|
| 21 |
+
async def websocket_detect(
|
| 22 |
+
websocket: WebSocket,
|
| 23 |
+
camera_id:str,
|
| 24 |
+
detector=Depends(get_detection_model),
|
| 25 |
+
safety_detector=Depends(get_safety_detection_model),
|
| 26 |
+
depth_model=Depends(get_depth_model)
|
| 27 |
+
):
|
| 28 |
"""
|
| 29 |
WebSocket stream takes the frame pass it to the ai models, save it under the camera id provided in the url.
|
| 30 |
|
|
|
|
| 33 |
# Yes, I asked the same questions, is using webscoket.app.state many times here is consuming. after checking, it is not performance consuming.
|
| 34 |
state = websocket.app.state
|
| 35 |
logger = state.logger
|
| 36 |
+
# Using Depends is important and called Inversion Of Control (IoC)/ Dependency injection, and is important for testing.
|
| 37 |
+
redis = state.redis
|
|
|
|
|
|
|
| 38 |
|
| 39 |
# Accepting the connection from the client
|
| 40 |
await websocket.accept()
|
|
|
|
| 44 |
logger.info(f"Client ID >>{camera_id}<< Connected...")
|
| 45 |
|
| 46 |
loop = asyncio.get_running_loop()
|
|
|
|
| 47 |
if mlflow.active_run():
|
| 48 |
mlflow.end_run()
|
| 49 |
run = mlflow.start_run(run_name=f'camera_{camera_id}', nested=True)
|
| 50 |
+
step_counter = itertools.count()
|
| 51 |
log_config()
|
| 52 |
|
| 53 |
try:
|
|
|
|
| 64 |
def decode_frame():
|
| 65 |
# Decode image
|
| 66 |
return cv.imdecode(np.frombuffer(frame_bytes, np.uint8), cv.IMREAD_COLOR)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
# Keep receiving messages in a loop until disconnection.
|
| 69 |
while True:
|
|
|
|
| 77 |
decode_duration_seconds.labels(camera_id).observe(round(time.time() - t0, 3))
|
| 78 |
mlflow.log_metric("frame_processing_time", round(time.time() - t0, 3), next(step_counter))
|
| 79 |
|
| 80 |
+
detection_task = loop.run_in_executor(None, detector.detect, image_array)
|
| 81 |
+
safety_task = loop.run_in_executor(None, safety_detector.detect, image_array)
|
| 82 |
|
| 83 |
detections, safety_detection = await asyncio.gather(detection_task, safety_task)
|
| 84 |
detection_duration_seconds.labels(camera_id).observe(round(time.time() - t0, 3))
|
|
|
|
| 99 |
boxes_center.append((int(xcenter), int(ycenter)))
|
| 100 |
boxes_center_ratio.append(xcenter / image_array.shape[1])
|
| 101 |
|
| 102 |
+
depth_points = await loop.run_in_executor(None, depth_model.calculate_depth, image_array, boxes_center) if boxes_center else []
|
| 103 |
depth_duration_seconds.labels(camera_id).observe(round(time.time() - t0, 3))
|
| 104 |
mlflow.log_metric("depth_duration_seconds", round(time.time() - t0, 3), next(step_counter))
|
| 105 |
|
| 106 |
detection_metadata = [DetectionMetadata(depth=depth, xRatio=xRatio) for depth, xRatio in zip(depth_points, boxes_center_ratio)]
|
| 107 |
metadata = CameraMetadata(camera_id=camera_id, is_danger = True if safety_detection else False, detection_metadata=detection_metadata)
|
| 108 |
+
|
| 109 |
+
# state.camera_metadata[camera_id] = metadata.model_dump()
|
| 110 |
+
await redis.publish("dashboard_stream", metadata.model_dump_json())
|
| 111 |
|
| 112 |
# Note that JSONResponse doesn't work here, as it is for HTTP
|
| 113 |
await websocket.send_json({"status": 200, "camera_id": camera_id})
|
| 114 |
|
| 115 |
except WebSocketDisconnect:
|
| 116 |
logger.warn(f"Client ID >>{camera_id}<< Disconnected Normally...")
|
| 117 |
+
# state.camera_metadata.pop(camera_id, None)
|
| 118 |
|
| 119 |
except Exception as e:
|
| 120 |
logger.error(f"Error in websocker, Client ID: >>{camera_id}<<: {e}")
|
api/routers/dashboard_stream.py
CHANGED
|
@@ -2,6 +2,7 @@ from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|
| 2 |
from backend.api.routers.metrics import active_dashboards
|
| 3 |
import asyncio
|
| 4 |
import traceback
|
|
|
|
| 5 |
|
| 6 |
router = APIRouter()
|
| 7 |
|
|
@@ -14,6 +15,7 @@ async def dashboard_websocket(websocket: WebSocket):
|
|
| 14 |
"""
|
| 15 |
state = websocket.app.state
|
| 16 |
logger = state.logger
|
|
|
|
| 17 |
|
| 18 |
# Accept the client connection.
|
| 19 |
await websocket.accept()
|
|
@@ -22,14 +24,20 @@ async def dashboard_websocket(websocket: WebSocket):
|
|
| 22 |
active_dashboards.inc()
|
| 23 |
logger.info("Dashboard Connected...")
|
| 24 |
|
|
|
|
|
|
|
|
|
|
| 25 |
try:
|
|
|
|
| 26 |
while True:
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
#
|
| 32 |
-
|
| 33 |
|
| 34 |
except WebSocketDisconnect:
|
| 35 |
logger.warn("Dashboard Disconnected Normally...")
|
|
@@ -39,4 +47,6 @@ async def dashboard_websocket(websocket: WebSocket):
|
|
| 39 |
traceback.print_exc()
|
| 40 |
|
| 41 |
finally:
|
| 42 |
-
active_dashboards.dec()
|
|
|
|
|
|
|
|
|
| 2 |
from backend.api.routers.metrics import active_dashboards
|
| 3 |
import asyncio
|
| 4 |
import traceback
|
| 5 |
+
import redis.asyncio as aioredis
|
| 6 |
|
| 7 |
router = APIRouter()
|
| 8 |
|
|
|
|
| 15 |
"""
|
| 16 |
state = websocket.app.state
|
| 17 |
logger = state.logger
|
| 18 |
+
redis = state.redis
|
| 19 |
|
| 20 |
# Accept the client connection.
|
| 21 |
await websocket.accept()
|
|
|
|
| 24 |
active_dashboards.inc()
|
| 25 |
logger.info("Dashboard Connected...")
|
| 26 |
|
| 27 |
+
pubsub = redis.pubsub()
|
| 28 |
+
await pubsub.subscribe("dashboard_stream")
|
| 29 |
+
|
| 30 |
try:
|
| 31 |
+
|
| 32 |
while True:
|
| 33 |
+
message = await pubsub.get_message(ignore_subscribe_messages=True)
|
| 34 |
+
|
| 35 |
+
if message:
|
| 36 |
+
logger.debug("Sending updates to Dashboard...")
|
| 37 |
+
await websocket.send_text(message["data"])
|
| 38 |
|
| 39 |
+
await asyncio.sleep(0.01) # giving time to detect server disconnection.
|
| 40 |
+
|
| 41 |
|
| 42 |
except WebSocketDisconnect:
|
| 43 |
logger.warn("Dashboard Disconnected Normally...")
|
|
|
|
| 47 |
traceback.print_exc()
|
| 48 |
|
| 49 |
finally:
|
| 50 |
+
active_dashboards.dec()
|
| 51 |
+
await pubsub.unsubscribe("dashboard_stream")
|
| 52 |
+
await pubsub.close()
|
main.py
CHANGED
|
@@ -14,6 +14,10 @@ import asyncio
|
|
| 14 |
import mlflow
|
| 15 |
from backend.utils.experiment import log_config
|
| 16 |
import torch
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
@asynccontextmanager
|
| 19 |
async def lifespan(app: FastAPI):
|
|
@@ -23,33 +27,39 @@ async def lifespan(app: FastAPI):
|
|
| 23 |
|
| 24 |
settings = AppConfig()
|
| 25 |
logger = StructLogger(settings=settings)
|
| 26 |
-
|
| 27 |
|
| 28 |
logger.info("Starting Server.... ")
|
| 29 |
-
asyncio.create_task(log_system_metrics(
|
| 30 |
-
logger,
|
| 31 |
-
logger_interval_sec=settings.intervals.system_metrics_seconds))
|
| 32 |
|
| 33 |
# Using this way to can store data. it is acts as a dict which holds instances
|
| 34 |
app.state.detection_model = YOLO_Detector(settings.yolo.model_path)
|
| 35 |
app.state.depth_model = DepthAnything(encoder=settings.depth.encoder, depth_model_path=settings.depth.model_path, DEVICE="cuda")
|
| 36 |
|
| 37 |
-
# safety_detection_path =
|
| 38 |
-
# repo_id="e1250/safety_detection",
|
| 39 |
-
# filename="yolo_smoke_fire.pt",
|
| 40 |
-
# )
|
| 41 |
app.state.safety_detection_model = YOLO_Detector(settings.security_detector.model_path)
|
| 42 |
|
| 43 |
app.state.logger = logger
|
| 44 |
app.state.settings = settings
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
# Each camera should have its tracker to be able to work fine.
|
| 46 |
# app.state.camera_trackers = {}
|
| 47 |
-
app.state.camera_metadata = {}
|
| 48 |
-
app.state.dashboard_clients = set()
|
| 49 |
yield
|
| 50 |
|
| 51 |
logger.warn("Shutting down the server....")
|
| 52 |
torch.cuda.empty_cache()
|
|
|
|
| 53 |
# You can remove connections and release gpu here .
|
| 54 |
|
| 55 |
mlflow.set_tracking_uri("sqlite:///config/logs/mlflow.db")
|
|
|
|
| 14 |
import mlflow
|
| 15 |
from backend.utils.experiment import log_config
|
| 16 |
import torch
|
| 17 |
+
from redis.asyncio import Redis
|
| 18 |
+
from huggingface_hub import hf_hub_download
|
| 19 |
+
import redis.asyncio as aioredis
|
| 20 |
+
|
| 21 |
|
| 22 |
@asynccontextmanager
|
| 23 |
async def lifespan(app: FastAPI):
|
|
|
|
| 27 |
|
| 28 |
settings = AppConfig()
|
| 29 |
logger = StructLogger(settings=settings)
|
|
|
|
| 30 |
|
| 31 |
logger.info("Starting Server.... ")
|
| 32 |
+
asyncio.create_task(log_system_metrics(logger, logger_interval_sec=settings.intervals.system_metrics_seconds))
|
|
|
|
|
|
|
| 33 |
|
| 34 |
# Using this way to can store data. it is acts as a dict which holds instances
|
| 35 |
app.state.detection_model = YOLO_Detector(settings.yolo.model_path)
|
| 36 |
app.state.depth_model = DepthAnything(encoder=settings.depth.encoder, depth_model_path=settings.depth.model_path, DEVICE="cuda")
|
| 37 |
|
| 38 |
+
# safety_detection_path = hf_hub_download(repo_id="e1250/safety_detection", filename="yolo_smoke_fire.pt")
|
|
|
|
|
|
|
|
|
|
| 39 |
app.state.safety_detection_model = YOLO_Detector(settings.security_detector.model_path)
|
| 40 |
|
| 41 |
app.state.logger = logger
|
| 42 |
app.state.settings = settings
|
| 43 |
+
# app.state.camera_metadata = {}
|
| 44 |
+
# app.state.dashboard_clients = set()
|
| 45 |
+
# Redis(host="localhost", port=6379, db=0, decode_responses=True)
|
| 46 |
+
app.state.redis = aioredis.from_url("redis://localhost:6379", db=0, decode_responses=True)
|
| 47 |
+
# Cnecking connection to redis.
|
| 48 |
+
# Thinking of moving this to the health check.
|
| 49 |
+
try:
|
| 50 |
+
await app.state.redis.ping()
|
| 51 |
+
logger.info("Redis connected successfully...")
|
| 52 |
+
except Exception as e:
|
| 53 |
+
logger.error(f"Failed to connect to Redis: {e}")
|
| 54 |
+
raise e
|
| 55 |
+
|
| 56 |
# Each camera should have its tracker to be able to work fine.
|
| 57 |
# app.state.camera_trackers = {}
|
|
|
|
|
|
|
| 58 |
yield
|
| 59 |
|
| 60 |
logger.warn("Shutting down the server....")
|
| 61 |
torch.cuda.empty_cache()
|
| 62 |
+
await app.state.redis.close()
|
| 63 |
# You can remove connections and release gpu here .
|
| 64 |
|
| 65 |
mlflow.set_tracking_uri("sqlite:///config/logs/mlflow.db")
|
requirements.txt
CHANGED
|
@@ -8,4 +8,6 @@ pydantic
|
|
| 8 |
pydantic_settings
|
| 9 |
structlog
|
| 10 |
|
| 11 |
-
tracking_system@git+https://github.com/E1250/p-tracking_system.git
|
|
|
|
|
|
|
|
|
| 8 |
pydantic_settings
|
| 9 |
structlog
|
| 10 |
|
| 11 |
+
tracking_system@git+https://github.com/E1250/p-tracking_system.git
|
| 12 |
+
|
| 13 |
+
redis
|