Zhen Ye commited on
Commit
fe2ee0d
·
1 Parent(s): b30c328

Integrated GPT Reasoning and Object Track Cards

Browse files
LaserPerception/LaserPerception.css CHANGED
@@ -866,4 +866,62 @@ input[type="number"]:focus {
866
 
867
  ::-webkit-scrollbar-thumb:hover {
868
  background: rgba(255, 255, 255, .16);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
869
  }
 
866
 
867
  ::-webkit-scrollbar-thumb:hover {
868
  background: rgba(255, 255, 255, .16);
869
+ }
870
+
871
+ /* Track Cards */
872
+ .track-card {
873
+ background: rgba(255, 255, 255, 0.03);
874
+ border: 1px solid var(--border-color);
875
+ border-radius: 4px;
876
+ padding: 8px;
877
+ margin-bottom: 8px;
878
+ cursor: pointer;
879
+ transition: all 0.2s;
880
+ }
881
+
882
+ .track-card:hover {
883
+ background: rgba(255, 255, 255, 0.08);
884
+ }
885
+
886
+ .track-card.active {
887
+ border-color: var(--accent);
888
+ background: rgba(34, 211, 238, 0.1);
889
+ }
890
+
891
+ .track-card-header {
892
+ display: flex;
893
+ justify-content: space-between;
894
+ align-items: center;
895
+ font-weight: 600;
896
+ margin-bottom: 4px;
897
+ font-size: 13px;
898
+ color: var(--text-color);
899
+ }
900
+
901
+ .track-card-meta {
902
+ font-size: 11px;
903
+ color: var(--text-dim);
904
+ margin-bottom: 4px;
905
+ }
906
+
907
+ .track-card-body {
908
+ font-size: 11px;
909
+ line-height: 1.4;
910
+ color: #ccc;
911
+ background: rgba(0, 0, 0, 0.2);
912
+ padding: 6px;
913
+ border-radius: 4px;
914
+ }
915
+
916
+ .gpt-badge {
917
+ color: gold;
918
+ font-size: 10px;
919
+ border: 1px solid gold;
920
+ border-radius: 3px;
921
+ padding: 1px 4px;
922
+ margin-left: 6px;
923
+ }
924
+
925
+ .gpt-text {
926
+ color: #e0e0e0;
927
  }
LaserPerception/LaserPerception.html CHANGED
@@ -95,6 +95,10 @@
95
  <input type="checkbox" id="enableDepthToggle">
96
  <span>Enable Legacy Depth Map (Slow)</span>
97
  </label>
 
 
 
 
98
  </div>
99
 
100
  <div class="hint mt-sm" id="detectorHint">
@@ -335,194 +339,156 @@
335
  </div>
336
  </div>
337
 
338
- <div class="panel panel-summary">
339
  <h3>
340
- <span>HEL Summary Outputs</span>
341
- <span class="rightnote" id="summaryStamp">—</span>
342
  </h3>
343
-
344
- <div class="metricgrid">
345
- <div class="metric">
346
- <div class="label">HEL Max power at target</div>
347
- <div class="value" id="mMaxP">—</div>
348
- <div class="sub" id="mMaxPSub">Propagation + beam control + duty cycle</div>
349
- </div>
350
- <div class="metric">
351
- <div class="label">Worst-case required power</div>
352
- <div class="value" id="mReqP">—</div>
353
- <div class="sub" id="mReqPSub">From target robustness + reflectivity + aimpoint</div>
354
- </div>
355
- <div class="metric">
356
- <div class="label">Feasibility margin</div>
357
- <div class="value" id="mMargin">—</div>
358
- <div class="sub" id="mMarginSub">Positive margin indicates viable engagement</div>
359
- </div>
360
- <div class="metric">
361
- <div class="label">Recommended engagement</div>
362
- <div class="value" id="mPlan">—</div>
363
- <div class="sub" id="mPlanSub">Order + dwell window + assess</div>
364
  </div>
365
  </div>
 
366
 
367
- <div class="scroll-table mt-md">
368
- <table class="table" id="summaryTable">
369
- <thead>
370
- <tr>
371
- <th style="width:9%">ID</th>
372
- <th style="width:16%">Class</th>
373
- <th style="width:14%">Range (m)</th>
374
- <th style="width:16%">Aimpoint</th>
375
- <th style="width:15%">Req P@Target</th>
376
- <th style="width:15%">Max P@Target</th>
377
- <th style="width:15%">Req Dwell</th>
378
- <th style="width:10%">P(kill)</th>
379
- </tr>
380
- </thead>
381
- <tbody>
382
- <tr>
383
- <td class="k">—</td>
384
- <td colspan="7" class="mini">No outputs yet. Click <b>Reason</b>.</td>
385
- </tr>
386
- </tbody>
387
- </table>
388
- </div>
 
 
389
  </div>
390
  </div>
391
- </section>
392
-
393
- <!-- ===== Tab 2 ===== -->
394
- <section class="tab" id="tab-engage">
395
- <div class="engage-grid">
396
- <div class="panel">
397
- <h3>
398
- <span>Video Engage · Tracking + Dynamic Dwell</span>
399
- <div style="display: flex; gap: 8px; align-items: center;">
400
- <button class="collapse-btn" id="btnToggleSidebar">◀ Hide Sidebar</button>
401
- <span class="rightnote" id="engageNote">Awaiting video</span>
402
- </div>
403
- </h3>
404
-
405
- <div class="viewbox" style="min-height: 420px;">
406
- <video id="videoEngage" playsinline muted></video>
407
- <canvas id="engageOverlay" class="overlay"></canvas>
408
- <div class="watermark">LOCK · DIST · DWELL · AIMPOINT · FIRE/ASSESS</div>
409
- <div class="empty" id="engageEmpty">
410
- <div class="big">No video loaded</div>
411
- <div class="small">Upload a video. Run <b>Reason</b> first to initialize aimpoints and baseline dwell.
412
- Then click <b>Engage</b>.</div>
413
- </div>
414
- </div>
415
 
416
- <div class="btnrow mt-md">
417
- <button id="btnEngage" class="btn">Engage</button>
418
- <button id="btnPause" class="btn secondary">Pause</button>
419
- <button id="btnReset" class="btn secondary">Reset</button>
420
- </div>
421
-
422
- <div class="strip mt-md">
423
- <span class="chip" id="chipPolicy">POLICY:AUTO</span>
424
- <span class="chip" id="chipTracks">TRACKS:0</span>
425
- <span class="chip" id="chipBeam">BEAM:OFF</span>
426
- <span class="chip" id="chipHz">DET:6Hz</span>
427
- <span class="chip" id="chipFeed" title="Toggle raw vs HF-processed feed (if available)">FEED:RAW</span>
428
- <span class="chip" id="chipDepth" title="Toggle depth view (if available)">VIEW:DEFAULT</span>
429
- </div>
430
 
431
- <div class="mt-md">
432
- <div class="row"><label>Active dwell progress (selected)</label><small class="mini"
433
- id="dwellText">—</small></div>
434
- <div class="bar">
435
- <div id="dwellBar"></div>
436
- </div>
437
- </div>
 
438
 
439
- <div class="hint mt-md">Manual targeting: choose “Manual” in Engagement Policy, then
440
- click a target in the video. The “beam” will track its aimpoint and accumulate dwell.</div>
441
  </div>
442
-
443
- <div class="engage-right">
444
- <div class="panel radar">
445
- <h3>
446
- <span>Radar / Relative Geometry</span>
447
- <span class="rightnote">Dynamic</span>
448
- </h3>
449
- <canvas id="radarCanvas" width="600" height="260" class="full-size"></canvas>
450
- </div>
451
-
452
- <div class="panel" style="flex:1; min-height:0">
453
- <h3>
454
- <span>Live Track Cards</span>
455
- <span class="rightnote" id="liveStamp">—</span>
456
- </h3>
457
- <div class="list" id="trackList" style="max-height:none"></div>
458
- </div>
459
  </div>
460
  </div>
461
- </section>
462
-
463
- <!-- ===== Tab 3 ===== -->
464
- <section class="tab" id="tab-trade">
465
- <div class="trade-grid">
466
- <div class="panel plot">
467
- <h3>
468
- <span>Range Sensitivity · Max vs Required Power · Dwell</span>
469
- <span class="rightnote">Interactive</span>
470
- </h3>
471
- <canvas id="tradeCanvas" width="1100" height="420" class="full-size"></canvas>
472
- </div>
473
 
474
- <div class="panel">
475
- <h3>
476
- <span>Trade Controls</span>
477
- <span class="rightnote">What-if</span>
478
- </h3>
479
- <div class="hint">This plot is computed from your current HEL and atmosphere knobs. It uses the selected
480
- target’s baseline requirements (from Tab 1) as a reference curve.</div>
481
 
482
- <div class="mt-md">
483
- <label>Selected target for curve</label>
484
- <select id="tradeTarget"></select>
485
- </div>
 
 
 
 
486
 
487
- <div class="grid2 mt-sm">
488
- <div>
489
- <label>Range sweep min (m)</label>
490
- <input id="rMin" type="number" value="200" min="50" max="10000" step="50" />
491
- </div>
492
- <div>
493
- <label>Range sweep max (m)</label>
494
- <input id="rMax" type="number" value="6000" min="100" max="20000" step="50" />
495
- </div>
496
- </div>
 
 
 
 
 
 
 
 
 
 
 
497
 
498
- <div class="row mt-md">
499
- <label>Show P(kill)</label>
500
- <select id="showPk">
501
- <option value="on">On</option>
502
- <option value="off">Off</option>
503
- </select>
504
- </div>
505
 
506
- <div class="btnrow">
507
- <button class="btn secondary" id="btnReplot">Replot</button>
508
- <button class="btn secondary" id="btnSnap">Snapshot (log)</button>
509
- </div>
510
 
511
- <div class="hint">This tab is designed to look like a weapon trade-space console: propagation, lethality
512
- margin, and dwell inflation with range and atmosphere.</div>
 
 
 
 
 
 
513
  </div>
514
  </div>
515
- </section>
516
- </main>
517
- </div>
518
 
519
- <footer>
520
- <div>Demo mode · Unclassified visuals · Integrate your APIs where marked</div>
521
- <div class="mono" id="telemetry">HEL=60kW · VIS=16km · Cn²=5/10 · AO=7/10 · DET=6Hz</div>
522
- </footer>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
 
524
- <!-- Hidden video used only for first-frame capture -->
525
- <video id="videoHidden" playsinline muted style="display:none"></video>
526
  </div>
527
 
528
  <script>
 
95
  <input type="checkbox" id="enableDepthToggle">
96
  <span>Enable Legacy Depth Map (Slow)</span>
97
  </label>
98
+ <label class="checkbox-row" for="enableGPTToggle" style="margin-top: 4px;">
99
+ <input type="checkbox" id="enableGPTToggle">
100
+ <span style="color: var(--accent-light);">Enable GPT Reasoning</span>
101
+ </label>
102
  </div>
103
 
104
  <div class="hint mt-sm" id="detectorHint">
 
339
  </div>
340
  </div>
341
 
342
+ <div class="panel panel-summary" style="display:flex; flex-direction:column; min-height: 0;">
343
  <h3>
344
+ <span>Object Track Cards</span>
345
+ <span class="rightnote" id="trackCount">0</span>
346
  </h3>
347
+ <div class="list" id="frameTrackList" style="flex:1; overflow-y:auto; padding:8px;">
348
+ <!-- Cards injected here -->
349
+ <div style="font-style:italic; color:var(--text-dim); text-align:center; margin-top:20px;">
350
+ No objects tracked.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  </div>
352
  </div>
353
+ </div>
354
 
355
+ </div>
356
+ </div>
357
+ </section>
358
+
359
+ <!-- ===== Tab 2 ===== -->
360
+ <section class="tab" id="tab-engage">
361
+ <div class="engage-grid">
362
+ <div class="panel">
363
+ <h3>
364
+ <span>Video Engage · Tracking + Dynamic Dwell</span>
365
+ <div style="display: flex; gap: 8px; align-items: center;">
366
+ <button class="collapse-btn" id="btnToggleSidebar">◀ Hide Sidebar</button>
367
+ <span class="rightnote" id="engageNote">Awaiting video</span>
368
+ </div>
369
+ </h3>
370
+
371
+ <div class="viewbox" style="min-height: 420px;">
372
+ <video id="videoEngage" playsinline muted></video>
373
+ <canvas id="engageOverlay" class="overlay"></canvas>
374
+ <div class="watermark">LOCK · DIST · DWELL · AIMPOINT · FIRE/ASSESS</div>
375
+ <div class="empty" id="engageEmpty">
376
+ <div class="big">No video loaded</div>
377
+ <div class="small">Upload a video. Run <b>Reason</b> first to initialize aimpoints and baseline dwell.
378
+ Then click <b>Engage</b>.</div>
379
  </div>
380
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
 
382
+ <div class="btnrow mt-md">
383
+ <button id="btnEngage" class="btn">Engage</button>
384
+ <button id="btnPause" class="btn secondary">Pause</button>
385
+ <button id="btnReset" class="btn secondary">Reset</button>
386
+ </div>
 
 
 
 
 
 
 
 
 
387
 
388
+ <div class="strip mt-md">
389
+ <span class="chip" id="chipPolicy">POLICY:AUTO</span>
390
+ <span class="chip" id="chipTracks">TRACKS:0</span>
391
+ <span class="chip" id="chipBeam">BEAM:OFF</span>
392
+ <span class="chip" id="chipHz">DET:6Hz</span>
393
+ <span class="chip" id="chipFeed" title="Toggle raw vs HF-processed feed (if available)">FEED:RAW</span>
394
+ <span class="chip" id="chipDepth" title="Toggle depth view (if available)">VIEW:DEFAULT</span>
395
+ </div>
396
 
397
+ <div class="mt-md">
398
+ <div class="row"><label>Active dwell progress (selected)</label><small class="mini" id="dwellText">—</small>
399
  </div>
400
+ <div class="bar">
401
+ <div id="dwellBar"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  </div>
403
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
404
 
405
+ <div class="hint mt-md">Manual targeting: choose “Manual” in Engagement Policy, then
406
+ click a target in the video. The “beam” will track its aimpoint and accumulate dwell.</div>
407
+ </div>
 
 
 
 
408
 
409
+ <div class="engage-right">
410
+ <div class="panel radar">
411
+ <h3>
412
+ <span>Radar / Relative Geometry</span>
413
+ <span class="rightnote">Dynamic</span>
414
+ </h3>
415
+ <canvas id="radarCanvas" width="600" height="260" class="full-size"></canvas>
416
+ </div>
417
 
418
+ <div class="panel" style="flex:1; min-height:0">
419
+ <h3>
420
+ <span>Live Track Cards</span>
421
+ <span class="rightnote" id="liveStamp">—</span>
422
+ </h3>
423
+ <div class="list" id="trackList" style="max-height:none"></div>
424
+ </div>
425
+ </div>
426
+ </div>
427
+ </section>
428
+
429
+ <!-- ===== Tab 3 ===== -->
430
+ <section class="tab" id="tab-trade">
431
+ <div class="trade-grid">
432
+ <div class="panel plot">
433
+ <h3>
434
+ <span>Range Sensitivity · Max vs Required Power · Dwell</span>
435
+ <span class="rightnote">Interactive</span>
436
+ </h3>
437
+ <canvas id="tradeCanvas" width="1100" height="420" class="full-size"></canvas>
438
+ </div>
439
 
440
+ <div class="panel">
441
+ <h3>
442
+ <span>Trade Controls</span>
443
+ <span class="rightnote">What-if</span>
444
+ </h3>
445
+ <div class="hint">This plot is computed from your current HEL and atmosphere knobs. It uses the selected
446
+ target’s baseline requirements (from Tab 1) as a reference curve.</div>
447
 
448
+ <div class="mt-md">
449
+ <label>Selected target for curve</label>
450
+ <select id="tradeTarget"></select>
451
+ </div>
452
 
453
+ <div class="grid2 mt-sm">
454
+ <div>
455
+ <label>Range sweep min (m)</label>
456
+ <input id="rMin" type="number" value="200" min="50" max="10000" step="50" />
457
+ </div>
458
+ <div>
459
+ <label>Range sweep max (m)</label>
460
+ <input id="rMax" type="number" value="6000" min="100" max="20000" step="50" />
461
  </div>
462
  </div>
 
 
 
463
 
464
+ <div class="row mt-md">
465
+ <label>Show P(kill)</label>
466
+ <select id="showPk">
467
+ <option value="on">On</option>
468
+ <option value="off">Off</option>
469
+ </select>
470
+ </div>
471
+
472
+ <div class="btnrow">
473
+ <button class="btn secondary" id="btnReplot">Replot</button>
474
+ <button class="btn secondary" id="btnSnap">Snapshot (log)</button>
475
+ </div>
476
+
477
+ <div class="hint">This tab is designed to look like a weapon trade-space console: propagation, lethality
478
+ margin, and dwell inflation with range and atmosphere.</div>
479
+ </div>
480
+ </div>
481
+ </section>
482
+ </main>
483
+ </div>
484
+
485
+ <footer>
486
+ <div>Demo mode · Unclassified visuals · Integrate your APIs where marked</div>
487
+ <div class="mono" id="telemetry">HEL=60kW · VIS=16km · Cn²=5/10 · AO=7/10 · DET=6Hz</div>
488
+ </footer>
489
 
490
+ <!-- Hidden video used only for first-frame capture -->
491
+ <video id="videoHidden" playsinline muted style="display:none"></video>
492
  </div>
493
 
494
  <script>
LaserPerception/LaserPerception.js CHANGED
@@ -146,8 +146,11 @@
146
  const objCount = $("#objCount");
147
  const featureTable = $("#featureTable");
148
  const selId = $("#selId");
 
149
 
150
- const summaryStamp = $("#summaryStamp");
 
 
151
  const summaryTable = $("#summaryTable");
152
  const mMaxP = $("#mMaxP");
153
  const mReqP = $("#mReqP");
@@ -876,9 +879,11 @@
876
  // Add depth_estimator parameter for depth processing
877
  const enableDepthToggle = document.getElementById("enableDepthToggle");
878
  const useLegacyDepth = enableDepthToggle && enableDepthToggle.checked;
 
879
 
880
  form.append("depth_estimator", useLegacyDepth ? "depth" : "");
881
  form.append("enable_depth", useLegacyDepth ? "true" : "false");
 
882
 
883
  // Submit async job
884
  setHfStatus(`submitting ${mode} job...`);
@@ -1927,9 +1932,9 @@
1927
  // Clear previous detections before running new detection
1928
  state.detections = [];
1929
  state.selectedId = null;
1930
- renderObjectList();
1931
  renderFrameOverlay();
1932
- renderSummary();
1933
  renderFeatures(null);
1934
  renderTrade();
1935
 
@@ -2023,9 +2028,9 @@
2023
 
2024
  // pick default selection
2025
  state.selectedId = state.detections[0]?.id || null;
2026
- renderObjectList();
2027
  renderFrameOverlay();
2028
- renderSummary();
2029
  renderFeatures(getSelected());
2030
  renderTrade();
2031
 
@@ -2160,61 +2165,87 @@
2160
  }
