@camstack/addon-benchmark 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon-benchmark.css +1 -0
- package/dist/index.js +6 -743
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -723
- package/dist/index.mjs.map +1 -1
- package/dist/page.mjs +34094 -0
- package/package.json +43 -12
- package/dist/index.d.mts +0 -222
- package/dist/index.d.ts +0 -222
package/dist/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,317 +15,15 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// src/index.ts
|
|
31
21
|
var src_exports = {};
|
|
32
22
|
__export(src_exports, {
|
|
33
|
-
BenchmarkAddon: () => BenchmarkAddon
|
|
34
|
-
BenchmarkAgent: () => BenchmarkAgent,
|
|
35
|
-
BenchmarkHub: () => BenchmarkHub,
|
|
36
|
-
BenchmarkRunner: () => BenchmarkRunner,
|
|
37
|
-
annotateImage: () => annotateImage,
|
|
38
|
-
collectSystemInfo: () => collectSystemInfo,
|
|
39
|
-
computeAccuracy: () => computeAccuracy,
|
|
40
|
-
computeFps: () => computeFps,
|
|
41
|
-
computeLatencyStats: () => computeLatencyStats,
|
|
42
|
-
iou: () => iou
|
|
23
|
+
BenchmarkAddon: () => BenchmarkAddon
|
|
43
24
|
});
|
|
44
25
|
module.exports = __toCommonJS(src_exports);
|
|
45
26
|
|
|
46
|
-
// src/runner/stats.ts
|
|
47
|
-
function percentile(sorted, p) {
|
|
48
|
-
if (sorted.length === 0) return 0;
|
|
49
|
-
const idx = p * (sorted.length - 1);
|
|
50
|
-
const lo = Math.floor(idx);
|
|
51
|
-
const hi = Math.ceil(idx);
|
|
52
|
-
const loVal = sorted[lo] ?? 0;
|
|
53
|
-
const hiVal = sorted[hi] ?? 0;
|
|
54
|
-
return loVal + (hiVal - loVal) * (idx - lo);
|
|
55
|
-
}
|
|
56
|
-
function computeLatencyStats(timingsMs) {
|
|
57
|
-
if (timingsMs.length === 0) {
|
|
58
|
-
return { mean: 0, median: 0, p95: 0, p99: 0, min: 0, max: 0 };
|
|
59
|
-
}
|
|
60
|
-
const sorted = [...timingsMs].sort((a, b) => a - b);
|
|
61
|
-
const sum = sorted.reduce((acc, v) => acc + v, 0);
|
|
62
|
-
const mean = sum / sorted.length;
|
|
63
|
-
return {
|
|
64
|
-
mean,
|
|
65
|
-
median: percentile(sorted, 0.5),
|
|
66
|
-
p95: percentile(sorted, 0.95),
|
|
67
|
-
p99: percentile(sorted, 0.99),
|
|
68
|
-
min: sorted[0] ?? 0,
|
|
69
|
-
max: sorted[sorted.length - 1] ?? 0
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
function computeFps(timingsMs) {
|
|
73
|
-
if (timingsMs.length === 0) return 0;
|
|
74
|
-
const stats = computeLatencyStats(timingsMs);
|
|
75
|
-
if (stats.mean === 0) return 0;
|
|
76
|
-
return 1e3 / stats.mean;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// src/runner/accuracy.ts
|
|
80
|
-
function iou(pred, gt) {
|
|
81
|
-
const predX2 = pred.x + pred.w;
|
|
82
|
-
const predY2 = pred.y + pred.h;
|
|
83
|
-
const gtX2 = gt.x + gt.w;
|
|
84
|
-
const gtY2 = gt.y + gt.h;
|
|
85
|
-
const interX1 = Math.max(pred.x, gt.x);
|
|
86
|
-
const interY1 = Math.max(pred.y, gt.y);
|
|
87
|
-
const interX2 = Math.min(predX2, gtX2);
|
|
88
|
-
const interY2 = Math.min(predY2, gtY2);
|
|
89
|
-
const interW = Math.max(0, interX2 - interX1);
|
|
90
|
-
const interH = Math.max(0, interY2 - interY1);
|
|
91
|
-
const interArea = interW * interH;
|
|
92
|
-
if (interArea === 0) return 0;
|
|
93
|
-
const predArea = pred.w * pred.h;
|
|
94
|
-
const gtArea = gt.w * gt.h;
|
|
95
|
-
const unionArea = predArea + gtArea - interArea;
|
|
96
|
-
if (unionArea <= 0) return 0;
|
|
97
|
-
return interArea / unionArea;
|
|
98
|
-
}
|
|
99
|
-
function areaUnderPRCurve(precision, recall) {
|
|
100
|
-
if (precision.length < 2) return precision[0] ?? 0;
|
|
101
|
-
let area = 0;
|
|
102
|
-
for (let i = 1; i < recall.length; i++) {
|
|
103
|
-
const dr = (recall[i] ?? 0) - (recall[i - 1] ?? 0);
|
|
104
|
-
const avgP = ((precision[i] ?? 0) + (precision[i - 1] ?? 0)) / 2;
|
|
105
|
-
area += dr * avgP;
|
|
106
|
-
}
|
|
107
|
-
return Math.abs(area);
|
|
108
|
-
}
|
|
109
|
-
function computeClassAP(predictions, groundTruths, iouThreshold) {
|
|
110
|
-
const nGt = groundTruths.length;
|
|
111
|
-
if (nGt === 0) return 0;
|
|
112
|
-
if (predictions.length === 0) return 0;
|
|
113
|
-
const matched = /* @__PURE__ */ new Set();
|
|
114
|
-
const tp = [];
|
|
115
|
-
const fp = [];
|
|
116
|
-
for (const pred of predictions) {
|
|
117
|
-
let bestIou = -1;
|
|
118
|
-
let bestGtIdx = -1;
|
|
119
|
-
for (let i = 0; i < groundTruths.length; i++) {
|
|
120
|
-
if (matched.has(i)) continue;
|
|
121
|
-
const gt = groundTruths[i];
|
|
122
|
-
if (!gt) continue;
|
|
123
|
-
const score = iou(pred.bbox, gt.bbox);
|
|
124
|
-
if (score > bestIou) {
|
|
125
|
-
bestIou = score;
|
|
126
|
-
bestGtIdx = i;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
if (bestIou >= iouThreshold && bestGtIdx >= 0) {
|
|
130
|
-
matched.add(bestGtIdx);
|
|
131
|
-
tp.push(1);
|
|
132
|
-
fp.push(0);
|
|
133
|
-
} else {
|
|
134
|
-
tp.push(0);
|
|
135
|
-
fp.push(1);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
const cumTp = [];
|
|
139
|
-
const cumFp = [];
|
|
140
|
-
let sumTp = 0;
|
|
141
|
-
let sumFp = 0;
|
|
142
|
-
for (let i = 0; i < tp.length; i++) {
|
|
143
|
-
sumTp += tp[i] ?? 0;
|
|
144
|
-
sumFp += fp[i] ?? 0;
|
|
145
|
-
cumTp.push(sumTp);
|
|
146
|
-
cumFp.push(sumFp);
|
|
147
|
-
}
|
|
148
|
-
const precisions = [];
|
|
149
|
-
const recalls = [];
|
|
150
|
-
precisions.push(1);
|
|
151
|
-
recalls.push(0);
|
|
152
|
-
for (let i = 0; i < cumTp.length; i++) {
|
|
153
|
-
const cTp = cumTp[i] ?? 0;
|
|
154
|
-
const cFp = cumFp[i] ?? 0;
|
|
155
|
-
const denom = cTp + cFp;
|
|
156
|
-
precisions.push(denom > 0 ? cTp / denom : 0);
|
|
157
|
-
recalls.push(nGt > 0 ? cTp / nGt : 0);
|
|
158
|
-
}
|
|
159
|
-
return areaUnderPRCurve(precisions, recalls);
|
|
160
|
-
}
|
|
161
|
-
function computeAccuracy(predictions, groundTruth, iouThreshold = 0.5) {
|
|
162
|
-
if (groundTruth.length === 0) {
|
|
163
|
-
return { mAP50: void 0, precision: void 0, recall: void 0 };
|
|
164
|
-
}
|
|
165
|
-
const classes = [...new Set(groundTruth.map((gt) => gt.class))];
|
|
166
|
-
const aps = [];
|
|
167
|
-
let totalTp = 0;
|
|
168
|
-
let totalFp = 0;
|
|
169
|
-
let totalGt = 0;
|
|
170
|
-
for (const cls of classes) {
|
|
171
|
-
const classPredictions = predictions.filter((p) => p.class === cls).sort((a, b) => b.score - a.score);
|
|
172
|
-
const classGt = groundTruth.filter((gt) => gt.class === cls);
|
|
173
|
-
totalGt += classGt.length;
|
|
174
|
-
const ap = computeClassAP(classPredictions, classGt, iouThreshold);
|
|
175
|
-
aps.push(ap);
|
|
176
|
-
const matched = /* @__PURE__ */ new Set();
|
|
177
|
-
for (const pred of classPredictions) {
|
|
178
|
-
let bestIou = -1;
|
|
179
|
-
let bestGtIdx = -1;
|
|
180
|
-
for (let i = 0; i < classGt.length; i++) {
|
|
181
|
-
if (matched.has(i)) continue;
|
|
182
|
-
const gt = classGt[i];
|
|
183
|
-
if (!gt) continue;
|
|
184
|
-
const score = iou(pred.bbox, gt.bbox);
|
|
185
|
-
if (score > bestIou) {
|
|
186
|
-
bestIou = score;
|
|
187
|
-
bestGtIdx = i;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
if (bestIou >= iouThreshold && bestGtIdx >= 0) {
|
|
191
|
-
matched.add(bestGtIdx);
|
|
192
|
-
totalTp++;
|
|
193
|
-
} else {
|
|
194
|
-
totalFp++;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
const mAP50 = aps.length > 0 ? aps.reduce((sum, ap) => sum + ap, 0) / aps.length : void 0;
|
|
199
|
-
const precision = totalTp + totalFp > 0 ? totalTp / (totalTp + totalFp) : void 0;
|
|
200
|
-
const recall = totalGt > 0 ? totalTp / totalGt : void 0;
|
|
201
|
-
return { mAP50, precision, recall };
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// src/runner/benchmark-runner.ts
|
|
205
|
-
function measureResources() {
|
|
206
|
-
const memUsage = process.memoryUsage();
|
|
207
|
-
return {
|
|
208
|
-
peakMemoryMB: Math.round(memUsage.heapUsed / (1024 * 1024)),
|
|
209
|
-
avgCpuPercent: 0
|
|
210
|
-
// CPU profiling requires native tooling; best-effort zero
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
var BenchmarkRunner = class {
|
|
214
|
-
constructor(options) {
|
|
215
|
-
this.options = options;
|
|
216
|
-
}
|
|
217
|
-
/** Run inference on a single frame, picking a reference image round-robin if needed */
|
|
218
|
-
async runOnce(target, frameIdx = 0) {
|
|
219
|
-
const images = this.options.referenceImages;
|
|
220
|
-
if (images.length === 0) {
|
|
221
|
-
const syntheticFrame = {
|
|
222
|
-
data: Buffer.alloc(320 * 240 * 3),
|
|
223
|
-
format: "rgb",
|
|
224
|
-
width: 320,
|
|
225
|
-
height: 240,
|
|
226
|
-
timestamp: Date.now()
|
|
227
|
-
};
|
|
228
|
-
return this.options.runInference(syntheticFrame, target.config);
|
|
229
|
-
}
|
|
230
|
-
const idx = frameIdx % images.length;
|
|
231
|
-
const entry = images[idx];
|
|
232
|
-
if (!entry) {
|
|
233
|
-
throw new Error(`Reference image at index ${idx} is undefined`);
|
|
234
|
-
}
|
|
235
|
-
return this.options.runInference(entry.frame, target.config);
|
|
236
|
-
}
|
|
237
|
-
/** Measure accuracy across all reference images */
|
|
238
|
-
async measureAccuracy(target) {
|
|
239
|
-
const images = this.options.referenceImages;
|
|
240
|
-
if (images.length === 0) return void 0;
|
|
241
|
-
const allPredictions = [];
|
|
242
|
-
const allGroundTruth = [];
|
|
243
|
-
for (const entry of images) {
|
|
244
|
-
const output = await this.options.runInference(entry.frame, target.config);
|
|
245
|
-
allPredictions.push(...output.detections);
|
|
246
|
-
allGroundTruth.push(...entry.groundTruth.annotations);
|
|
247
|
-
}
|
|
248
|
-
return computeAccuracy(allPredictions, allGroundTruth);
|
|
249
|
-
}
|
|
250
|
-
/** Run benchmark for a single target configuration */
|
|
251
|
-
async runTarget(target, config) {
|
|
252
|
-
const warmup = config.warmup ?? 10;
|
|
253
|
-
const iterations = config.iterations ?? 100;
|
|
254
|
-
const timings = [];
|
|
255
|
-
for (let i = 0; i < warmup; i++) {
|
|
256
|
-
await this.runOnce(target, i);
|
|
257
|
-
}
|
|
258
|
-
for (let i = 0; i < iterations; i++) {
|
|
259
|
-
const start = performance.now();
|
|
260
|
-
await this.runOnce(target, i);
|
|
261
|
-
timings.push(performance.now() - start);
|
|
262
|
-
this.options.onProgress?.(i + 1, iterations, target.label);
|
|
263
|
-
}
|
|
264
|
-
const latency = computeLatencyStats(timings);
|
|
265
|
-
const fps = computeFps(timings);
|
|
266
|
-
const accuracy = await this.measureAccuracy(target);
|
|
267
|
-
const resources = measureResources();
|
|
268
|
-
return {
|
|
269
|
-
label: target.label,
|
|
270
|
-
addonId: target.addonId,
|
|
271
|
-
config: target.config,
|
|
272
|
-
latency,
|
|
273
|
-
fps,
|
|
274
|
-
accuracy,
|
|
275
|
-
resources
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
/** Run benchmark for all targets defined in the config */
|
|
279
|
-
async runAll(config) {
|
|
280
|
-
const results = [];
|
|
281
|
-
for (const target of config.targets) {
|
|
282
|
-
results.push(await this.runTarget(target, config));
|
|
283
|
-
}
|
|
284
|
-
return results;
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
// src/runner/system-info.ts
|
|
289
|
-
var import_node_os = __toESM(require("os"));
|
|
290
|
-
var import_node_child_process = require("child_process");
|
|
291
|
-
function detectGpuModel() {
|
|
292
|
-
try {
|
|
293
|
-
const output = (0, import_node_child_process.execSync)("nvidia-smi --query-gpu=name --format=csv,noheader", {
|
|
294
|
-
timeout: 3e3,
|
|
295
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
296
|
-
}).toString().trim();
|
|
297
|
-
if (output.length > 0) {
|
|
298
|
-
return output.split("\n")[0]?.trim();
|
|
299
|
-
}
|
|
300
|
-
} catch {
|
|
301
|
-
}
|
|
302
|
-
if (process.platform === "darwin") {
|
|
303
|
-
try {
|
|
304
|
-
const output = (0, import_node_child_process.execSync)(
|
|
305
|
-
"system_profiler SPDisplaysDataType 2>/dev/null | grep 'Chipset Model:' | head -1",
|
|
306
|
-
{ timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
|
|
307
|
-
).toString().trim();
|
|
308
|
-
const match = output.match(/Chipset Model:\s*(.+)/);
|
|
309
|
-
if (match?.[1]) {
|
|
310
|
-
return match[1].trim();
|
|
311
|
-
}
|
|
312
|
-
} catch {
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return void 0;
|
|
316
|
-
}
|
|
317
|
-
function collectSystemInfo() {
|
|
318
|
-
const cpus = import_node_os.default.cpus();
|
|
319
|
-
const firstCpu = cpus[0];
|
|
320
|
-
return {
|
|
321
|
-
os: `${import_node_os.default.type()} ${import_node_os.default.release()}`,
|
|
322
|
-
arch: import_node_os.default.arch(),
|
|
323
|
-
cpuModel: firstCpu?.model ?? "Unknown",
|
|
324
|
-
cpuCores: cpus.length,
|
|
325
|
-
totalMemoryMB: Math.round(import_node_os.default.totalmem() / (1024 * 1024)),
|
|
326
|
-
gpuModel: detectGpuModel(),
|
|
327
|
-
nodeVersion: process.version
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
|
|
331
27
|
// src/benchmark-addon.ts
|
|
332
28
|
var BenchmarkAddon = class {
|
|
333
29
|
id = "benchmark";
|
|
@@ -335,8 +31,8 @@ var BenchmarkAddon = class {
|
|
|
335
31
|
id: "benchmark",
|
|
336
32
|
name: "Benchmark",
|
|
337
33
|
version: "0.1.0",
|
|
338
|
-
|
|
339
|
-
|
|
34
|
+
packageName: "@camstack/addon-benchmark",
|
|
35
|
+
description: "Detection benchmark, image tester, and pipeline runner"
|
|
340
36
|
};
|
|
341
37
|
pages = [
|
|
342
38
|
{
|
|
@@ -344,18 +40,12 @@ var BenchmarkAddon = class {
|
|
|
344
40
|
label: "Benchmark",
|
|
345
41
|
icon: "gauge",
|
|
346
42
|
path: "/addon/benchmark",
|
|
347
|
-
bundle: "
|
|
348
|
-
element: "camstack-benchmark"
|
|
43
|
+
bundle: "page.mjs"
|
|
349
44
|
}
|
|
350
45
|
];
|
|
351
|
-
|
|
352
|
-
options = {};
|
|
353
|
-
async initialize(ctx, opts) {
|
|
354
|
-
this.context = ctx;
|
|
355
|
-
this.options = opts ?? {};
|
|
46
|
+
async initialize(_ctx) {
|
|
356
47
|
}
|
|
357
48
|
async shutdown() {
|
|
358
|
-
this.context = null;
|
|
359
49
|
}
|
|
360
50
|
getCapabilityProvider(name) {
|
|
361
51
|
if (name === "addon-pages") {
|
|
@@ -367,436 +57,9 @@ var BenchmarkAddon = class {
|
|
|
367
57
|
}
|
|
368
58
|
return null;
|
|
369
59
|
}
|
|
370
|
-
/**
|
|
371
|
-
* Run a benchmark locally and return a MultiBenchmarkReport.
|
|
372
|
-
*/
|
|
373
|
-
async run(config, onProgress) {
|
|
374
|
-
const systemInfo = collectSystemInfo();
|
|
375
|
-
const results = await this.runLocally(config, onProgress);
|
|
376
|
-
const leaderboard = results.sort((a, b) => a.latency.mean - b.latency.mean).map((r, idx) => ({
|
|
377
|
-
rank: idx + 1,
|
|
378
|
-
agentId: "local",
|
|
379
|
-
label: r.label,
|
|
380
|
-
meanMs: r.latency.mean,
|
|
381
|
-
fps: r.fps,
|
|
382
|
-
peakMemoryMB: r.resources.peakMemoryMB,
|
|
383
|
-
accuracy: r.accuracy
|
|
384
|
-
}));
|
|
385
|
-
return {
|
|
386
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
387
|
-
source: config.source,
|
|
388
|
-
agents: [
|
|
389
|
-
{
|
|
390
|
-
agentId: "local",
|
|
391
|
-
status: "completed",
|
|
392
|
-
systemInfo,
|
|
393
|
-
results
|
|
394
|
-
}
|
|
395
|
-
],
|
|
396
|
-
leaderboard
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Run a distributed benchmark across agents.
|
|
401
|
-
*
|
|
402
|
-
* Stub: distributed execution requires a BenchmarkHub instance bound to
|
|
403
|
-
* live WebSocket connections. Call BenchmarkHub.dispatch() directly from
|
|
404
|
-
* the server transport layer. This method runs locally as a fallback.
|
|
405
|
-
*/
|
|
406
|
-
async runDistributed(config, onProgress) {
|
|
407
|
-
return this.run(config, onProgress);
|
|
408
|
-
}
|
|
409
|
-
async runLocally(config, onProgress) {
|
|
410
|
-
const { inferenceFactory, referenceImages = [] } = this.options;
|
|
411
|
-
const firstTarget = config.targets[0];
|
|
412
|
-
if (!firstTarget) {
|
|
413
|
-
throw new Error("BenchmarkConfig must have at least one target");
|
|
414
|
-
}
|
|
415
|
-
if (!inferenceFactory) {
|
|
416
|
-
return config.targets.map((target) => ({
|
|
417
|
-
label: target.label,
|
|
418
|
-
addonId: target.addonId,
|
|
419
|
-
config: target.config,
|
|
420
|
-
latency: { mean: 0, median: 0, p95: 0, p99: 0, min: 0, max: 0 },
|
|
421
|
-
fps: 0,
|
|
422
|
-
resources: { peakMemoryMB: 0, avgCpuPercent: 0 }
|
|
423
|
-
}));
|
|
424
|
-
}
|
|
425
|
-
const runInference = inferenceFactory(firstTarget.addonId, firstTarget.config);
|
|
426
|
-
const runner = new BenchmarkRunner({
|
|
427
|
-
runInference,
|
|
428
|
-
referenceImages,
|
|
429
|
-
onProgress
|
|
430
|
-
});
|
|
431
|
-
return runner.runAll(config);
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
// src/annotator/result-annotator.ts
|
|
436
|
-
var import_sharp = __toESM(require("sharp"));
|
|
437
|
-
var COLOR_PALETTE = [
|
|
438
|
-
"#FF4136",
|
|
439
|
-
"#2ECC40",
|
|
440
|
-
"#0074D9",
|
|
441
|
-
"#FF851B",
|
|
442
|
-
"#B10DC9",
|
|
443
|
-
"#FFDC00",
|
|
444
|
-
"#7FDBFF",
|
|
445
|
-
"#01FF70",
|
|
446
|
-
"#F012BE",
|
|
447
|
-
"#3D9970"
|
|
448
|
-
];
|
|
449
|
-
function classColor(cls) {
|
|
450
|
-
let hash = 0;
|
|
451
|
-
for (let i = 0; i < cls.length; i++) {
|
|
452
|
-
hash = hash * 31 + (cls.codePointAt(i) ?? 0) >>> 0;
|
|
453
|
-
}
|
|
454
|
-
return COLOR_PALETTE[hash % COLOR_PALETTE.length] ?? "#FF4136";
|
|
455
|
-
}
|
|
456
|
-
function escSvg(str) {
|
|
457
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
458
|
-
}
|
|
459
|
-
function buildSvgOverlay(detections, width, height) {
|
|
460
|
-
const rects = detections.map((det) => {
|
|
461
|
-
const color = classColor(det.class);
|
|
462
|
-
const label = escSvg(`${det.class} ${(det.score * 100).toFixed(0)}%`);
|
|
463
|
-
const { x, y, w, h } = det.bbox;
|
|
464
|
-
const clampX = Math.max(0, Math.min(x, width - 1));
|
|
465
|
-
const clampY = Math.max(0, Math.min(y, height - 1));
|
|
466
|
-
const clampW = Math.max(1, Math.min(w, width - clampX));
|
|
467
|
-
const clampH = Math.max(1, Math.min(h, height - clampY));
|
|
468
|
-
const labelY = clampY > 14 ? clampY - 2 : clampY + clampH + 12;
|
|
469
|
-
return `
|
|
470
|
-
<rect x="${clampX}" y="${clampY}" width="${clampW}" height="${clampH}"
|
|
471
|
-
stroke="${color}" stroke-width="2" fill="none"/>
|
|
472
|
-
<rect x="${clampX}" y="${labelY - 12}" width="${label.length * 7}" height="14"
|
|
473
|
-
fill="${color}" fill-opacity="0.75"/>
|
|
474
|
-
<text x="${clampX + 2}" y="${labelY}" font-family="monospace" font-size="11"
|
|
475
|
-
fill="white">${label}</text>`;
|
|
476
|
-
});
|
|
477
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">${rects.join("")}</svg>`;
|
|
478
|
-
}
|
|
479
|
-
async function buildMaskOverlays(detections, width, height) {
|
|
480
|
-
const overlays = [];
|
|
481
|
-
for (const det of detections) {
|
|
482
|
-
if (!det.mask || !det.maskWidth || !det.maskHeight) continue;
|
|
483
|
-
const color = classColor(det.class);
|
|
484
|
-
const r = parseInt(color.slice(1, 3), 16);
|
|
485
|
-
const g = parseInt(color.slice(3, 5), 16);
|
|
486
|
-
const b = parseInt(color.slice(5, 7), 16);
|
|
487
|
-
const rgba = Buffer.alloc(det.maskWidth * det.maskHeight * 4);
|
|
488
|
-
for (let i = 0; i < det.mask.length; i++) {
|
|
489
|
-
const alpha = det.mask[i] ?? 0;
|
|
490
|
-
if (alpha > 0) {
|
|
491
|
-
const base = i * 4;
|
|
492
|
-
rgba[base] = r;
|
|
493
|
-
rgba[base + 1] = g;
|
|
494
|
-
rgba[base + 2] = b;
|
|
495
|
-
rgba[base + 3] = Math.round(alpha * 0.5);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
const maskBuffer = await (0, import_sharp.default)(rgba, {
|
|
499
|
-
raw: { width: det.maskWidth, height: det.maskHeight, channels: 4 }
|
|
500
|
-
}).resize(width, height).png().toBuffer();
|
|
501
|
-
overlays.push(maskBuffer);
|
|
502
|
-
}
|
|
503
|
-
return overlays;
|
|
504
|
-
}
|
|
505
|
-
async function annotateImage(frame, detections, outputPath) {
|
|
506
|
-
let image;
|
|
507
|
-
if (frame.format === "jpeg") {
|
|
508
|
-
image = (0, import_sharp.default)(frame.data);
|
|
509
|
-
} else {
|
|
510
|
-
image = (0, import_sharp.default)(frame.data, {
|
|
511
|
-
raw: { width: frame.width, height: frame.height, channels: 3 }
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
const composites = [];
|
|
515
|
-
const maskBuffers = await buildMaskOverlays(detections, frame.width, frame.height);
|
|
516
|
-
for (const maskBuf of maskBuffers) {
|
|
517
|
-
composites.push({ input: maskBuf, blend: "over" });
|
|
518
|
-
}
|
|
519
|
-
const svgBuf = Buffer.from(buildSvgOverlay(detections, frame.width, frame.height));
|
|
520
|
-
composites.push({ input: svgBuf, blend: "over" });
|
|
521
|
-
await image.composite(composites).jpeg({ quality: 90 }).toFile(outputPath);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// src/distributed/benchmark-hub.ts
|
|
525
|
-
var BenchmarkHub = class {
|
|
526
|
-
constructor(send) {
|
|
527
|
-
this.send = send;
|
|
528
|
-
}
|
|
529
|
-
agents = /* @__PURE__ */ new Map();
|
|
530
|
-
activeTaskId = null;
|
|
531
|
-
resolveTask = null;
|
|
532
|
-
rejectTask = null;
|
|
533
|
-
pendingConfig = null;
|
|
534
|
-
/** Register a connected agent (typically on WS connect + hello message) */
|
|
535
|
-
registerAgent(agentId, systemInfo) {
|
|
536
|
-
this.agents.set(agentId, {
|
|
537
|
-
agentId,
|
|
538
|
-
systemInfo,
|
|
539
|
-
status: "idle"
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
/** Mark an agent as disconnected */
|
|
543
|
-
disconnectAgent(agentId) {
|
|
544
|
-
const entry = this.agents.get(agentId);
|
|
545
|
-
if (entry) {
|
|
546
|
-
this.agents.set(agentId, { ...entry, status: "disconnected" });
|
|
547
|
-
}
|
|
548
|
-
this.checkCompletion();
|
|
549
|
-
}
|
|
550
|
-
/** Handle incoming message from any agent */
|
|
551
|
-
handleMessage(message) {
|
|
552
|
-
switch (message.type) {
|
|
553
|
-
case "agent.hello":
|
|
554
|
-
this.onAgentHello(message);
|
|
555
|
-
break;
|
|
556
|
-
case "benchmark.progress":
|
|
557
|
-
this.onProgress(message);
|
|
558
|
-
break;
|
|
559
|
-
case "benchmark.result":
|
|
560
|
-
this.onResult(message);
|
|
561
|
-
break;
|
|
562
|
-
case "benchmark.error":
|
|
563
|
-
this.onError(message);
|
|
564
|
-
break;
|
|
565
|
-
default:
|
|
566
|
-
break;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Dispatch a benchmark to a subset of agents.
|
|
571
|
-
* Returns a promise that resolves when all targeted agents have finished.
|
|
572
|
-
*
|
|
573
|
-
* @param config - Benchmark configuration
|
|
574
|
-
* @param agentIds - If empty, dispatches to all connected/idle agents
|
|
575
|
-
* @param referenceImages - Reference image buffers to send to agents
|
|
576
|
-
*/
|
|
577
|
-
async dispatch(config, agentIds, referenceImages = []) {
|
|
578
|
-
const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
579
|
-
this.activeTaskId = taskId;
|
|
580
|
-
this.pendingConfig = config;
|
|
581
|
-
const targets = agentIds.length > 0 ? agentIds : [...this.agents.values()].filter((a) => a.status === "idle").map((a) => a.agentId);
|
|
582
|
-
if (targets.length === 0) {
|
|
583
|
-
throw new Error("No idle agents available for benchmark dispatch");
|
|
584
|
-
}
|
|
585
|
-
for (const agentId of targets) {
|
|
586
|
-
const entry = this.agents.get(agentId);
|
|
587
|
-
if (entry) {
|
|
588
|
-
this.agents.set(agentId, { ...entry, status: "running", results: void 0, error: void 0 });
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
for (const agentId of targets) {
|
|
592
|
-
this.send(agentId, {
|
|
593
|
-
type: "benchmark.start",
|
|
594
|
-
taskId,
|
|
595
|
-
config,
|
|
596
|
-
referenceImages
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
return new Promise((resolve, reject) => {
|
|
600
|
-
this.resolveTask = resolve;
|
|
601
|
-
this.rejectTask = reject;
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
onAgentHello(message) {
|
|
605
|
-
this.registerAgent(message.agentId, message.systemInfo);
|
|
606
|
-
}
|
|
607
|
-
onProgress(message) {
|
|
608
|
-
const entry = this.agents.get(message.agentId);
|
|
609
|
-
if (entry) {
|
|
610
|
-
this.agents.set(message.agentId, {
|
|
611
|
-
...entry,
|
|
612
|
-
completedIterations: message.completed
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
onResult(message) {
|
|
617
|
-
const entry = this.agents.get(message.agentId);
|
|
618
|
-
if (entry) {
|
|
619
|
-
this.agents.set(message.agentId, {
|
|
620
|
-
...entry,
|
|
621
|
-
status: "done",
|
|
622
|
-
systemInfo: message.systemInfo,
|
|
623
|
-
results: message.results
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
this.checkCompletion();
|
|
627
|
-
}
|
|
628
|
-
onError(message) {
|
|
629
|
-
const entry = this.agents.get(message.agentId);
|
|
630
|
-
if (entry) {
|
|
631
|
-
this.agents.set(message.agentId, {
|
|
632
|
-
...entry,
|
|
633
|
-
status: "error",
|
|
634
|
-
error: message.error
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
this.checkCompletion();
|
|
638
|
-
}
|
|
639
|
-
checkCompletion() {
|
|
640
|
-
if (!this.resolveTask || !this.activeTaskId) return;
|
|
641
|
-
const runningAgents = [...this.agents.values()].filter(
|
|
642
|
-
(a) => a.status === "running"
|
|
643
|
-
);
|
|
644
|
-
if (runningAgents.length > 0) return;
|
|
645
|
-
const report = this.buildReport();
|
|
646
|
-
this.resolveTask(report);
|
|
647
|
-
this.resolveTask = null;
|
|
648
|
-
this.rejectTask = null;
|
|
649
|
-
this.activeTaskId = null;
|
|
650
|
-
}
|
|
651
|
-
buildReport() {
|
|
652
|
-
const source = this.pendingConfig?.source ?? { type: "reference", images: "all" };
|
|
653
|
-
const agentEntries = [...this.agents.values()];
|
|
654
|
-
const allResults = [];
|
|
655
|
-
for (const agent of agentEntries) {
|
|
656
|
-
if (agent.results) {
|
|
657
|
-
for (const result of agent.results) {
|
|
658
|
-
allResults.push({ agentId: agent.agentId, result });
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
const leaderboard = allResults.sort((a, b) => a.result.latency.mean - b.result.latency.mean).map((entry, idx) => ({
|
|
663
|
-
rank: idx + 1,
|
|
664
|
-
agentId: entry.agentId,
|
|
665
|
-
label: entry.result.label,
|
|
666
|
-
meanMs: entry.result.latency.mean,
|
|
667
|
-
fps: entry.result.fps,
|
|
668
|
-
peakMemoryMB: entry.result.resources.peakMemoryMB,
|
|
669
|
-
accuracy: entry.result.accuracy
|
|
670
|
-
}));
|
|
671
|
-
return {
|
|
672
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
673
|
-
source,
|
|
674
|
-
agents: agentEntries.map((a) => ({
|
|
675
|
-
agentId: a.agentId,
|
|
676
|
-
status: a.status === "done" ? "completed" : a.status === "error" ? "failed" : a.status === "disconnected" ? "disconnected" : "partial",
|
|
677
|
-
systemInfo: a.systemInfo ?? {
|
|
678
|
-
os: "unknown",
|
|
679
|
-
arch: "unknown",
|
|
680
|
-
cpuModel: "unknown",
|
|
681
|
-
cpuCores: 0,
|
|
682
|
-
totalMemoryMB: 0,
|
|
683
|
-
nodeVersion: "unknown"
|
|
684
|
-
},
|
|
685
|
-
results: a.results ?? [],
|
|
686
|
-
error: a.error,
|
|
687
|
-
completedIterations: a.completedIterations
|
|
688
|
-
})),
|
|
689
|
-
leaderboard
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
};
|
|
693
|
-
|
|
694
|
-
// src/distributed/benchmark-agent.ts
|
|
695
|
-
var BenchmarkAgent = class {
|
|
696
|
-
constructor(agentId, send, inferenceFactory) {
|
|
697
|
-
this.send = send;
|
|
698
|
-
this.inferenceFactory = inferenceFactory;
|
|
699
|
-
this.agentId = agentId;
|
|
700
|
-
this.systemInfo = collectSystemInfo();
|
|
701
|
-
}
|
|
702
|
-
agentId;
|
|
703
|
-
systemInfo;
|
|
704
|
-
cancelRequested = false;
|
|
705
|
-
/** Send hello to the hub — call this on WS connect */
|
|
706
|
-
hello() {
|
|
707
|
-
this.send({
|
|
708
|
-
type: "agent.hello",
|
|
709
|
-
agentId: this.agentId,
|
|
710
|
-
systemInfo: this.systemInfo
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
/** Handle incoming message from the hub */
|
|
714
|
-
handleMessage(message) {
|
|
715
|
-
switch (message.type) {
|
|
716
|
-
case "benchmark.start":
|
|
717
|
-
void this.onStart(message);
|
|
718
|
-
break;
|
|
719
|
-
case "benchmark.cancel":
|
|
720
|
-
this.onCancel(message);
|
|
721
|
-
break;
|
|
722
|
-
default:
|
|
723
|
-
break;
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
async onStart(message) {
|
|
727
|
-
this.cancelRequested = false;
|
|
728
|
-
const { taskId, config } = message;
|
|
729
|
-
try {
|
|
730
|
-
const referenceImages = message.referenceImages.map((img) => ({
|
|
731
|
-
frame: {
|
|
732
|
-
data: img.data,
|
|
733
|
-
format: "jpeg",
|
|
734
|
-
width: img.width,
|
|
735
|
-
height: img.height,
|
|
736
|
-
timestamp: Date.now()
|
|
737
|
-
},
|
|
738
|
-
groundTruth: { image: img.name, annotations: [] }
|
|
739
|
-
}));
|
|
740
|
-
const firstTarget = config.targets[0];
|
|
741
|
-
if (!firstTarget) {
|
|
742
|
-
throw new Error("No benchmark targets specified");
|
|
743
|
-
}
|
|
744
|
-
const runInference = this.inferenceFactory(firstTarget.addonId, firstTarget.config);
|
|
745
|
-
const runner = new BenchmarkRunner({
|
|
746
|
-
runInference,
|
|
747
|
-
referenceImages,
|
|
748
|
-
onProgress: (completed, total, currentTarget) => {
|
|
749
|
-
if (this.cancelRequested) return;
|
|
750
|
-
this.send({
|
|
751
|
-
type: "benchmark.progress",
|
|
752
|
-
taskId,
|
|
753
|
-
agentId: this.agentId,
|
|
754
|
-
completed,
|
|
755
|
-
total,
|
|
756
|
-
currentTarget
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
});
|
|
760
|
-
const results = await runner.runAll(config);
|
|
761
|
-
if (!this.cancelRequested) {
|
|
762
|
-
this.send({
|
|
763
|
-
type: "benchmark.result",
|
|
764
|
-
taskId,
|
|
765
|
-
agentId: this.agentId,
|
|
766
|
-
systemInfo: this.systemInfo,
|
|
767
|
-
results
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
} catch (err) {
|
|
771
|
-
this.send({
|
|
772
|
-
type: "benchmark.error",
|
|
773
|
-
taskId,
|
|
774
|
-
agentId: this.agentId,
|
|
775
|
-
error: err instanceof Error ? err.message : String(err)
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
onCancel(message) {
|
|
780
|
-
if (message.taskId === this.activeTaskId) {
|
|
781
|
-
this.cancelRequested = true;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
/** Returns the current task ID (last started), or null if idle */
|
|
785
|
-
get activeTaskId() {
|
|
786
|
-
return null;
|
|
787
|
-
}
|
|
788
60
|
};
|
|
789
61
|
// Annotate the CommonJS export names for ESM import in node:
|
|
790
62
|
0 && (module.exports = {
|
|
791
|
-
BenchmarkAddon
|
|
792
|
-
BenchmarkAgent,
|
|
793
|
-
BenchmarkHub,
|
|
794
|
-
BenchmarkRunner,
|
|
795
|
-
annotateImage,
|
|
796
|
-
collectSystemInfo,
|
|
797
|
-
computeAccuracy,
|
|
798
|
-
computeFps,
|
|
799
|
-
computeLatencyStats,
|
|
800
|
-
iou
|
|
63
|
+
BenchmarkAddon
|
|
801
64
|
});
|
|
802
65
|
//# sourceMappingURL=index.js.map
|