Zhen Ye commited on
Commit
3a793fe
·
1 Parent(s): 90f0a6b

feat: Real-time radar directionality via track sync

Browse files
app.py CHANGED
@@ -438,6 +438,31 @@ async def detect_status(job_id: str):
438
  }
439
 
440
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  @app.delete("/detect/job/{job_id}")
442
  async def cancel_job(job_id: str):
443
  """Cancel a running job."""
 
438
  }
439
 
440
 
441
+ @app.get("/detect/tracks/{job_id}/{frame_idx}")
442
+ async def get_frame_tracks(job_id: str, frame_idx: int):
443
+ """Retrieve detections (with tracking info) for a specific frame."""
444
+ # This requires us to store detections PER FRAME in JobStorage or similar.
445
+ # Currently, inference.py returns 'sorted_detections' at the end.
446
+ # But during streaming, where is it?
447
+ # We can peek into the 'stream_queue' logic or we need a shared store.
448
+ # Ideally, inference should write to a map/db that we can read.
449
+
450
+ # Quick fix: If job is done, we might have it. If running, it's harder absent a DB.
451
+ # BUT, 'stream_queue' sends frames.
452
+ # Let's use a global cache in memory for active jobs?
453
+ # See inference.py: 'all_detections_map' is local to that function.
454
+
455
+ # BETTER APPROACH for this demo:
456
+ # Use a simple shared dictionary in jobs/storage.py or app.py used by inference.
457
+ # We will pass a callback or shared dict to run_inference.
458
+
459
+ # For now, let's just return 404 if not implemented, but I need to implement it.
460
+ # I'll add a cache in app.py for active job tracks?
461
+ from jobs.storage import get_track_data
462
+ data = get_track_data(job_id, frame_idx)
463
+ return data or []
464
+
465
+
466
  @app.delete("/detect/job/{job_id}")
467
  async def cancel_job(job_id: str):
468
  """Cancel a running job."""