2161
 
2162
  // ========= Rendering: Object list, features, summary table =========
2163
- function renderObjectList() {
2164
- objList.innerHTML = "";
2165
- objCount.textContent = `${state.detections.length}`;
2166
- if (!state.detections.length) {
2167
- const empty = document.createElement("div");
2168
- empty.className = "mini";
2169
- empty.style.padding = "8px";
2170
- empty.textContent = "No detections yet. Click Reason.";
2171
- objList.appendChild(empty);
2172
- return;
2173
  }
2174
 
2175
- const dets = state.detections.slice();
2176
- // Sort by confidence (mission filtering is handled by backend)
2177
- dets.sort((a, b) => ((b.score || 0) - (a.score || 0)));
 
2178
 
2179
- dets.forEach(d => {
2180
- const div = document.createElement("div");
2181
- div.className = "obj" + (d.id === state.selectedId ? " active" : "");
2182
- div.dataset.id = d.id;
2183
 
2184
- const rangeData = getDisplayRange(d, d.baseRange_m);
2185
- const rangeTxt = Number.isFinite(rangeData.range) ? `${Math.round(rangeData.range)} m` : "—";
2186
- const rangeSuffix = rangeTxt === "—" ? "" : ` (${rangeData.source})`;
2187
- const relVal = getDisplayRel(d);
2188
- const relTxt = relVal != null ? relVal.toFixed(2) : "—";
2189
- const dwellTxt = d.baseDwell_s ? `${d.baseDwell_s.toFixed(1)} s` : "—";
2190
- const pkTxt = (d.pkill != null) ? `${Math.round(d.pkill * 100)}%` : "—";
2191
 
2192
- div.innerHTML = `
2193
- <div class="top">
2194
- <div>
2195
- <div class="id">${d.id}</div>
2196
- <div class="cls">${escapeHtml(d.label)}</div>
2197
- </div>
2198
- <div style="display:flex; gap:8px; align-items:center; justify-content:flex-end;">${isMissionFocusLabel(d.label) ? `<span class="badge" style="border-color: rgba(34,211,238,.45); background: rgba(34,211,238,.08)">FOCUS</span>` : ""}<div class="badge"><span class="dot" style="width:7px;height:7px"></span><span>${Math.round(d.score * 100)}%</span></div></div>
2199
- </div>
2200
- <div class="meta">
2201
- <span class="badge">RANGE:${rangeTxt}${rangeSuffix}</span>
2202
- <span class="badge">REL:${relTxt}</span>
2203
- <span class="badge">DWELL:${dwellTxt}</span>
2204
- <span class="badge">P(k):${pkTxt}</span>
2205
- <span class="badge">AIM:${escapeHtml(d.aim?.label || "center")}</span>
2206
- </div>
2207
- `;
2208
 
2209
- div.addEventListener("click", () => {
2210
- state.selectedId = d.id;
2211
- renderObjectList();
2212
- renderFeatures(d);
2213
- renderFrameOverlay();
2214
- renderTrade();
2215
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2216
 
2217
- objList.appendChild(div);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2218
  });
2219
  }
2220
 
 
146
  const objCount = $("#objCount");
147
  const featureTable = $("#featureTable");
148
  const selId = $("#selId");
149
+ const checkEnableGPT = $("#enableGPTToggle");
150
 
151
+ const trackCount = $("#trackCount");
152
+ const frameTrackList = $("#frameTrackList");
153
+ // Removed old summary references
154
  const summaryTable = $("#summaryTable");
155
  const mMaxP = $("#mMaxP");
156
  const mReqP = $("#mReqP");
 
879
  // Add depth_estimator parameter for depth processing
880
  const enableDepthToggle = document.getElementById("enableDepthToggle");
881
  const useLegacyDepth = enableDepthToggle && enableDepthToggle.checked;
882
+ const useGPT = checkEnableGPT && checkEnableGPT.checked;
883
 
884
  form.append("depth_estimator", useLegacyDepth ? "depth" : "");
885
  form.append("enable_depth", useLegacyDepth ? "true" : "false");
886
+ form.append("enable_gpt", useGPT ? "true" : "false");
887
 
888
  // Submit async job
889
  setHfStatus(`submitting ${mode} job...`);
 
1932
  // Clear previous detections before running new detection
1933
  state.detections = [];
1934
  state.selectedId = null;
1935
+ renderFrameTrackList();
1936
  renderFrameOverlay();
1937
+ // renderSummary(); // Removed
1938
  renderFeatures(null);
1939
  renderTrade();
1940
 
 
2028
 
2029
  // pick default selection
2030
  state.selectedId = state.detections[0]?.id || null;
2031
+ renderFrameTrackList();
2032
  renderFrameOverlay();
2033
+ // renderSummary(); // Removed
2034
  renderFeatures(getSelected());
2035
  renderTrade();
2036
 
 
2165
  }
2166
 
2167
  // ========= Rendering: Object list, features, summary table =========
2168
+ // ========= Track Cards & Interaction =========
2169
+ function selectObject(id) {
2170
+ state.selectedId = id;
2171
+
2172
+ // Highlight Card
2173
+ $$(".track-card").forEach(el => el.classList.remove("active"));
2174
+ const card = document.getElementById("card-" + id);
2175
+ if (card) {
2176
+ // card.scrollIntoView({ behavior: "smooth", block: "nearest" });
2177
+ card.classList.add("active");
2178
  }
2179
 
2180
+ // Highlight BBox (via Overlay)
2181
+ renderFrameOverlay();
2182
+ // Highlight Radar uses state.selectedId, loops automatically
2183
+ }
2184
 
2185
+ function renderFrameTrackList() {
2186
+ if (!frameTrackList || !trackCount) return;
2187
+ frameTrackList.innerHTML = "";
 
2188
 
2189
+ const dets = state.detections || [];
2190
+ trackCount.textContent = dets.length;
 
 
 
 
 
2191
 
2192
+ if (dets.length === 0) {
2193
+ frameTrackList.innerHTML = '<div style="font-style:italic; color:var(--text-dim); text-align:center; margin-top:20px;">No objects tracked.</div>';
2194
+ return;
2195
+ }
 
 
 
 
 
 
 
 
 
 
 
 
2196
 
2197
+ dets.forEach((det, i) => {
2198
+ // ID: T01, T02...
2199
+ // Ensure ID exists
2200
+ const id = det.id || `T${String(i + 1).padStart(2, '0')}`;
2201
+
2202
+ // Resolve Range/Bearing
2203
+ let rangeStr = "---";
2204
+ let bearingStr = "---";
2205
+
2206
+ if (det.gpt_distance_m) {
2207
+ rangeStr = `${det.gpt_distance_m}m (GPT)`;
2208
+ } else if (det.depth_est_m) {
2209
+ rangeStr = `${Math.round(det.depth_est_m)}m (Lidar)`;
2210
+ } else {
2211
+ // Fallback
2212
+ if (det.box) {
2213
+ const [x1, y1, x2, y2] = det.box;
2214
+ const area = ((x2 - x1) * (y2 - y1)) / (state.frame.w * state.frame.h);
2215
+ const est = clamp(200 / Math.sqrt(Math.max(1e-6, area)), 50, 6000);
2216
+ rangeStr = `~${Math.round(est)}m (Est)`;
2217
+ }
2218
+ }
2219
+
2220
+ if (det.gpt_direction) {
2221
+ bearingStr = det.gpt_direction;
2222
+ }
2223
 
2224
+ const card = document.createElement("div");
2225
+ card.className = "track-card";
2226
+ if (state.selectedId === id) card.classList.add("active");
2227
+ card.id = `card-${id}`;
2228
+ card.onclick = () => selectObject(id);
2229
+
2230
+ const desc = det.gpt_description
2231
+ ? `<div class="track-card-body"><span class="gpt-text">${det.gpt_description}</span></div>`
2232
+ : ""; // No description, hide body
2233
+
2234
+ const gptBadge = (det.gpt_distance_m || det.gpt_description)
2235
+ ? `<span class="gpt-badge">GPT</span>`
2236
+ : "";
2237
+
2238
+ card.innerHTML = `
2239
+ <div class="track-card-header">
2240
+ <span>${id} · ${det.label} ${gptBadge}</span>
2241
+ <span class="badgemini">${(det.score * 100).toFixed(0)}%</span>
2242
+ </div>
2243
+ <div class="track-card-meta">
2244
+ RANGE: ${rangeStr} | BEARING: ${bearingStr}
2245
+ </div>
2246
+ ${desc}
2247
+ `;
2248
+ frameTrackList.appendChild(card);
2249
  });
2250
  }
2251
 
app.py CHANGED
@@ -146,6 +146,7 @@ async def detect_endpoint(
146
  detector: str = Form("hf_yolov8"),
147
  segmenter: str = Form("sam3"),
148
  enable_depth: bool = Form(False),
 
149
  ):
150
  """
151
  Main detection endpoint.
@@ -289,6 +290,7 @@ async def detect_async_endpoint(
289
  depth_estimator: str = Form("depth"),
290
  depth_scale: float = Form(25.0),
291
  enable_depth: bool = Form(False),
 
292
  ):
293
  if mode not in VALID_MODES:
294
  raise HTTPException(
@@ -346,6 +348,7 @@ async def detect_async_endpoint(
346
  depth_estimator_name=active_depth,
347
  depth_scale=depth_scale,
348
  enable_depth_estimator=enable_depth,
 
349
  )
350
  cv2.imwrite(str(first_frame_path), processed_frame)
351
  except Exception:
 
146
  detector: str = Form("hf_yolov8"),
147
  segmenter: str = Form("sam3"),
148
  enable_depth: bool = Form(False),
149
+ enable_gpt: bool = Form(False),
150
  ):
151
  """
152
  Main detection endpoint.
 
290
  depth_estimator: str = Form("depth"),
291
  depth_scale: float = Form(25.0),
292
  enable_depth: bool = Form(False),
293
+ enable_gpt: bool = Form(False),
294
  ):
