e1250 commited on
Commit
22a85f1
·
1 Parent(s): 85d6e94

feat: Add redis to backend

Browse files
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(websocket: WebSocket, camera_id:str):
 
 
 
 
 
 
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
- detector = state.detection_model
29
- safety_detector = state.safety_detection_model
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, run_detection, image_array)
84
- safety_task = loop.run_in_executor(None, run_safety, image_array)
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, run_depth, image_array, boxes_center) if boxes_center else []
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
- state.camera_metadata[camera_id] = metadata.model_dump()
 
 
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
- logger.debug("Sending updates to Dashboard...")
28
- cameras_metadata = state.camera_metadata
29
- await websocket.send_json(cameras_metadata)
 
 
30
 
31
- # Sending data to the dashboard every 1.5 seconds.
32
- await asyncio.sleep(state.settings.intervals.realtime_updates_every)
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 = hg_hub_download(
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