deployment_logs.txt ADDED
@@ -0,0 +1,398 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ data: {"data":"===== Application Startup at 2026-01-23 22:02:27 =====\n","timestamp":"2026-01-23T22:02:27Z"}
2
+
3
+ data: {"data":"Set HF_HOME to /tmp/huggingface","timestamp":"2026-01-23T22:02:32.613Z"}
4
+
5
+ data: {"data":"CUDA_VISIBLE_DEVICES not set. All GPUs should be visible.","timestamp":"2026-01-23T22:02:32.613Z"}
6
+
7
+ data: {"data":"Startup Diagnostics: Torch version 2.9.1+cu128, CUDA available: True, Device count: 4","timestamp":"2026-01-23T22:02:34.118Z"}
8
+
9
+ data: {"data":"WARNING ⚠️ user config directory '/home/user/.config/Ultralytics' is not writable, using '/tmp/Ultralytics'. Set YOLO_CONFIG_DIR to override.","timestamp":"2026-01-23T22:02:36.713Z"}
10
+
11
+ data: {"data":"Creating new Ultralytics Settings v0.0.6 file ✅ ","timestamp":"2026-01-23T22:02:36.721Z"}
12
+
13
+ data: {"data":"View Ultralytics Settings with 'yolo settings' or at '/tmp/Ultralytics/settings.json'","timestamp":"2026-01-23T22:02:36.721Z"}
14
+
15
+ data: {"data":"Update Settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'. For help see https://docs.ultralytics.com/quickstart/#ultralytics-settings.","timestamp":"2026-01-23T22:02:36.721Z"}
16
+
17
+ data: {"data":"INFO: Started server process [1]","timestamp":"2026-01-23T22:02:36.837Z"}
18
+
19
+ data: {"data":"INFO: Waiting for application startup.","timestamp":"2026-01-23T22:02:36.837Z"}
20
+
21
+ data: {"data":"INFO: Application startup complete.","timestamp":"2026-01-23T22:02:36.837Z"}
22
+
23
+ data: {"data":"INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)","timestamp":"2026-01-23T22:02:36.837Z"}
24
+
25
+ data: {"data":"INFO: 10.16.14.243:63556 - \"GET /?logs=container HTTP/1.1\" 307 Temporary Redirect","timestamp":"2026-01-23T22:02:38.715Z"}
26
+
27
+ data: {"data":"INFO: 10.16.42.137:52271 - \"GET /?logs=container HTTP/1.1\" 307 Temporary Redirect","timestamp":"2026-01-23T22:02:38.764Z"}
28
+
29
+ data: {"data":"INFO: 10.16.14.243:63556 - \"GET /laser/index.html HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:38.884Z"}
30
+
31
+ data: {"data":"INFO: 10.16.14.243:63556 - \"GET /laser/js/init.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:38.978Z"}
32
+
33
+ data: {"data":"INFO: 10.16.42.137:52271 - \"GET /laser/style.css HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:38.979Z"}
34
+
35
+ data: {"data":"INFO: 10.16.14.243:32424 - \"GET /laser/js/core/config.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.056Z"}
36
+
37
+ data: {"data":"INFO: 10.16.42.137:11947 - \"GET /laser/js/core/state.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.061Z"}
38
+
39
+ data: {"data":"INFO: 10.16.42.137:38479 - \"GET /laser/js/core/utils.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.061Z"}
40
+
41
+ data: {"data":"INFO: 10.16.14.243:21985 - \"GET /laser/js/core/physics.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.072Z"}
42
+
43
+ data: {"data":"INFO: 10.16.42.137:61072 - \"GET /laser/js/core/video.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.073Z"}
44
+
45
+ data: {"data":"INFO: 10.16.14.243:19017 - \"GET /laser/js/ui/logging.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.073Z"}
46
+
47
+ data: {"data":"INFO: 10.16.42.137:8536 - \"GET /laser/js/core/hel.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.074Z"}
48
+
49
+ data: {"data":"INFO: 10.16.14.243:55450 - \"GET /laser/js/ui/cards.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.075Z"}
50
+
51
+ data: {"data":"INFO: 10.16.42.137:7723 - \"GET /laser/js/api/client.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.075Z"}
52
+
53
+ data: {"data":"INFO: 10.16.14.243:61397 - \"GET /laser/js/ui/features.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.075Z"}
54
+
55
+ data: {"data":"INFO: 10.16.14.243:47351 - \"GET /laser/js/core/tracker.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.075Z"}
56
+
57
+ data: {"data":"INFO: 10.16.42.137:28176 - \"GET /laser/js/ui/overlays.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.076Z"}
58
+
59
+ data: {"data":"INFO: 10.16.42.137:33696 - \"GET /laser/js/ui/radar.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.076Z"}
60
+
61
+ data: {"data":"INFO: 10.16.14.243:60830 - \"GET /laser/js/main.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.076Z"}
62
+
63
+ data: {"data":"INFO: 10.16.42.137:13066 - \"GET /laser/js/ui/cursor.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.077Z"}
64
+
65
+ data: {"data":"INFO: 10.16.42.137:52984 - \"GET /laser/js/ui/intel.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.078Z"}
66
+
67
+ data: {"data":"INFO: 10.16.14.243:33644 - \"GET /laser/js/ui/trade.js HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:02:39.080Z"}
68
+
69
+ data: {"data":"INFO:root:Loading Hugging Face YOLOv8 weights spencercdz/YOLOv8m_defence/yolov8m_defence.pt onto cuda:0","timestamp":"2026-01-23T22:02:53.356Z"}
70
+
71
+ data: {"data":"INFO:root:GPT Output for First Frame:","timestamp":"2026-01-23T22:03:01.919Z"}
72
+
73
+ data: {"data":"{'T01': {'id': 'T01', 'distance_m': 300.0, 'direction': \"1 o'clock\", 'description': 'Helicopter in the upper right'}, 'T02': {'id': 'T02', 'distance_m': 350.0, 'direction': \"10 o'clock\", 'description': 'Helicopter in the upper left'}, 'T03': {'id': 'T03', 'distance_m': 280.0, 'direction': \"12 o'clock\", 'description': 'Helicopter in the center'}, 'T04': {'id': 'T04', 'distance_m': 320.0, 'direction': \"11 o'clock\", 'description': 'Helicopter slightly left of center'}}","timestamp":"2026-01-23T22:03:01.919Z"}
74
+
75
+ data: {"data":"INFO: 10.16.14.243:35547 - \"POST /detect/async HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:01.926Z"}
76
+
77
+ data: {"data":"INFO:root:Detection queries: ['person', 'car', 'truck', 'motorcycle', 'bicycle', 'bus', 'train', 'airplane']","timestamp":"2026-01-23T22:03:01.941Z"}
78
+
79
+ data: {"data":"INFO:root:Detected 1 GPUs. Loading models in parallel...","timestamp":"2026-01-23T22:03:01.941Z"}
80
+
81
+ data: {"data":"INFO:root:Loading Hugging Face YOLOv8 weights spencercdz/YOLOv8m_defence/yolov8m_defence.pt onto cuda:0","timestamp":"2026-01-23T22:03:01.942Z"}
82
+
83
+ data: {"data":"INFO: 10.16.14.243:35547 - \"GET /detect/first-frame/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:02.031Z"}
84
+
85
+ data: {"data":"INFO:root:Running GPT estimation for video start (Frame 0)...","timestamp":"2026-01-23T22:03:03.012Z"}
86
+
87
+ data: {"data":"INFO: 10.16.14.243:37805 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:05.147Z"}
88
+
89
+ data: {"data":"ERROR:root:Worker failed processing frame","timestamp":"2026-01-23T22:03:05.472Z"}
90
+
91
+ data: {"data":"Traceback (most recent call last):","timestamp":"2026-01-23T22:03:05.472Z"}
92
+
93
+ data: {"data":" File \"/app/inference.py\", line 1048, in flush_batch","timestamp":"2026-01-23T22:03:05.472Z"}
94
+
95
+ data: {"data":" queue_out.put((idx, processed, detections), timeout=1.0)","timestamp":"2026-01-23T22:03:05.472Z"}
96
+
97
+ data: {"data":" File \"/usr/local/lib/python3.10/queue.py\", line 148, in put","timestamp":"2026-01-23T22:03:05.472Z"}
98
+
99
+ data: {"data":" raise Full","timestamp":"2026-01-23T22:03:05.472Z"}
100
+
101
+ data: {"data":"queue.Full","timestamp":"2026-01-23T22:03:05.472Z"}
102
+
103
+ data: {"data":"","timestamp":"2026-01-23T22:03:05.472Z"}
104
+
105
+ data: {"data":"During handling of the above exception, another exception occurred:","timestamp":"2026-01-23T22:03:05.472Z"}
106
+
107
+ data: {"data":"","timestamp":"2026-01-23T22:03:05.472Z"}
108
+
109
+ data: {"data":"Traceback (most recent call last):","timestamp":"2026-01-23T22:03:05.472Z"}
110
+
111
+ data: {"data":" File \"/app/inference.py\", line 1072, in worker_task","timestamp":"2026-01-23T22:03:05.472Z"}
112
+
113
+ data: {"data":" flush_batch()","timestamp":"2026-01-23T22:03:05.472Z"}
114
+
115
+ data: {"data":" File \"/app/inference.py\", line 1050, in flush_batch","timestamp":"2026-01-23T22:03:05.472Z"}
116
+
117
+ data: {"data":" except Full:","timestamp":"2026-01-23T22:03:05.472Z"}
118
+
119
+ data: {"data":"NameError: name 'Full' is not defined","timestamp":"2026-01-23T22:03:05.472Z"}
120
+
121
+ data: {"data":"Exception in thread Thread-4 (worker_task):","timestamp":"2026-01-23T22:03:05.472Z"}
122
+
123
+ data: {"data":"Traceback (most recent call last):","timestamp":"2026-01-23T22:03:05.472Z"}
124
+
125
+ data: {"data":" File \"/app/inference.py\", line 1048, in flush_batch","timestamp":"2026-01-23T22:03:05.472Z"}
126
+
127
+ data: {"data":" queue_out.put((idx, processed, detections), timeout=1.0)","timestamp":"2026-01-23T22:03:05.472Z"}
128
+
129
+ data: {"data":" File \"/usr/local/lib/python3.10/queue.py\", line 148, in put","timestamp":"2026-01-23T22:03:05.472Z"}
130
+
131
+ data: {"data":" raise Full","timestamp":"2026-01-23T22:03:05.472Z"}
132
+
133
+ data: {"data":"queue.Full","timestamp":"2026-01-23T22:03:05.472Z"}
134
+
135
+ data: {"data":"","timestamp":"2026-01-23T22:03:05.472Z"}
136
+
137
+ data: {"data":"During handling of the above exception, another exception occurred:","timestamp":"2026-01-23T22:03:05.472Z"}
138
+
139
+ data: {"data":"","timestamp":"2026-01-23T22:03:05.472Z"}
140
+
141
+ data: {"data":"Traceback (most recent call last):","timestamp":"2026-01-23T22:03:05.472Z"}
142
+
143
+ data: {"data":" File \"/usr/local/lib/python3.10/threading.py\", line 1016, in _bootstrap_inner","timestamp":"2026-01-23T22:03:05.472Z"}
144
+
145
+ data: {"data":" self.run()","timestamp":"2026-01-23T22:03:05.472Z"}
146
+
147
+ data: {"data":" File \"/usr/local/lib/python3.10/threading.py\", line 953, in run","timestamp":"2026-01-23T22:03:05.472Z"}
148
+
149
+ data: {"data":" self._target(*self._args, **self._kwargs)","timestamp":"2026-01-23T22:03:05.472Z"}
150
+
151
+ data: {"data":" File \"/app/inference.py\", line 1072, in worker_task","timestamp":"2026-01-23T22:03:05.473Z"}
152
+
153
+ data: {"data":" flush_batch()","timestamp":"2026-01-23T22:03:05.473Z"}
154
+
155
+ data: {"data":" File \"/app/inference.py\", line 1050, in flush_batch","timestamp":"2026-01-23T22:03:05.473Z"}
156
+
157
+ data: {"data":" except Full:","timestamp":"2026-01-23T22:03:05.473Z"}
158
+
159
+ data: {"data":"NameError: name 'Full' is not defined","timestamp":"2026-01-23T22:03:05.473Z"}
160
+
161
+ data: {"data":"INFO: 10.16.14.243:37805 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:08.144Z"}
162
+
163
+ data: {"data":"ERROR:root:Workers stopped unexpectedly.","timestamp":"2026-01-23T22:03:09.919Z"}
164
+
165
+ data: {"data":"INFO: 10.16.14.243:37805 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:11.146Z"}
166
+
167
+ data: {"data":"INFO: 10.16.14.243:37805 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:14.146Z"}
168
+
169
+ data: {"data":"INFO: 10.16.14.243:37805 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:17.147Z"}
170
+
171
+ data: {"data":"INFO: 10.16.42.137:24819 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:20.146Z"}
172
+
173
+ data: {"data":"INFO: 10.16.14.243:44429 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:23.148Z"}
174
+
175
+ data: {"data":"INFO: 10.16.42.137:53311 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:26.145Z"}
176
+
177
+ data: {"data":"INFO: 10.16.42.137:53311 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:29.146Z"}
178
+
179
+ data: {"data":"INFO: 10.16.42.137:53862 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:32.152Z"}
180
+
181
+ data: {"data":"INFO: 10.16.14.243:19151 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:35.152Z"}
182
+
183
+ data: {"data":"INFO: 10.16.42.137:55390 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:38.148Z"}
184
+
185
+ data: {"data":"INFO: 10.16.42.137:55390 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:41.150Z"}
186
+
187
+ data: {"data":"INFO: 10.16.14.243:57552 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:44.145Z"}
188
+
189
+ data: {"data":"INFO: 10.16.42.137:40211 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:47.147Z"}
190
+
191
+ data: {"data":"INFO: 10.16.42.137:40211 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:50.145Z"}
192
+
193
+ data: {"data":"INFO: 10.16.14.243:65208 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:53.153Z"}
194
+
195
+ data: {"data":"INFO: 10.16.14.243:65208 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:56.149Z"}
196
+
197
+ data: {"data":"INFO: 10.16.42.137:40007 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:03:59.150Z"}
198
+
199
+ data: {"data":"INFO: 10.16.42.137:40007 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:02.144Z"}
200
+
201
+ data: {"data":"INFO: 10.16.14.243:12635 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:05.147Z"}
202
+
203
+ data: {"data":"INFO: 10.16.14.243:12584 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:08.151Z"}
204
+
205
+ data: {"data":"INFO: 10.16.42.137:20413 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:11.149Z"}
206
+
207
+ data: {"data":"INFO: 10.16.42.137:20413 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:14.150Z"}
208
+
209
+ data: {"data":"INFO: 10.16.14.243:38842 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:17.148Z"}
210
+
211
+ data: {"data":"INFO: 10.16.14.243:38842 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:20.150Z"}
212
+
213
+ data: {"data":"INFO: 10.16.14.243:38842 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:23.145Z"}
214
+
215
+ data: {"data":"INFO: 10.16.14.243:7310 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:26.151Z"}
216
+
217
+ data: {"data":"INFO: 10.16.42.137:30457 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:29.152Z"}
218
+
219
+ data: {"data":"INFO: 10.16.14.243:50162 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:32.148Z"}
220
+
221
+ data: {"data":"INFO: 10.16.14.243:50162 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:35.147Z"}
222
+
223
+ data: {"data":"INFO: 10.16.14.243:45255 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:38.152Z"}
224
+
225
+ data: {"data":"INFO: 10.16.14.243:45255 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:41.145Z"}
226
+
227
+ data: {"data":"INFO: 10.16.42.137:61679 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:44.150Z"}
228
+
229
+ data: {"data":"INFO: 10.16.42.137:61679 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:47.148Z"}
230
+
231
+ data: {"data":"INFO: 10.16.42.137:37407 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:50.156Z"}
232
+
233
+ data: {"data":"INFO: 10.16.42.137:37407 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:53.155Z"}
234
+
235
+ data: {"data":"INFO: 10.16.14.243:12888 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:56.146Z"}
236
+
237
+ data: {"data":"INFO: 10.16.42.137:54774 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:04:59.151Z"}
238
+
239
+ data: {"data":"INFO: 10.16.14.243:13189 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:02.153Z"}
240
+
241
+ data: {"data":"INFO: 10.16.42.137:7369 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:05.150Z"}
242
+
243
+ data: {"data":"INFO: 10.16.14.243:44904 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:08.151Z"}
244
+
245
+ data: {"data":"INFO: 10.16.14.243:44904 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:11.146Z"}
246
+
247
+ data: {"data":"INFO: 10.16.14.243:44904 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:14.146Z"}
248
+
249
+ data: {"data":"INFO: 10.16.42.137:38667 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:17.148Z"}
250
+
251
+ data: {"data":"INFO: 10.16.14.243:17731 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:20.149Z"}
252
+
253
+ data: {"data":"INFO: 10.16.14.243:17731 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:23.147Z"}
254
+
255
+ data: {"data":"INFO: 10.16.42.137:27197 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:26.152Z"}
256
+
257
+ data: {"data":"INFO: 10.16.42.137:27197 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:29.146Z"}
258
+
259
+ data: {"data":"INFO: 10.16.14.243:61177 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:32.148Z"}
260
+
261
+ data: {"data":"INFO: 10.16.14.243:61177 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:35.149Z"}
262
+
263
+ data: {"data":"INFO: 10.16.14.243:61177 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:38.150Z"}
264
+
265
+ data: {"data":"INFO: 10.16.14.243:61177 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:41.146Z"}
266
+
267
+ data: {"data":"INFO: 10.16.14.243:61177 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:44.147Z"}
268
+
269
+ data: {"data":"INFO: 10.16.14.243:61177 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:47.147Z"}
270
+
271
+ data: {"data":"INFO: 10.16.14.243:61177 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:50.146Z"}
272
+
273
+ data: {"data":"INFO: 10.16.14.243:61177 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:53.146Z"}
274
+
275
+ data: {"data":"INFO: 10.16.14.243:61177 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:56.147Z"}
276
+
277
+ data: {"data":"INFO: 10.16.42.137:41705 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:05:59.152Z"}
278
+
279
+ data: {"data":"INFO: 10.16.42.137:41705 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:02.149Z"}
280
+
281
+ data: {"data":"INFO: 10.16.14.243:51116 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:05.147Z"}
282
+
283
+ data: {"data":"INFO: 10.16.14.243:51116 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:08.147Z"}
284
+
285
+ data: {"data":"INFO: 10.16.42.137:59918 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:11.148Z"}
286
+
287
+ data: {"data":"INFO: 10.16.42.137:37252 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:14.153Z"}
288
+
289
+ data: {"data":"INFO: 10.16.14.243:49604 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:17.152Z"}
290
+
291
+ data: {"data":"INFO: 10.16.42.137:24055 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:20.148Z"}
292
+
293
+ data: {"data":"INFO: 10.16.42.137:24055 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:23.148Z"}
294
+
295
+ data: {"data":"INFO: 10.16.14.243:24858 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:26.149Z"}
296
+
297
+ data: {"data":"INFO: 10.16.42.137:65148 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:29.152Z"}
298
+
299
+ data: {"data":"INFO: 10.16.14.243:29064 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:32.154Z"}
300
+
301
+ data: {"data":"INFO: 10.16.14.243:29064 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:35.151Z"}
302
+
303
+ data: {"data":"INFO: 10.16.42.137:44620 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:38.152Z"}
304
+
305
+ data: {"data":"INFO: 10.16.14.243:46299 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:41.149Z"}
306
+
307
+ data: {"data":"INFO: 10.16.42.137:8876 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:44.150Z"}
308
+
309
+ data: {"data":"INFO: 10.16.42.137:8876 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:47.149Z"}
310
+
311
+ data: {"data":"INFO: 10.16.42.137:8876 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:50.149Z"}
312
+
313
+ data: {"data":"INFO: 10.16.42.137:8876 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:53.147Z"}
314
+
315
+ data: {"data":"INFO: 10.16.42.137:14006 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:56.154Z"}
316
+
317
+ data: {"data":"INFO: 10.16.14.243:55621 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:06:59.153Z"}
318
+
319
+ data: {"data":"INFO: 10.16.14.243:55621 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:02.151Z"}
320
+
321
+ data: {"data":"INFO: 10.16.14.243:19848 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:05.159Z"}
322
+
323
+ data: {"data":"INFO: 10.16.42.137:12980 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:08.151Z"}
324
+
325
+ data: {"data":"INFO: 10.16.42.137:12980 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:11.152Z"}
326
+
327
+ data: {"data":"INFO: 10.16.14.243:27976 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:14.155Z"}
328
+
329
+ data: {"data":"INFO: 10.16.42.137:50949 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:17.154Z"}
330
+
331
+ data: {"data":"INFO: 10.16.14.243:47930 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:20.155Z"}
332
+
333
+ data: {"data":"INFO: 10.16.42.137:26741 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:23.154Z"}
334
+
335
+ data: {"data":"INFO: 10.16.14.243:45298 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:26.150Z"}
336
+
337
+ data: {"data":"INFO: 10.16.14.243:45298 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:29.151Z"}
338
+
339
+ data: {"data":"INFO: 10.16.14.243:45298 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:32.153Z"}
340
+
341
+ data: {"data":"INFO: 10.16.14.243:45298 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:35.148Z"}
342
+
343
+ data: {"data":"INFO: 10.16.14.243:63025 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:38.159Z"}
344
+
345
+ data: {"data":"INFO: 10.16.42.137:38082 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:41.153Z"}
346
+
347
+ data: {"data":"INFO: 10.16.42.137:38082 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:44.149Z"}
348
+
349
+ data: {"data":"INFO: 10.16.14.243:53673 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:47.152Z"}
350
+
351
+ data: {"data":"INFO: 10.16.14.243:5029 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:50.414Z"}
352
+
353
+ data: {"data":"INFO: 10.16.42.137:21004 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:53.415Z"}
354
+
355
+ data: {"data":"INFO: 10.16.42.137:21004 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:56.415Z"}
356
+
357
+ data: {"data":"INFO: 10.16.14.243:18558 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:07:59.415Z"}
358
+
359
+ data: {"data":"INFO: 10.16.42.137:21979 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:02.416Z"}
360
+
361
+ data: {"data":"INFO: 10.16.14.243:4153 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:05.414Z"}
362
+
363
+ data: {"data":"INFO: 10.16.42.137:47440 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:08.415Z"}
364
+
365
+ data: {"data":"INFO: 10.16.42.137:47440 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:11.412Z"}
366
+
367
+ data: {"data":"INFO: 10.16.14.243:51902 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:14.416Z"}
368
+
369
+ data: {"data":"INFO: 10.16.42.137:50678 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:17.416Z"}
370
+
371
+ data: {"data":"INFO: 10.16.42.137:50678 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:20.411Z"}
372
+
373
+ data: {"data":"INFO: 10.16.14.243:24592 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:23.410Z"}
374
+
375
+ data: {"data":"INFO: 10.16.14.243:24592 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:26.409Z"}
376
+
377
+ data: {"data":"INFO: 10.16.14.243:43041 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:29.415Z"}
378
+
379
+ data: {"data":"INFO: 10.16.14.243:13327 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:32.422Z"}
380
+
381
+ data: {"data":"INFO: 10.16.42.137:48743 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:35.421Z"}
382
+
383
+ data: {"data":"INFO: 10.16.14.243:64507 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:38.412Z"}
384
+
385
+ data: {"data":"INFO: 10.16.14.243:29935 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:41.493Z"}
386
+
387
+ data: {"data":"INFO: 10.16.14.243:29935 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:44.419Z"}
388
+
389
+ data: {"data":"INFO: 10.16.14.243:12182 - \"GET /detect/status/720717af953045d5816689f0b22bbecd HTTP/1.1\" 200 OK","timestamp":"2026-01-23T22:08:47.428Z"}
390
+
391
+ data: {"data":"INFO: Shutting down","timestamp":"2026-01-23T22:08:55.311Z"}
392
+
393
+ data: {"data":"INFO: Waiting for application shutdown.","timestamp":"2026-01-23T22:08:55.411Z"}
394
+
395
+ data: {"data":"INFO: Application shutdown complete.","timestamp":"2026-01-23T22:08:55.412Z"}
396
+
397
+ data: {"data":"INFO: Finished server process [1]","timestamp":"2026-01-23T22:08:55.412Z"}
398
+
frontend/js/core/state.js CHANGED
@@ -49,6 +49,7 @@ APP.core.state = {
49
  tracks: [],
50
  nextId: 1,
51
  lastDetTime: 0,
 
52
  running: false,
53
  selectedTrackId: null,
54
  beamOn: false,
 
49
  tracks: [],
50
  nextId: 1,
51
  lastDetTime: 0,
52
+ lastHFSync: 0,
53
  running: false,
54
  selectedTrackId: null,
55
  beamOn: false,
frontend/js/core/tracker.js CHANGED
@@ -141,6 +141,64 @@ APP.core.tracker.matchAndUpdateTracks = function (dets, dtSec) {
141
  state.tracker.tracks = tracks.filter(tr => (tNow - tr.lastSeen) < CONFIG.TRACK_PRUNE_MS || tr.killed);
142
  };
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  APP.core.tracker.predictTracks = function (dtSec) {
145
  const { state } = APP.core;
146
  const { $ } = APP.core.utils;
 
141
  state.tracker.tracks = tracks.filter(tr => (tNow - tr.lastSeen) < CONFIG.TRACK_PRUNE_MS || tr.killed);
142
  };
143
 
144
+ // Polling for backend tracks
145
+ APP.core.tracker.syncWithBackend = async function (frameIdx) {
146
+ const { state } = APP.core;
147
+ const { $ } = APP.core.utils;
148
+ const jobId = state.hf.asyncJobId;
149
+
150
+ if (!jobId || !state.hf.baseUrl) return;
151
+
152
+ try {
153
+ const resp = await fetch(`${state.hf.baseUrl}/detect/tracks/${jobId}/${frameIdx}`);
154
+ if (!resp.ok) return;
155
+
156
+ const dets = await resp.json();
157
+ if (!dets || !Array.isArray(dets)) return;
158
+
159
+ // Transform backend format to frontend track format
160
+ // Backend: { bbox: [x1, y1, x2, y2], label: "car", track_id: "T01", angle_deg: 90, ... }
161
+ // Frontend: { id: "T01", bbox: {x,y,w,h}, label: "car", angle_deg: 90, ... }
162
+
163
+ const videoEngage = $("#videoEngage");
164
+ const w = videoEngage ? (videoEngage.videoWidth || state.frame.w) : state.frame.w;
165
+ const h = videoEngage ? (videoEngage.videoHeight || state.frame.h) : state.frame.h;
166
+
167
+ const newTracks = dets.map(d => {
168
+ const x = d.bbox[0], y = d.bbox[1];
169
+ const wBox = d.bbox[2] - d.bbox[0];
170
+ const hBox = d.bbox[3] - d.bbox[1];
171
+
172
+ // Normalize
173
+ const nx = x / w;
174
+ const ny = y / h;
175
+ const nw = wBox / w;
176
+ const nh = hBox / h;
177
+
178
+ return {
179
+ id: d.track_id || `T${Math.floor(Math.random() * 1000)}`, // Fallback
180
+ label: d.label,
181
+ bbox: { x: nx, y: ny, w: nw, h: nh },
182
+ score: d.score,
183
+ angle_deg: d.angle_deg,
184
+ gpt_distance_m: d.gpt_distance_m,
185
+ speed_kph: d.speed_kph,
186
+
187
+ // Keep UI state fields
188
+ lastSeen: Date.now(),
189
+ state: "TRACK"
190
+ };
191
+ });
192
+
193
+ // Update state
194
+ state.tracker.tracks = newTracks;
195
+ state.detections = newTracks; // Keep synced
196
+
197
+ } catch (e) {
198
+ console.warn("Track sync failed", e);
199
+ }
200
+ };
201
+
202
  APP.core.tracker.predictTracks = function (dtSec) {
203
  const { state } = APP.core;
204
  const { $ } = APP.core.utils;
frontend/js/main.js CHANGED
@@ -642,6 +642,18 @@ document.addEventListener("DOMContentLoaded", () => {
642
  // Update tracker when engaged
643
  if (state.tracker.running && videoEngage && !videoEngage.paused) {
644
  predictTracks(dt);
 
 
 
 
 
 
 
 
 
 
 
 
645
  }
646
 
647
  // Render UI
 
642
  // Update tracker when engaged
643
  if (state.tracker.running && videoEngage && !videoEngage.paused) {
644
  predictTracks(dt);
645
+
646
+ // Sync with backend every few frames (approx 5Hz)
647
+ if (t - state.tracker.lastHFSync > 200) {
648
+ // Estimate frame index
649
+ const fps = 30; // hardcoded for now, ideal: state.fps
650
+ const frameIdx = Math.floor(videoEngage.currentTime * fps);
651
+ // Only sync if we have a job ID
652
+ if (state.hf.asyncJobId) {
653
+ APP.core.tracker.syncWithBackend(frameIdx);
654
+ }
655
+ state.tracker.lastHFSync = t;
656
+ }
657
  }
658
 
659
  // Render UI
frontend/js/ui/radar.js CHANGED
@@ -123,10 +123,54 @@ APP.ui.radar.render = function (canvasId, trackSource) {
123
  if (isSelected) col = "#ffffff"; // White for selected
124
 
125
  ctx.fillStyle = col;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  ctx.beginPath();
127
- ctx.arc(px, py, isSelected ? 5 : 3.5, 0, Math.PI * 2);
 
 
 
128
  ctx.fill();
129
 
 
 
130
  // Selected UI overlays
131
  if (isSelected) {
132
  ctx.fillStyle = "#fff";
 
123
  if (isSelected) col = "#ffffff"; // White for selected
124
 
125
  ctx.fillStyle = col;
126
+ // Draw Triangle (Directional)
127
+ ctx.fillStyle = col;
128
+
129
+ ctx.save();
130
+ ctx.translate(px, py);
131
+
132
+ // Rotation:
133
+ // Backend angle: 0 is Right (Screen X+), 90 gets mapped to Down (Screen Y+).
134
+ // Radar view: Up is Forward (Screen Y-).
135
+ // We need to map screen motion to radar heading.
136
+ // Screen Right (0 deg) -> Radar Right (0 deg)
137
+ // Screen Down (90 deg) -> Radar Backwards (180 deg)?
138
+ // Screen Up (-90 deg) -> Radar Forwards (-90 deg)?
139
+
140
+ // Wait, radar usually maps:
141
+ // Top of radar = Forward.
142
+ // Screen perspective: Objects moving "Down" (Y+) are coming CLOSER (Backwards relative to view).
143
+ // Objects moving "Up" (Y-) are moving AWAY (Forwards relative to view).
144
+ // So: Screen Y+ (90 deg) -> Radar Down (90 deg)
145
+ // Screen Y- (-90 deg) -> Radar Up (-90 deg)
146
+ // It actually aligns well if we consider standard canvas coordinates where Y is down.
147
+ // But visually on radar, "Up" (Y=0) is usually forward/away.
148
+
149
+ let rotation = 0;
150
+ if (det.angle_deg !== undefined) {
151
+ // Convert degrees to radians
152
+ // Adjust phase:
153
+ // det.angle_deg is math angle (0=Right, 90=Down).
154
+ // If we want triangle to point in velocity direction:
155
+ // Just use the angle directly. Canvas rotation is clockwise.
156
+ rotation = det.angle_deg * (Math.PI / 180);
157
+ } else {
158
+ // Default (point up/forward if unknown?)
159
+ rotation = -Math.PI / 2;
160
+ }
161
+
162
+ ctx.rotate(rotation);
163
+
164
+ const size = isSelected ? 8 : 6;
165
  ctx.beginPath();
166
+ ctx.moveTo(size, 0); // Tip (Right, at 0 deg)
167
+ ctx.lineTo(-size / 2, -size / 2); // Top Left
168
+ ctx.lineTo(-size / 2, size / 2); // Bottom Left
169
+ ctx.closePath();
170
  ctx.fill();
171
 
172
+ ctx.restore();
173
+
174
  // Selected UI overlays
175
  if (isSelected) {
176
  ctx.fillStyle = "#fff";
inference.py CHANGED
@@ -437,6 +437,7 @@ class SpeedEstimator:
437
  clock_hour = ((angle + 90) / 30 + 12) % 12
438
  if clock_hour == 0: clock_hour = 12.0
439
  det['direction_clock'] = f"{int(round(clock_hour))} o'clock"
 
440
 
441
 
442
  _MODEL_LOCKS: Dict[str, RLock] = {}
@@ -1217,6 +1218,11 @@ def run_inference(
1217
  pass
1218
 
1219
  all_detections_map[next_idx] = dets
 
 
 
 
 
1220
  next_idx += 1
1221
 
1222
  if next_idx % 30 == 0:
 
437
  clock_hour = ((angle + 90) / 30 + 12) % 12
438
  if clock_hour == 0: clock_hour = 12.0
439
  det['direction_clock'] = f"{int(round(clock_hour))} o'clock"
440
+ det['angle_deg'] = angle # 0 is right, 90 is down (screen space)
441
 
442
 
443
  _MODEL_LOCKS: Dict[str, RLock] = {}
 
1218
  pass
1219
 
1220
  all_detections_map[next_idx] = dets
1221
+
1222
+ # Store tracks for frontend access
1223
+ if job_id:
1224
+ set_track_data(job_id, next_idx, dets)
1225
+
1226
  next_idx += 1
1227
 
1228
  if next_idx % 30 == 0:
jobs/storage.py CHANGED
@@ -38,11 +38,22 @@ def get_first_frame_depth_path(job_id: str) -> Path:
38
  class JobStorage:
39
  def __init__(self) -> None:
40
  self._jobs: Dict[str, JobInfo] = {}
 
41
  self._lock = RLock()
42
 
43
  def create(self, job: JobInfo) -> None:
44
  with self._lock:
45
  self._jobs[job.job_id] = job
 
 
 
 
 
 
 
 
 
 
46
 
47
  def get(self, job_id: str) -> Optional[JobInfo]:
48
  with self._lock:
@@ -59,6 +70,7 @@ class JobStorage:
59
  def delete(self, job_id: str) -> None:
60
  with self._lock:
61
  self._jobs.pop(job_id, None)
 
62
  shutil.rmtree(get_job_directory(job_id), ignore_errors=True)
63
 
64
  def cleanup_expired(self, max_age: timedelta) -> None:
@@ -80,3 +92,9 @@ def get_job_storage() -> JobStorage:
80
  if _STORAGE is None:
81
  _STORAGE = JobStorage()
82
  return _STORAGE
 
 
 
 
 
 
 
38
  class JobStorage:
39
  def __init__(self) -> None:
40
  self._jobs: Dict[str, JobInfo] = {}
41
+ self._tracks: Dict[str, Dict[int, list]] = {} # job_id -> {frame_idx -> tracks}
42
  self._lock = RLock()
43
 
44
  def create(self, job: JobInfo) -> None:
45
  with self._lock:
46
  self._jobs[job.job_id] = job
47
+ self._tracks[job.job_id] = {}
48
+
49
+ def set_track_data(self, job_id: str, frame_idx: int, tracks: list) -> None:
50
+ with self._lock:
51
+ if job_id in self._tracks:
52
+ self._tracks[job_id][frame_idx] = tracks
53
+
54
+ def get_track_data(self, job_id: str, frame_idx: int) -> list:
55
+ with self._lock:
56
+ return self._tracks.get(job_id, {}).get(frame_idx, [])
57
 
58
  def get(self, job_id: str) -> Optional[JobInfo]:
59
  with self._lock:
 
70
  def delete(self, job_id: str) -> None:
71
  with self._lock:
72
  self._jobs.pop(job_id, None)
73
+ self._tracks.pop(job_id, None)
74
  shutil.rmtree(get_job_directory(job_id), ignore_errors=True)
75
 
76
  def cleanup_expired(self, max_age: timedelta) -> None:
 
92
  if _STORAGE is None:
93
  _STORAGE = JobStorage()
94
  return _STORAGE
95
+
96
+ def get_track_data(job_id: str, frame_idx: int) -> list:
97
+ return get_job_storage().get_track_data(job_id, frame_idx)
98
+
99
+ def set_track_data(job_id: str, frame_idx: int, tracks: list) -> None:
100
+ get_job_storage().set_track_data(job_id, frame_idx, tracks)