295
  if mode not in VALID_MODES:
296
  raise HTTPException(
 
348
  depth_estimator_name=active_depth,
349
  depth_scale=depth_scale,
350
  enable_depth_estimator=enable_depth,
351
+ enable_gpt=enable_gpt,
352
  )
353
  cv2.imwrite(str(first_frame_path), processed_frame)
354
  except Exception:
inference.py CHANGED
@@ -404,6 +404,7 @@ def process_first_frame(
404
  depth_estimator_name: Optional[str] = None,
405
  depth_scale: Optional[float] = None,
406
  enable_depth_estimator: bool = False,
 
407
  ) -> Tuple[np.ndarray, List[Dict[str, Any]]]:
408
  frame, _, _, _ = extract_first_frame(video_path)
409
  if mode == "segmentation":
@@ -426,8 +427,8 @@ def process_first_frame(
426
  _DEPTH_SCALE if depth_scale is None else depth_scale,
427
  )
428
 
429
- # 2. GPT-based Distance/Direction Estimation (Now gated by enable_depth_estimator to prevent "depth" appearing when unwanted)
430
- if enable_depth_estimator:
431
  # We need to save the frame temporarily to pass to GPT (or refactor gpt_distance to take buffer)
432
  # For now, write to temp file
433
  try:
 
404
  depth_estimator_name: Optional[str] = None,
405
  depth_scale: Optional[float] = None,
406
  enable_depth_estimator: bool = False,
407
+ enable_gpt: bool = False,
408
  ) -> Tuple[np.ndarray, List[Dict[str, Any]]]:
409
  frame, _, _, _ = extract_first_frame(video_path)
410
  if mode == "segmentation":
 
427
  _DEPTH_SCALE if depth_scale is None else depth_scale,
428
  )
429
 
430
+ # 2. GPT-based Distance/Direction Estimation (Explicitly enabled)
431
+ if enable_gpt:
432
  # We need to save the frame temporarily to pass to GPT (or refactor gpt_distance to take buffer)
433
  # For now, write to temp file
434
  try: