sebastientaylor commited on
Commit
e0489cf
·
verified ·
1 Parent(s): 2a048e0

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +176 -83
index.html CHANGED
@@ -145,6 +145,9 @@
145
  border-bottom: 2px solid var(--border-strong);
146
  text-transform: uppercase; letter-spacing: 0.03em; font-size: 0.72rem;
147
  }
 
 
 
148
  table.dt td {
149
  padding: 0.5rem 0.65rem; border-bottom: 1px solid var(--border);
150
  vertical-align: middle;
@@ -182,6 +185,8 @@
182
  font-family: var(--mono); font-weight: 600; font-size: 0.72rem;
183
  }
184
  .cell-val { color: var(--good); background: var(--good-bg); padding: 0.15rem 0.35rem; border-radius: 3px; }
 
 
185
  .cell-plan { color: var(--warn); background: var(--warn-bg); padding: 0.15rem 0.35rem; border-radius: 3px; }
186
  .cell-none { color: var(--text-muted); opacity: 0.5; }
187
  .cell-target { color: var(--text-muted); background: #f4f4f4; padding: 0.15rem 0.35rem; border-radius: 3px; }
@@ -452,19 +457,6 @@
452
  </div>
453
  </div>
454
 
455
- <div class="section">
456
- <div class="section-header"><h2>Accuracy vs On-target Throughput</h2>
457
- <span class="section-note">COCO val2017 · pipelined FPS measured on each platform by the <a href="https://doc.edgefirst.ai/latest/profiler/" target="_blank" rel="noopener" style="color:var(--teal-text)">EdgeFirst Profiler</a></span>
458
- </div>
459
- <div class="chart-host" style="height:420px"><canvas id="chart-det-fps"></canvas></div>
460
- <p style="font-family:var(--serif);color:var(--text-muted);font-size:0.82rem;margin-top:0.5rem">
461
- Each point is a public validation session: color = platform, shape = model family
462
- (● YOLOv5 · ■ YOLOv8 · ▲ YOLO11 · ◆ YOLO26). X axis is sustained pipelined throughput on a log
463
- scale — the full image load → decode → preprocess → inference → postprocess pipeline, not bare
464
- inference time. ⚠ flagged sessions are shown at their measured accuracy.
465
- </p>
466
- </div>
467
-
468
  <div class="section">
469
  <div class="section-header"><h2>Nano Segmentation — Mask mAP</h2>
470
  <span class="section-note">YOLOv8 / YOLO11 / YOLO26, COCO val2017</span>
@@ -482,12 +474,25 @@
482
  </div>
483
 
484
  <div class="section">
485
- <div class="section-header"><h2>On-target Timing</h2>
486
- <span class="section-note">Measured by the <a href="https://doc.edgefirst.ai/latest/profiler/" target="_blank" rel="noopener" style="color:var(--teal-text)">EdgeFirst Profiler</a> on deployment hardware</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  </div>
488
  <div id="timing-host">
489
  <p style="font-family:var(--serif);color:var(--text-muted);font-size:0.92rem">
490
- On-target latency and throughput results are being collected across the platform matrix.
491
  See the <a href="https://doc.edgefirst.ai/latest/profiler/" target="_blank" rel="noopener" style="color:var(--teal-text)">EdgeFirst Profiler documentation</a>
492
  for how profiling and validation are performed.
493
  </p>
@@ -509,16 +514,16 @@
509
  </div>
510
  <table class="dt" id="studio-table" style="display:none">
511
  <thead><tr>
512
- <th>Session</th>
513
- <th>Family · Task</th>
514
- <th>Target</th>
515
- <th>Precision</th>
516
- <th>Det mAP<sub>50</sub></th>
517
- <th>Det mAP<sub>50-95</sub></th>
518
- <th>Mask mAP<sub>50-95</sub></th>
519
- <th>Inf (ms)</th>
520
- <th>E2E (ms)</th>
521
- <th>FPS</th>
522
  <th>Studio</th>
523
  </tr></thead>
524
  <tbody id="studio-body"></tbody>
@@ -545,7 +550,7 @@
545
  <div class="coverage-wrap"><table class="matrix" id="coverage-seg"></table></div>
546
  </div>
547
  <div class="legend">
548
- <span><span class="swatch" style="background:var(--good-bg);border:1px solid var(--good)"></span>Validated — public results on EdgeFirst Studio</span>
549
  <span><span class="swatch" style="background:transparent;border:1px solid var(--border)"></span>Validation pending</span>
550
  </div>
551
  </section>
@@ -568,7 +573,7 @@
568
  <section class="tab-panel" id="tab-leaderboard">
569
  <div class="section">
570
  <div class="section-header"><h2>Platform Throughput Leaderboard</h2>
571
- <span class="section-note">Best sustained pipelined throughput measured per platform · full image load → decode → preprocess → inference → postprocess pipeline</span>
572
  </div>
573
  <table class="dt">
574
  <thead><tr>
@@ -634,9 +639,14 @@
634
  // Platforms as rows: hosts (reference) first, then embedded targets, then Jetson.
635
  // `match` is tested (in order) against the Studio target/footer string to map a
636
  // validation session onto a platform row.
 
 
 
637
  const PLATFORM_ROWS = [
638
  { key: "linux-x86", label: "Linux x86_64", detail: "ONNX Runtime + CUDA", group: "reference", color: "#6C757D", match: /linux|x86|cuda/ },
639
- { key: "m2max", label: "Apple M2 Max", detail: "ONNX + CoreML (CPU, GPU, ANE)", group: "reference", color: "#4B0082", match: /macos|coreml/ },
 
 
640
  { key: "imx8mp", label: "NXP i.MX 8M Plus", detail: "VSI NPU · TFLite INT8", group: "embedded", color: "#E8B820", match: /imx8mp/ },
641
  { key: "imx93", label: "NXP i.MX 93", detail: "Ethos-U65 · VELA TFLite", group: "embedded", color: "#8B7DC4", match: /imx93/ },
642
  { key: "imx95", label: "NXP i.MX 95", detail: "eIQ Neutron · TFLite INT8", group: "embedded", color: "#1FA0A8", match: /imx95/ },
@@ -822,13 +832,20 @@ const COVERAGE_SIZES = ["n", "s", "m"];
822
  const SIZE_NAMES = { n: "Nano", s: "Small", m: "Medium" };
823
 
824
  // Build a lookup of validated (task, version, size, platform) cells from the
825
- // embedded Studio sessions.
 
826
  function coverageLookup() {
827
  const seen = {};
828
  for (const s of (STUDIO?.sessions ?? [])) {
829
  const p = platformRowForSession(s);
830
  if (!p || !s.has_metrics) continue;
831
- seen[`${s.task_key}|${s.version_key}|${s.size}|${p.key}`] = true;
 
 
 
 
 
 
832
  }
833
  return seen;
834
  }
@@ -868,8 +885,9 @@ function renderCoverageTable(taskKey, tableId, seen) {
868
  for (const v of versions) {
869
  for (const sz of COVERAGE_SIZES) {
870
  const td = el("td");
871
- if (seen[`${taskKey}|${v}|${sz}|${p.key}`]) {
872
- td.innerHTML = '<span class="cell cell-val">✓</span>';
 
873
  } else {
874
  td.innerHTML = '<span class="cell cell-none">—</span>';
875
  }
@@ -971,23 +989,27 @@ function leaderboardEntries(platformKey, metricKey) {
971
  }));
972
  }
973
 
974
- // Cross-platform throughput board: ranks platforms by their best measured
975
- // pipelined FPS (accuracy intentionally not ranked here).
 
976
  function renderPlatformThroughput() {
977
  const host = document.getElementById("platform-fps-body");
978
  if (!host) return;
979
  host.innerHTML = "";
980
- const rows = [];
981
- for (const p of PLATFORM_ROWS) {
982
- let top = null;
983
- for (const s of (STUDIO?.sessions ?? [])) {
984
- const pr = platformRowForSession(s);
985
- if (!pr || pr.key !== p.key || s.fps_median == null) continue;
986
- if (!top || s.fps_median > top.fps_median) top = s;
 
 
987
  }
988
- if (top) rows.push({ p, s: top });
989
  }
990
- rows.sort((a, b) => b.s.fps_median - a.s.fps_median);
 
 
991
  for (let i = 0; i < rows.length; i++) {
992
  const { p, s } = rows[i];
993
  const tr = el("tr");
@@ -1005,7 +1027,8 @@ function renderPlatformThroughput() {
1005
  function renderLeaderboard() {
1006
  const grid = document.getElementById("leaderboard-grid");
1007
  grid.innerHTML = "";
1008
- const platforms = PLATFORM_ROWS.filter(p => lbGroup === "all" || p.group === lbGroup);
 
1009
  const metricLabel = lbMetric === "fps"
1010
  ? "Pipelined throughput FPS (higher is better)"
1011
  : "mAP@0.5-0.95 (higher is better)";
@@ -1120,7 +1143,7 @@ function chartDetFps() {
1120
  const datasets = PLATFORM_ROWS.filter(p => byPlatform[p.key]).map(p => {
1121
  const pts = byPlatform[p.key];
1122
  return {
1123
- label: p.label,
1124
  data: pts.map(s => ({
1125
  x: s.fps_median,
1126
  y: s.det_map_50_95,
@@ -1309,13 +1332,6 @@ function totalMs(s) {
1309
  return v > 0 ? v : null;
1310
  }
1311
 
1312
- function studioSessionLabel(s) {
1313
- const sz = s.size ? s.size.toUpperCase() : "?";
1314
- const tk = s.task_key === "det" ? "Det" : "Seg";
1315
- const tgt = s.target_label || s.target_raw || s.format_key || "?";
1316
- return `${s.version_key?.toUpperCase()}${sz}-${tk} · ${tgt}`;
1317
- }
1318
-
1319
  function renderStudio() {
1320
  const table = document.getElementById("studio-table");
1321
  const empty = document.getElementById("studio-empty");
@@ -1329,27 +1345,93 @@ function renderStudio() {
1329
  empty.style.display = "none";
1330
  table.style.display = "";
1331
  const okCount = STUDIO.counts?.sessions_ok ?? STUDIO.counts?.ok ?? STUDIO.sessions.length;
1332
- source.innerHTML = `updated <code>${STUDIO.generated_at}</code> · ${okCount} public sessions · every row links to <a href="https://edgefirst.studio" target="_blank" rel="noopener" style="color:var(--teal-text)">EdgeFirst Studio</a>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1333
 
1334
- const body = document.getElementById("studio-body");
1335
- body.innerHTML = "";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1336
 
1337
- // Sort: detection first, then by version, size, target_key/raw, precision
1338
- const sizeOrder = ["n", "s", "m", "l", "x"];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1339
  const targetOrder = ["onnx", "tflite", "imx93", "imx95", "imx8mp", "ara240", "hailo8l", "hailo8", "hailo10", "engine"];
1340
- const sessions = STUDIO.sessions.slice().sort((a, b) => {
1341
  if (a.task_key !== b.task_key) return a.task_key.localeCompare(b.task_key);
1342
  const ai = VERSION_ORDER.indexOf(a.version_key);
1343
  const bi = VERSION_ORDER.indexOf(b.version_key);
1344
  if (ai !== bi) return ai - bi;
1345
- const si = sizeOrder.indexOf(a.size), sj = sizeOrder.indexOf(b.size);
1346
  if (si !== sj) return si - sj;
1347
  const ti = targetOrder.indexOf(a.target_key) === -1 ? 99 : targetOrder.indexOf(a.target_key);
1348
  const tj = targetOrder.indexOf(b.target_key) === -1 ? 99 : targetOrder.indexOf(b.target_key);
1349
  if (ti !== tj) return ti - tj;
1350
  return (a.precision || "").localeCompare(b.precision || "");
1351
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1352
 
 
1353
  for (const s of sessions) {
1354
  const total = totalMs(s);
1355
  const tr = el("tr");
@@ -1377,13 +1459,13 @@ function renderStudio() {
1377
  }
1378
  }
1379
 
1380
- // On-target timingPareto chart using Studio inference timing if available
 
1381
  function renderTiming() {
1382
  const host = document.getElementById("timing-host");
1383
  if (!host) return;
1384
  const usable = (STUDIO?.sessions ?? []).filter(s =>
1385
- typeof s.avg_inference_ms === "number" && s.avg_inference_ms > 0 &&
1386
- typeof s.det_map_50_95 === "number"
1387
  );
1388
  if (usable.length === 0) {
1389
  // leave the default placeholder copy
@@ -1391,29 +1473,38 @@ function renderTiming() {
1391
  }
1392
 
1393
  host.innerHTML = `
1394
- <div class="chart-host" style="height:360px"><canvas id="chart-timing"></canvas></div>
1395
  <p style="font-family:var(--serif);color:var(--text-muted);font-size:0.82rem;margin-top:0.5rem">
1396
- Each point is a public Studio validation session · X = average inference time (ms) · Y = mAP@0.5-0.95 ·
1397
- colored by model family. Timing is measured on the deployment hardware by the
1398
- <a href="https://doc.edgefirst.ai/latest/profiler/" target="_blank" rel="noopener" style="color:var(--teal-text)">EdgeFirst Profiler</a>.
 
1399
  </p>
1400
  `;
1401
 
1402
- const byVersion = {};
1403
  for (const s of usable) {
1404
- (byVersion[s.version_key] ||= []).push({
1405
- x: s.avg_inference_ms,
1406
- y: s.det_map_50_95,
1407
- label: studioSessionLabel(s),
1408
- });
1409
  }
1410
- const datasets = Object.entries(byVersion).map(([v, pts]) => ({
1411
- label: v.toUpperCase(),
1412
- data: pts,
1413
- backgroundColor: COLORS[v] ?? "#888",
1414
- borderColor: COLORS[v] ?? "#888",
1415
- pointRadius: 7, pointHoverRadius: 10,
1416
- }));
 
 
 
 
 
 
 
 
 
 
1417
 
1418
  destroyChart("timing");
1419
  charts.timing = new Chart(document.getElementById("chart-timing"), {
@@ -1425,13 +1516,15 @@ function renderTiming() {
1425
  legend: { display: true, position: "bottom" },
1426
  tooltip: {
1427
  callbacks: {
1428
- label: (ctx) => `${ctx.raw.label}: ${ctx.parsed.y}% mAP @ ${ctx.parsed.x} ms`,
1429
  },
1430
  },
1431
  },
1432
  scales: {
1433
- x: { title: { display: true, text: "Avg inference time (ms, lower = faster)" }, beginAtZero: true,
1434
- ticks: { font: { family: "monospace", size: 10 } } },
 
 
1435
  y: { title: { display: true, text: "mAP@0.5-0.95 (%)" }, beginAtZero: false,
1436
  ticks: { font: { family: "monospace", size: 10 } } },
1437
  },
 
145
  border-bottom: 2px solid var(--border-strong);
146
  text-transform: uppercase; letter-spacing: 0.03em; font-size: 0.72rem;
147
  }
148
+ table.dt th[data-sort] { cursor: pointer; user-select: none; white-space: nowrap; }
149
+ table.dt th[data-sort]:hover { color: var(--navy); }
150
+ table.dt th .sort-ind { color: var(--gold); margin-left: 0.2rem; }
151
  table.dt td {
152
  padding: 0.5rem 0.65rem; border-bottom: 1px solid var(--border);
153
  vertical-align: middle;
 
185
  font-family: var(--mono); font-weight: 600; font-size: 0.72rem;
186
  }
187
  .cell-val { color: var(--good); background: var(--good-bg); padding: 0.15rem 0.35rem; border-radius: 3px; }
188
+ a.cell-val { text-decoration: none; cursor: pointer; }
189
+ a.cell-val:hover { background: var(--good); color: #fff; }
190
  .cell-plan { color: var(--warn); background: var(--warn-bg); padding: 0.15rem 0.35rem; border-radius: 3px; }
191
  .cell-none { color: var(--text-muted); opacity: 0.5; }
192
  .cell-target { color: var(--text-muted); background: #f4f4f4; padding: 0.15rem 0.35rem; border-radius: 3px; }
 
457
  </div>
458
  </div>
459
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  <div class="section">
461
  <div class="section-header"><h2>Nano Segmentation — Mask mAP</h2>
462
  <span class="section-note">YOLOv8 / YOLO11 / YOLO26, COCO val2017</span>
 
474
  </div>
475
 
476
  <div class="section">
477
+ <div class="section-header"><h2>Accuracy vs On-target Throughput</h2>
478
+ <span class="section-note">COCO val2017 · pipelined FPS measured on each platform by the <a href="https://doc.edgefirst.ai/latest/profiler/" target="_blank" rel="noopener" style="color:var(--teal-text)">EdgeFirst Profiler</a></span>
479
+ </div>
480
+ <div class="chart-host" style="height:420px"><canvas id="chart-det-fps"></canvas></div>
481
+ <p style="font-family:var(--serif);color:var(--text-muted);font-size:0.82rem;margin-top:0.5rem">
482
+ Each point is a public validation session: color = platform, shape = model family
483
+ (● YOLOv5 · ■ YOLOv8 · ▲ YOLO11 · ◆ YOLO26). X axis is sustained pipelined throughput on a log
484
+ scale — the full image load → decode → preprocess → inference → postprocess pipeline, not bare
485
+ inference time. ⚠ flagged sessions are shown at their measured accuracy.
486
+ </p>
487
+ </div>
488
+
489
+ <div class="section">
490
+ <div class="section-header"><h2>Accuracy vs On-target Latency</h2>
491
+ <span class="section-note">End-to-end per-image latency measured by the <a href="https://doc.edgefirst.ai/latest/profiler/" target="_blank" rel="noopener" style="color:var(--teal-text)">EdgeFirst Profiler</a> on deployment hardware</span>
492
  </div>
493
  <div id="timing-host">
494
  <p style="font-family:var(--serif);color:var(--text-muted);font-size:0.92rem">
495
+ On-target latency results are being collected across the platform matrix.
496
  See the <a href="https://doc.edgefirst.ai/latest/profiler/" target="_blank" rel="noopener" style="color:var(--teal-text)">EdgeFirst Profiler documentation</a>
497
  for how profiling and validation are performed.
498
  </p>
 
514
  </div>
515
  <table class="dt" id="studio-table" style="display:none">
516
  <thead><tr>
517
+ <th data-sort="session">Session</th>
518
+ <th data-sort="family">Family · Task</th>
519
+ <th data-sort="target">Target</th>
520
+ <th data-sort="precision">Precision</th>
521
+ <th data-sort="det50">Det mAP<sub>50</sub></th>
522
+ <th data-sort="det5095">Det mAP<sub>50-95</sub></th>
523
+ <th data-sort="mask">Mask mAP<sub>50-95</sub></th>
524
+ <th data-sort="inf">Inf (ms)</th>
525
+ <th data-sort="e2e">E2E (ms)</th>
526
+ <th data-sort="fps">FPS</th>
527
  <th>Studio</th>
528
  </tr></thead>
529
  <tbody id="studio-body"></tbody>
 
550
  <div class="coverage-wrap"><table class="matrix" id="coverage-seg"></table></div>
551
  </div>
552
  <div class="legend">
553
+ <span><span class="swatch" style="background:var(--good-bg);border:1px solid var(--good)"></span>Validated — click to open the proving session on EdgeFirst Studio</span>
554
  <span><span class="swatch" style="background:transparent;border:1px solid var(--border)"></span>Validation pending</span>
555
  </div>
556
  </section>
 
573
  <section class="tab-panel" id="tab-leaderboard">
574
  <div class="section">
575
  <div class="section-header"><h2>Platform Throughput Leaderboard</h2>
576
+ <span class="section-note">Top 20 fastest validated model × platform results · full image load → decode → preprocess → inference → postprocess pipeline</span>
577
  </div>
578
  <table class="dt">
579
  <thead><tr>
 
639
  // Platforms as rows: hosts (reference) first, then embedded targets, then Jetson.
640
  // `match` is tested (in order) against the Studio target/footer string to map a
641
  // validation session onto a platform row.
642
+ // The three CoreML compute units are separate accelerators on one host —
643
+ // each gets its own coverage row, but only the ANE (the fastest) is ranked
644
+ // in leaderboards; CPU/GPU numbers remain in the validation sessions table.
645
  const PLATFORM_ROWS = [
646
  { key: "linux-x86", label: "Linux x86_64", detail: "ONNX Runtime + CUDA", group: "reference", color: "#6C757D", match: /linux|x86|cuda/ },
647
+ { key: "m2max-ane", label: "Apple M2 Max", detail: "ONNX + CoreML ANE", group: "reference", color: "#4B0082", match: /coreml-ane/ },
648
+ { key: "m2max-gpu", label: "Apple M2 Max", detail: "ONNX + CoreML GPU", group: "reference", color: "#7A4FB3", match: /coreml-gpu/, leaderboard: false },
649
+ { key: "m2max-cpu", label: "Apple M2 Max", detail: "ONNX + CoreML CPU", group: "reference", color: "#A98FD0", match: /coreml-cpu|macos|coreml/, leaderboard: false },
650
  { key: "imx8mp", label: "NXP i.MX 8M Plus", detail: "VSI NPU · TFLite INT8", group: "embedded", color: "#E8B820", match: /imx8mp/ },
651
  { key: "imx93", label: "NXP i.MX 93", detail: "Ethos-U65 · VELA TFLite", group: "embedded", color: "#8B7DC4", match: /imx93/ },
652
  { key: "imx95", label: "NXP i.MX 95", detail: "eIQ Neutron · TFLite INT8", group: "embedded", color: "#1FA0A8", match: /imx95/ },
 
832
  const SIZE_NAMES = { n: "Nano", s: "Small", m: "Medium" };
833
 
834
  // Build a lookup of validated (task, version, size, platform) cells from the
835
+ // embedded Studio sessions. Each cell keeps the best proving session (smart
836
+ // decoder preferred, then highest mAP) so the ✓ can link to it.
837
  function coverageLookup() {
838
  const seen = {};
839
  for (const s of (STUDIO?.sessions ?? [])) {
840
  const p = platformRowForSession(s);
841
  if (!p || !s.has_metrics) continue;
842
+ const key = `${s.task_key}|${s.version_key}|${s.size}|${p.key}`;
843
+ const metric = s.det_map_50_95 ?? s.mask_map_50_95 ?? 0;
844
+ const isSmart = isSmartSession(s);
845
+ const cur = seen[key];
846
+ if (!cur || (isSmart && !cur.isSmart) || (isSmart === cur.isSmart && metric > cur.metric)) {
847
+ seen[key] = { s, metric, isSmart };
848
+ }
849
  }
850
  return seen;
851
  }
 
885
  for (const v of versions) {
886
  for (const sz of COVERAGE_SIZES) {
887
  const td = el("td");
888
+ const hit = seen[`${taskKey}|${v}|${sz}|${p.key}`];
889
+ if (hit) {
890
+ td.innerHTML = `<a class="cell cell-val" href="${hit.s.url}" target="_blank" rel="noopener" title="Open validation session ${hit.s.session_id} on EdgeFirst Studio">✓</a>`;
891
  } else {
892
  td.innerHTML = '<span class="cell cell-none">—</span>';
893
  }
 
989
  }));
990
  }
991
 
992
+ // Cross-platform throughput board: the top 20 fastest validated
993
+ // model × platform results by pipelined FPS (accuracy intentionally not
994
+ // ranked here; smart decoder preferred over logical duplicates).
995
  function renderPlatformThroughput() {
996
  const host = document.getElementById("platform-fps-body");
997
  if (!host) return;
998
  host.innerHTML = "";
999
+ const best = {};
1000
+ for (const s of (STUDIO?.sessions ?? [])) {
1001
+ const p = platformRowForSession(s);
1002
+ if (!p || p.leaderboard === false || s.fps_median == null) continue;
1003
+ const key = `${p.key}|${s.task_key}|${s.version_key}|${s.size}`;
1004
+ const isSmart = isSmartSession(s);
1005
+ const cur = best[key];
1006
+ if (!cur || (isSmart && !cur.isSmart) || (isSmart === cur.isSmart && s.fps_median > cur.s.fps_median)) {
1007
+ best[key] = { p, s, isSmart };
1008
  }
 
1009
  }
1010
+ const rows = Object.values(best)
1011
+ .sort((a, b) => b.s.fps_median - a.s.fps_median)
1012
+ .slice(0, 20);
1013
  for (let i = 0; i < rows.length; i++) {
1014
  const { p, s } = rows[i];
1015
  const tr = el("tr");
 
1027
  function renderLeaderboard() {
1028
  const grid = document.getElementById("leaderboard-grid");
1029
  grid.innerHTML = "";
1030
+ const platforms = PLATFORM_ROWS.filter(p =>
1031
+ p.leaderboard !== false && (lbGroup === "all" || p.group === lbGroup));
1032
  const metricLabel = lbMetric === "fps"
1033
  ? "Pipelined throughput FPS (higher is better)"
1034
  : "mAP@0.5-0.95 (higher is better)";
 
1143
  const datasets = PLATFORM_ROWS.filter(p => byPlatform[p.key]).map(p => {
1144
  const pts = byPlatform[p.key];
1145
  return {
1146
+ label: `${p.label} ${p.detail.includes("CoreML") ? p.detail.replace("ONNX + ", "") : ""}`.trim(),
1147
  data: pts.map(s => ({
1148
  x: s.fps_median,
1149
  y: s.det_map_50_95,
 
1332
  return v > 0 ? v : null;
1333
  }
1334
 
 
 
 
 
 
 
 
1335
  function renderStudio() {
1336
  const table = document.getElementById("studio-table");
1337
  const empty = document.getElementById("studio-empty");
 
1345
  empty.style.display = "none";
1346
  table.style.display = "";
1347
  const okCount = STUDIO.counts?.sessions_ok ?? STUDIO.counts?.ok ?? STUDIO.sessions.length;
1348
+ source.innerHTML = `updated <code>${STUDIO.generated_at}</code> · ${okCount} public sessions · every row links to <a href="https://edgefirst.studio" target="_blank" rel="noopener" style="color:var(--teal-text)">EdgeFirst Studio</a> · click a column header to sort`;
1349
+
1350
+ // Attach sort handlers once
1351
+ table.querySelectorAll("th[data-sort]").forEach(th => {
1352
+ if (th.dataset.bound) return;
1353
+ th.dataset.bound = "1";
1354
+ th.addEventListener("click", () => {
1355
+ const key = th.dataset.sort;
1356
+ if (studioSortKey === key) {
1357
+ studioSortDir = -studioSortDir;
1358
+ } else {
1359
+ studioSortKey = key;
1360
+ // Metrics default to descending (best first), text to ascending.
1361
+ studioSortDir = ["session", "family", "target", "precision"].includes(key) ? 1 : -1;
1362
+ }
1363
+ renderStudioBody();
1364
+ });
1365
+ });
1366
 
1367
+ renderStudioBody();
1368
+ }
1369
+
1370
+ const SIZE_ORDER = ["n", "s", "m", "l", "x"];
1371
+
1372
+ const STUDIO_SORT_ACCESSORS = {
1373
+ session: s => s.session_name || s.session_id || "",
1374
+ family: s => `${String(VERSION_ORDER.indexOf(s.version_key)).padStart(2, "0")}|${s.task_key}|${SIZE_ORDER.indexOf(s.size)}`,
1375
+ target: s => s.target_label || s.target_raw || "",
1376
+ precision: s => s.precision || "",
1377
+ det50: s => s.det_map_50,
1378
+ det5095: s => s.det_map_50_95,
1379
+ mask: s => s.mask_map_50_95,
1380
+ inf: s => s.avg_inference_ms,
1381
+ e2e: s => totalMs(s),
1382
+ fps: s => s.fps_median,
1383
+ };
1384
 
1385
+ let studioSortKey = null;
1386
+ let studioSortDir = 1;
1387
+
1388
+ function sortedStudioSessions() {
1389
+ const sessions = STUDIO.sessions.slice();
1390
+ if (studioSortKey && STUDIO_SORT_ACCESSORS[studioSortKey]) {
1391
+ const acc = STUDIO_SORT_ACCESSORS[studioSortKey];
1392
+ sessions.sort((a, b) => {
1393
+ const va = acc(a), vb = acc(b);
1394
+ if (va == null && vb == null) return 0;
1395
+ if (va == null) return 1; // nulls always last
1396
+ if (vb == null) return -1;
1397
+ const c = (typeof va === "number" && typeof vb === "number")
1398
+ ? va - vb
1399
+ : String(va).localeCompare(String(vb));
1400
+ return c * studioSortDir;
1401
+ });
1402
+ return sessions;
1403
+ }
1404
+ // Default: detection first, then by version, size, target, precision
1405
  const targetOrder = ["onnx", "tflite", "imx93", "imx95", "imx8mp", "ara240", "hailo8l", "hailo8", "hailo10", "engine"];
1406
+ return sessions.sort((a, b) => {
1407
  if (a.task_key !== b.task_key) return a.task_key.localeCompare(b.task_key);
1408
  const ai = VERSION_ORDER.indexOf(a.version_key);
1409
  const bi = VERSION_ORDER.indexOf(b.version_key);
1410
  if (ai !== bi) return ai - bi;
1411
+ const si = SIZE_ORDER.indexOf(a.size), sj = SIZE_ORDER.indexOf(b.size);
1412
  if (si !== sj) return si - sj;
1413
  const ti = targetOrder.indexOf(a.target_key) === -1 ? 99 : targetOrder.indexOf(a.target_key);
1414
  const tj = targetOrder.indexOf(b.target_key) === -1 ? 99 : targetOrder.indexOf(b.target_key);
1415
  if (ti !== tj) return ti - tj;
1416
  return (a.precision || "").localeCompare(b.precision || "");
1417
  });
1418
+ }
1419
+
1420
+ function renderStudioBody() {
1421
+ const table = document.getElementById("studio-table");
1422
+ const body = document.getElementById("studio-body");
1423
+ body.innerHTML = "";
1424
+
1425
+ // Header sort indicators
1426
+ table.querySelectorAll("th[data-sort]").forEach(th => {
1427
+ th.querySelector(".sort-ind")?.remove();
1428
+ if (th.dataset.sort === studioSortKey) {
1429
+ th.insertAdjacentHTML("beforeend",
1430
+ `<span class="sort-ind">${studioSortDir === 1 ? "▲" : "▼"}</span>`);
1431
+ }
1432
+ });
1433
 
1434
+ const sessions = sortedStudioSessions();
1435
  for (const s of sessions) {
1436
  const total = totalMs(s);
1437
  const tr = el("tr");
 
1459
  }
1460
  }
1461
 
1462
+ // Accuracy vs on-target latencyend-to-end per-image wall time, encoded
1463
+ // like the throughput chart: color = platform, shape = model family.
1464
  function renderTiming() {
1465
  const host = document.getElementById("timing-host");
1466
  if (!host) return;
1467
  const usable = (STUDIO?.sessions ?? []).filter(s =>
1468
+ s.task_key === "det" && totalMs(s) != null && typeof s.det_map_50_95 === "number"
 
1469
  );
1470
  if (usable.length === 0) {
1471
  // leave the default placeholder copy
 
1473
  }
1474
 
1475
  host.innerHTML = `
1476
+ <div class="chart-host" style="height:420px"><canvas id="chart-timing"></canvas></div>
1477
  <p style="font-family:var(--serif);color:var(--text-muted);font-size:0.82rem;margin-top:0.5rem">
1478
+ Each point is a public validation session: color = platform, shape = model family
1479
+ (● YOLOv5 · ■ YOLOv8 · ▲ YOLO11 · ◆ YOLO26). X axis is end-to-end per-image latency on a log
1480
+ scale the sequential image load → decode → preprocess → inference → postprocess wall time, not
1481
+ bare inference. Throughput exceeds 1000 ÷ latency because stages pipeline across frames.
1482
  </p>
1483
  `;
1484
 
1485
+ const byPlatform = {};
1486
  for (const s of usable) {
1487
+ const p = platformRowForSession(s);
1488
+ if (!p) continue;
1489
+ (byPlatform[p.key] ||= []).push(s);
 
 
1490
  }
1491
+
1492
+ const datasets = PLATFORM_ROWS.filter(p => byPlatform[p.key]).map(p => {
1493
+ const pts = byPlatform[p.key];
1494
+ return {
1495
+ label: `${p.label} ${p.detail.includes("CoreML") ? p.detail.replace("ONNX + ", "") : ""}`.trim(),
1496
+ data: pts.map(s => ({
1497
+ x: totalMs(s),
1498
+ y: s.det_map_50_95,
1499
+ name: sessionModelName(s),
1500
+ flagged: isBelowExpectations(s),
1501
+ })),
1502
+ backgroundColor: p.color,
1503
+ borderColor: p.color,
1504
+ pointStyle: pts.map(s => FAMILY_SHAPES[s.version_key] ?? "circle"),
1505
+ pointRadius: 7, pointHoverRadius: 10,
1506
+ };
1507
+ });
1508
 
1509
  destroyChart("timing");
1510
  charts.timing = new Chart(document.getElementById("chart-timing"), {
 
1516
  legend: { display: true, position: "bottom" },
1517
  tooltip: {
1518
  callbacks: {
1519
+ label: (ctx) => `${ctx.raw.name} on ${ctx.dataset.label}: ${ctx.parsed.y.toFixed(1)} mAP @ ${ctx.parsed.x.toFixed(1)} ms E2E${ctx.raw.flagged ? " ⚠" : ""}`,
1520
  },
1521
  },
1522
  },
1523
  scales: {
1524
+ x: { type: "logarithmic",
1525
+ title: { display: true, text: "End-to-end latency (ms, log scale lower is better)" },
1526
+ ticks: { font: { family: "monospace", size: 10 },
1527
+ callback: (v) => ([1,2,5,10,20,50,100,200,500,1000,2000].includes(v) ? String(v) : null) } },
1528
  y: { title: { display: true, text: "mAP@0.5-0.95 (%)" }, beginAtZero: false,
1529
  ticks: { font: { family: "monospace", size: 10 } } },
1530
  },