Subh775 commited on
Commit
a869123
·
1 Parent(s): 77210b8

restart/ crash/ tracker/ loading with minor fixes..

Browse files
Files changed (4) hide show
  1. backend/engine.py +10 -3
  2. backend/model.py +38 -14
  3. backend/server.py +18 -4
  4. requirements.txt +1 -0
backend/engine.py CHANGED
@@ -90,8 +90,9 @@ def run(model, video_path, line, config, on_frame, save_annotated=False, annotat
90
  annotated_dir = os.path.join(tempfile.gettempdir(), "funky_reports")
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
  fourcc = cv2.VideoWriter_fourcc(*"mp4v")
94
- writer = cv2.VideoWriter(annotated_path, fourcc, fps / stride, (out_w, out_h))
95
 
96
  prev_side = {}
97
  counted_ids = set()
@@ -144,6 +145,11 @@ def run(model, video_path, line, config, on_frame, save_annotated=False, annotat
144
 
145
  current = _side((cx, cy), a, b)
146
 
 
 
 
 
 
147
  if obj_id in prev_side and obj_id not in counted_ids:
148
  if prev_side[obj_id] != current:
149
  dist = _point_to_segment_dist(cx, cy, a[0], a[1], b[0], b[1])
@@ -160,10 +166,11 @@ def run(model, video_path, line, config, on_frame, save_annotated=False, annotat
160
 
161
  prev_side[obj_id] = current
162
 
163
- # Write annotated frame (only for frames we already process)
 
164
  if writer is not None:
165
  frame = r.orig_img.copy()
166
- _draw_annotations(frame, cur_boxes, cur_ids, cls if r.boxes.id is not None else None, [a, b], annotated_options)
167
  writer.write(frame)
168
 
169
  congestion.append(active)
 
90
  annotated_dir = os.path.join(tempfile.gettempdir(), "funky_reports")
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()
 
145
 
146
  current = _side((cx, cy), a, b)
147
 
148
+ # Skip if centroid is exactly on the line (cross-product == 0)
149
+ # — avoids misfired crossings due to floating-point boundary hits
150
+ if current == 0:
151
+ continue
152
+
153
  if obj_id in prev_side and obj_id not in counted_ids:
154
  if prev_side[obj_id] != current:
155
  dist = _point_to_segment_dist(cx, cy, a[0], a[1], b[0], b[1])
 
166
 
167
  prev_side[obj_id] = current
168
 
169
+ # Write annotated frame
170
+ cur_clses = cls if r.boxes.id is not None else None
171
  if writer is not None:
172
  frame = r.orig_img.copy()
173
+ _draw_annotations(frame, cur_boxes, cur_ids, cur_clses, [a, b], annotated_options)
174
  writer.write(frame)
175
 
176
  congestion.append(active)
backend/model.py CHANGED
@@ -5,27 +5,51 @@ from ultralytics import YOLO
5
 
6
  load_dotenv()
7
 
 
 
 
8
  MODEL_DIR = Path(__file__).parent / "weights"
9
- PT_PATH = MODEL_DIR / "best.pt"
10
  OV_DIR = MODEL_DIR / "best_int8_openvino_model"
11
 
 
 
 
 
 
 
 
 
12
 
13
- def ensure_openvino():
14
- MODEL_DIR.mkdir(exist_ok=True)
 
15
 
16
- if not PT_PATH.exists():
17
- token = os.getenv("HF_TOKEN")
18
- os.system(
19
- f'curl -L -H "Authorization: Bearer {token}" '
20
- f'-o {PT_PATH} '
21
- f'https://huggingface.co/Perception365/VehicleNet-Y26s/resolve/main/weights/best.pt'
22
- )
23
 
24
- # https://docs.ultralytics.com/modes/export/#arguments
25
- if not OV_DIR.exists():
26
- YOLO(str(PT_PATH)).export(format="openvino", imgsz=736, dynamic=False, int8=True, device='cpu', batch=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
 
29
  def load_model():
30
- ensure_openvino()
 
 
 
 
 
31
  return YOLO(str(OV_DIR), task="detect")
 
5
 
6
  load_dotenv()
7
 
8
+ # Silence Ultralytics config dir warning on read-only containers
9
+ os.environ.setdefault("YOLO_CONFIG_DIR", "/tmp/Ultralytics")
10
+
11
  MODEL_DIR = Path(__file__).parent / "weights"
 
12
  OV_DIR = MODEL_DIR / "best_int8_openvino_model"
13
 
14
+ _HF_BASE = (
15
+ "https://huggingface.co/Perception365/VehicleNet-Y26s"
16
+ "/resolve/main/weights/best_int8_openvino_model"
17
+ )
18
+
19
+ # The three files that make up the OpenVINO INT8 model
20
+ _OV_FILES = ["best.bin", "best.xml", "metadata.yaml"]
21
+
22
 
23
+ def _auth_header():
24
+ token = os.getenv("HF_TOKEN", "")
25
+ return f'-H "Authorization: Bearer {token}"' if token else ""
26
 
 
 
 
 
 
 
 
27
 
28
+ def _download_ov_model():
29
+ """Download the pre-built OV INT8 model files directly from HF."""
30
+ OV_DIR.mkdir(parents=True, exist_ok=True)
31
+ auth = _auth_header()
32
+ for filename in _OV_FILES:
33
+ dest = OV_DIR / filename
34
+ if dest.exists():
35
+ print(f"[model] {filename} already present, skipping.")
36
+ continue
37
+ url = f"{_HF_BASE}/{filename}"
38
+ print(f"[model] Downloading {filename} ...")
39
+ ret = os.system(f'curl -L --fail {auth} -o "{dest}" "{url}"')
40
+ if ret != 0 or not dest.exists() or dest.stat().st_size < 100:
41
+ raise RuntimeError(
42
+ f"[model] Failed to download {filename} from HF. "
43
+ "Check HF_TOKEN and repo visibility."
44
+ )
45
+ print("[model] OV model files ready ✅")
46
 
47
 
48
  def load_model():
49
+ if not OV_DIR.exists() or not all(
50
+ (OV_DIR / f).exists() for f in _OV_FILES
51
+ ):
52
+ _download_ov_model()
53
+ else:
54
+ print("[model] OV model already present — skipping download.")
55
  return YOLO(str(OV_DIR), task="detect")
backend/server.py CHANGED
@@ -17,7 +17,8 @@ from engine import run
17
  from constants import MODEL_CLASSES, BUSINESS_MAP
18
  from visualize import generate_all
19
 
20
- app = FastAPI()
 
21
 
22
  BASE = Path(__file__).parent.parent
23
  FRONTEND = BASE / "frontend"
@@ -31,10 +32,23 @@ run_results = {}
31
  model = None
32
 
33
 
34
- @app.on_event("startup")
35
- def startup():
36
  global model
37
  model = load_model()
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
 
40
  @app.get("/")
@@ -135,7 +149,7 @@ async def ws_run(ws: WebSocket):
135
 
136
  path = videos.get(video_id)
137
 
138
- loop = asyncio.get_event_loop()
139
 
140
  queue = asyncio.Queue()
141
 
 
17
  from constants import MODEL_CLASSES, BUSINESS_MAP
18
  from visualize import generate_all
19
 
20
+ from contextlib import asynccontextmanager
21
+ import numpy as np
22
 
23
  BASE = Path(__file__).parent.parent
24
  FRONTEND = BASE / "frontend"
 
32
  model = None
33
 
34
 
35
+ @asynccontextmanager
36
+ async def lifespan(app: FastAPI):
37
  global model
38
  model = load_model()
39
+ # Warm up: run a dummy inference so OpenVINO compiles its graph now,
40
+ # not on the first real user request
41
+ try:
42
+ dummy = np.zeros((1, 3, 640, 640), dtype=np.uint8)
43
+ model(dummy, verbose=False)
44
+ print("[BACKEND] Model warm-up complete.")
45
+ except Exception as e:
46
+ print(f"[BACKEND] Warm-up skipped: {e}")
47
+ yield
48
+ # Shutdown: nothing to clean up
49
+
50
+
51
+ app = FastAPI(lifespan=lifespan)
52
 
53
 
54
  @app.get("/")
 
149
 
150
  path = videos.get(video_id)
151
 
152
+ loop = asyncio.get_running_loop()
153
 
154
  queue = asyncio.Queue()
155
 
requirements.txt CHANGED
@@ -9,6 +9,7 @@ python-dotenv==1.0.0
9
  websockets==12.0
10
  onnxruntime
11
  openvino>=2024.0.0
 
12
  onnx>=1.12.0
13
  onnxslim>=0.1.71
14
  lap>=0.5.12
 
9
  websockets==12.0
10
  onnxruntime
11
  openvino>=2024.0.0
12
+ nncf>=2.14.0,<3.0.0
13
  onnx>=1.12.0
14
  onnxslim>=0.1.71
15
  lap>=0.5.12