Subh775 commited on
Commit
c53fbf0
·
1 Parent(s): 58c8891

threaded vdeo processing, report building..

Browse files
Files changed (2) hide show
  1. backend/engine.py +42 -4
  2. backend/server.py +20 -12
backend/engine.py CHANGED
@@ -20,11 +20,50 @@ def _point_to_segment_dist(px, py, ax, ay, bx, by):
20
  return np.linalg.norm(P - (A + t * AB))
21
 
22
 
 
 
 
23
  # Lightweight drawing colors (BGR for OpenCV)
24
  _CLR_BOX = (230, 180, 50) # teal-ish
25
  _CLR_LINE = (80, 220, 100) # green
26
  _CLR_TEXT_BG = (30, 30, 30) # dark bg for text
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
  def _draw_annotations(frame, boxes, ids, clses, line_pts, options):
30
  """Draw bounding boxes, track IDs, and counting line on frame in-place."""
@@ -91,8 +130,7 @@ def run(model, video_path, line, config, on_frame, save_annotated=False, annotat
91
  os.makedirs(annotated_dir, exist_ok=True)
92
  annotated_path = os.path.join(annotated_dir, f"annotated_{os.path.basename(video_path)}.mp4")
93
  writer_fps = max(1.0, fps / stride)
94
- fourcc = cv2.VideoWriter_fourcc(*"mp4v")
95
- writer = cv2.VideoWriter(annotated_path, fourcc, writer_fps, (out_w, out_h))
96
 
97
  prev_side = {}
98
  counted_ids = set()
@@ -112,7 +150,7 @@ def run(model, video_path, line, config, on_frame, save_annotated=False, annotat
112
  results = model.track(
113
  source=video_path,
114
  tracker="bytetrack.yaml",
115
- imgsz=736, # MUST match OpenVINO export imgsz
116
  conf=config.get("conf", 0.12),
117
  iou=config.get("iou", 0.6),
118
  vid_stride=stride,
@@ -210,7 +248,7 @@ def run(model, video_path, line, config, on_frame, save_annotated=False, annotat
210
  on_frame(update)
211
 
212
  if writer is not None:
213
- writer.release()
214
 
215
  processing_time = round(time.time() - start, 2)
216
  actual_fps = round(total / processing_time, 2) if processing_time > 0 else 0
 
20
  return np.linalg.norm(P - (A + t * AB))
21
 
22
 
23
+ import threading
24
+ import queue
25
+
26
  # Lightweight drawing colors (BGR for OpenCV)
27
  _CLR_BOX = (230, 180, 50) # teal-ish
28
  _CLR_LINE = (80, 220, 100) # green
29
  _CLR_TEXT_BG = (30, 30, 30) # dark bg for text
30
 
31
+ class ThreadedVideoWriter:
32
+ def __init__(self, path, fps, size):
33
+ self.path = path
34
+ self.fps = fps
35
+ self.size = size
36
+ self.queue = queue.Queue(maxsize=128)
37
+ self.stopped = False
38
+ self.writer = cv2.VideoWriter(path, cv2.VideoWriter_fourcc(*"mp4v"), fps, size)
39
+ self.thread = threading.Thread(target=self._run, daemon=True)
40
+ self.thread.start()
41
+
42
+ def _run(self):
43
+ while not self.stopped or not self.queue.empty():
44
+ try:
45
+ frame = self.queue.get(timeout=1.0)
46
+ if frame is not None:
47
+ self.writer.write(frame)
48
+ self.queue.task_done()
49
+ except queue.Empty:
50
+ continue
51
+ self.writer.release()
52
+ print(f"[BACKEND] Threaded writer finished: {self.path}")
53
+
54
+ def write(self, frame):
55
+ if not self.stopped:
56
+ try:
57
+ # If queue is full, we might want to wait or drop, but here we'll wait
58
+ # to ensure the export video is complete and accurate.
59
+ self.queue.put(frame.copy())
60
+ except Exception as e:
61
+ print(f"[BACKEND] Writer queue error: {e}")
62
+
63
+ def stop(self):
64
+ self.stopped = True
65
+ self.thread.join()
66
+
67
 
68
  def _draw_annotations(frame, boxes, ids, clses, line_pts, options):
69
  """Draw bounding boxes, track IDs, and counting line on frame in-place."""
 
130
  os.makedirs(annotated_dir, exist_ok=True)
131
  annotated_path = os.path.join(annotated_dir, f"annotated_{os.path.basename(video_path)}.mp4")
132
  writer_fps = max(1.0, fps / stride)
133
+ writer = ThreadedVideoWriter(annotated_path, writer_fps, (out_w, out_h))
 
134
 
135
  prev_side = {}
136
  counted_ids = set()
 
150
  results = model.track(
151
  source=video_path,
152
  tracker="bytetrack.yaml",
153
+ imgsz=640, # Optimized from 736 for real-time performance on CPU
154
  conf=config.get("conf", 0.12),
155
  iou=config.get("iou", 0.6),
156
  vid_stride=stride,
 
248
  on_frame(update)
249
 
250
  if writer is not None:
251
+ writer.stop()
252
 
253
  processing_time = round(time.time() - start, 2)
254
  actual_fps = round(total / processing_time, 2) if processing_time > 0 else 0
backend/server.py CHANGED
@@ -5,6 +5,7 @@ import asyncio
5
  import tempfile
6
  import shutil
7
  from pathlib import Path
 
8
 
9
  import cv2
10
  from fastapi import FastAPI, WebSocket, UploadFile, File
@@ -134,6 +135,7 @@ def get_report(video_id: str, name: str):
134
  media = "application/pdf"
135
  elif name.endswith(".mp4"):
136
  media = "video/mp4"
 
137
  return FileResponse(str(path), media_type=media)
138
 
139
 
@@ -146,29 +148,35 @@ def download_all_reports(video_id: str):
146
  return Response(content=f"Report directory not found for {video_id}", status_code=404)
147
 
148
  try:
149
- # Create zip in temp dir
150
- zip_base = REPORT_DIR / f"bundle_{video_id}"
151
- if os.path.exists(f"{zip_base}.zip"):
152
- os.remove(f"{zip_base}.zip")
 
153
 
154
- print(f"[BACKEND] Archiving {base_path} to {zip_base}.zip")
155
- shutil.make_archive(str(zip_base), 'zip', str(base_path))
 
 
 
 
 
156
 
157
- final_zip = Path(f"{zip_base}.zip")
158
- if not final_zip.exists():
159
  raise Exception("Zip file was not created")
160
 
161
  original_name = video_info.get(video_id, "UrbanFlow_Analysis")
162
  safe_name = "".join(x for x in original_name if x.isalnum() or x in "._-").rsplit(".", 1)[0]
163
 
164
- print(f"[BACKEND] Serving ZIP: {final_zip}")
165
  return FileResponse(
166
- str(final_zip),
167
  media_type="application/zip",
168
- filename=f"{safe_name}_reports.zip"
169
  )
170
  except Exception as e:
171
- print(f"[BACKEND] ZIP Error: {str(e)}")
 
172
  return Response(content=str(e), status_code=500)
173
 
174
 
 
5
  import tempfile
6
  import shutil
7
  from pathlib import Path
8
+ import zipfile
9
 
10
  import cv2
11
  from fastapi import FastAPI, WebSocket, UploadFile, File
 
135
  media = "application/pdf"
136
  elif name.endswith(".mp4"):
137
  media = "video/mp4"
138
+
139
  return FileResponse(str(path), media_type=media)
140
 
141
 
 
148
  return Response(content=f"Report directory not found for {video_id}", status_code=404)
149
 
150
  try:
151
+ zip_filename = f"bundle_{video_id}.zip"
152
+ zip_path = REPORT_DIR / zip_filename
153
+
154
+ if zip_path.exists():
155
+ zip_path.unlink()
156
 
157
+ print(f"[BACKEND] Creating ZIP: {zip_path}")
158
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
159
+ for root, _, files in os.walk(base_path):
160
+ for file in files:
161
+ file_path = os.path.join(root, file)
162
+ arcname = os.path.relpath(file_path, base_path)
163
+ zipf.write(file_path, arcname)
164
 
165
+ if not zip_path.exists():
 
166
  raise Exception("Zip file was not created")
167
 
168
  original_name = video_info.get(video_id, "UrbanFlow_Analysis")
169
  safe_name = "".join(x for x in original_name if x.isalnum() or x in "._-").rsplit(".", 1)[0]
170
 
171
+ print(f"[BACKEND] Serving ZIP: {zip_path}")
172
  return FileResponse(
173
+ str(zip_path),
174
  media_type="application/zip",
175
+ filename=f"{safe_name}_UrbanFlow.zip"
176
  )
177
  except Exception as e:
178
+ import traceback
179
+ print(f"[BACKEND] ZIP Error: {str(e)}\n{traceback.format_exc()}")
180
  return Response(content=str(e), status_code=500)
181
 
182