@pose-tracker/pose-estimation-web 0.2.0 → 0.3.0
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/README.md +6 -2
- package/dist/index.cjs +275 -36
- package/dist/index.d.cts +53 -2
- package/dist/index.d.ts +53 -2
- package/dist/index.js +273 -37
- package/dist/pose-tracker.global.js +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { sha256 } from 'js-sha256';
|
|
2
2
|
|
|
3
3
|
// src/version.ts
|
|
4
|
-
var SDK_VERSION = "0.
|
|
4
|
+
var SDK_VERSION = "0.3.0";
|
|
5
5
|
var SDK_NAME = "posetracker-web";
|
|
6
6
|
|
|
7
7
|
// src/api/configure.ts
|
|
@@ -22,7 +22,8 @@ async function configure(apiToken, options = {}) {
|
|
|
22
22
|
targetPlatform: "web",
|
|
23
23
|
poseModelProfile: options.poseModelProfile ?? "AdaptiveChoice",
|
|
24
24
|
locale: options.locale,
|
|
25
|
-
localVersions: options.localVersions
|
|
25
|
+
localVersions: options.localVersions,
|
|
26
|
+
engineChannel: options.engine === "v4" ? "v4" : "v3"
|
|
26
27
|
};
|
|
27
28
|
let response;
|
|
28
29
|
try {
|
|
@@ -529,6 +530,10 @@ function waitForImageReady(img, timeoutMs = 15e3) {
|
|
|
529
530
|
resolve();
|
|
530
531
|
return;
|
|
531
532
|
}
|
|
533
|
+
if (img.complete) {
|
|
534
|
+
reject(new Error("Image failed to decode (naturalWidth=0)"));
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
532
537
|
const onReady = () => {
|
|
533
538
|
cleanup();
|
|
534
539
|
resolve();
|
|
@@ -612,6 +617,19 @@ function findExerciseByIdOrAlias(exerciseId, exercises) {
|
|
|
612
617
|
return exercises.find((e) => resolveExerciseName(e.id) === canonical);
|
|
613
618
|
}
|
|
614
619
|
|
|
620
|
+
// src/engineChannel.ts
|
|
621
|
+
var V4_ONLY_EXERCISE_IDS = [
|
|
622
|
+
"shoulder_roll",
|
|
623
|
+
"shoulder_deep_breath",
|
|
624
|
+
"chair_forward_fold"
|
|
625
|
+
];
|
|
626
|
+
function normalizeEngineChannel(raw) {
|
|
627
|
+
return raw === "v4" ? "v4" : "v3";
|
|
628
|
+
}
|
|
629
|
+
function requiresEngineV4(exerciseId) {
|
|
630
|
+
return V4_ONLY_EXERCISE_IDS.includes(exerciseId);
|
|
631
|
+
}
|
|
632
|
+
|
|
615
633
|
// src/events/classicMessage.ts
|
|
616
634
|
var ANGLE_RELATIONS = {
|
|
617
635
|
left_knee: "from_hip_to_ankle",
|
|
@@ -1067,6 +1085,23 @@ var BLAZE_TO_COCO = {
|
|
|
1067
1085
|
left_ankle: "left_ankle",
|
|
1068
1086
|
right_ankle: "right_ankle"
|
|
1069
1087
|
};
|
|
1088
|
+
var FACE_EXTRA_NAMES = /* @__PURE__ */ new Set([
|
|
1089
|
+
"left_eye_inner",
|
|
1090
|
+
"left_eye_outer",
|
|
1091
|
+
"right_eye_inner",
|
|
1092
|
+
"right_eye_outer",
|
|
1093
|
+
"mouth_left",
|
|
1094
|
+
"mouth_right"
|
|
1095
|
+
]);
|
|
1096
|
+
var BLAZE_MAX_SIDE = 1280;
|
|
1097
|
+
function toSourcePixels(x, y, scratchW, scratchH, srcW, srcH, maxCoord) {
|
|
1098
|
+
if (maxCoord <= 1.5 && scratchW > 2) {
|
|
1099
|
+
return { x: x * srcW, y: y * srcH };
|
|
1100
|
+
}
|
|
1101
|
+
const sx = scratchW / srcW;
|
|
1102
|
+
const sy = scratchH / srcH;
|
|
1103
|
+
return { x: sx > 0 ? x / sx : x, y: sy > 0 ? y / sy : y };
|
|
1104
|
+
}
|
|
1070
1105
|
var BlazePoseAdapter = class {
|
|
1071
1106
|
constructor(_ctx) {
|
|
1072
1107
|
this.modelId = "blazepose";
|
|
@@ -1087,7 +1122,7 @@ var BlazePoseAdapter = class {
|
|
|
1087
1122
|
{
|
|
1088
1123
|
runtime: "tfjs",
|
|
1089
1124
|
modelType: this.modelType,
|
|
1090
|
-
enableSmoothing:
|
|
1125
|
+
enableSmoothing: false
|
|
1091
1126
|
}
|
|
1092
1127
|
);
|
|
1093
1128
|
}
|
|
@@ -1095,8 +1130,26 @@ var BlazePoseAdapter = class {
|
|
|
1095
1130
|
if (!this.detector) throw new Error("BlazePose adapter not loaded");
|
|
1096
1131
|
const size = blazeInputSize(input);
|
|
1097
1132
|
if (!(size.vw > 0 && size.vh > 0)) return null;
|
|
1133
|
+
if (options.temporalSmooth === false) this.resetTemporal();
|
|
1134
|
+
let estimateTarget = input;
|
|
1135
|
+
let scratch = null;
|
|
1136
|
+
let scratchW = size.vw;
|
|
1137
|
+
let scratchH = size.vh;
|
|
1138
|
+
if (typeof HTMLImageElement !== "undefined" && input instanceof HTMLImageElement || typeof HTMLVideoElement !== "undefined" && input instanceof HTMLVideoElement || typeof HTMLCanvasElement !== "undefined" && input instanceof HTMLCanvasElement) {
|
|
1139
|
+
const fit = Math.min(1, BLAZE_MAX_SIDE / Math.max(size.vw, size.vh));
|
|
1140
|
+
scratchW = Math.max(1, Math.round(size.vw * fit));
|
|
1141
|
+
scratchH = Math.max(1, Math.round(size.vh * fit));
|
|
1142
|
+
scratch = document.createElement("canvas");
|
|
1143
|
+
scratch.width = scratchW;
|
|
1144
|
+
scratch.height = scratchH;
|
|
1145
|
+
const ctx = scratch.getContext("2d");
|
|
1146
|
+
if (ctx) {
|
|
1147
|
+
ctx.drawImage(input, 0, 0, scratchW, scratchH);
|
|
1148
|
+
estimateTarget = scratch;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1098
1151
|
const t0 = performance.now();
|
|
1099
|
-
const poses = await this.detector.estimatePoses(
|
|
1152
|
+
const poses = await this.detector.estimatePoses(estimateTarget, {
|
|
1100
1153
|
flipHorizontal: false,
|
|
1101
1154
|
maxPoses: 1
|
|
1102
1155
|
});
|
|
@@ -1127,16 +1180,22 @@ var BlazePoseAdapter = class {
|
|
|
1127
1180
|
const vh = size.vh || 1;
|
|
1128
1181
|
const dispW = options.displayWidth || 1;
|
|
1129
1182
|
const dispH = options.displayHeight || 1;
|
|
1183
|
+
let maxCoord = 0;
|
|
1184
|
+
for (const kp of pose.keypoints) {
|
|
1185
|
+
maxCoord = Math.max(maxCoord, Math.abs(kp.x || 0), Math.abs(kp.y || 0));
|
|
1186
|
+
}
|
|
1130
1187
|
const byName = /* @__PURE__ */ new Map();
|
|
1188
|
+
const extrasRaw = [];
|
|
1131
1189
|
for (const kp of pose.keypoints) {
|
|
1132
1190
|
const name = String(kp.name || "").toLowerCase();
|
|
1191
|
+
const score = typeof kp.score === "number" ? kp.score : 0;
|
|
1192
|
+
const src = toSourcePixels(kp.x, kp.y, scratchW, scratchH, vw, vh, maxCoord);
|
|
1133
1193
|
const coco = BLAZE_TO_COCO[name];
|
|
1134
|
-
if (
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
y:
|
|
1138
|
-
|
|
1139
|
-
});
|
|
1194
|
+
if (coco) {
|
|
1195
|
+
byName.set(coco, { x: src.x, y: src.y, score });
|
|
1196
|
+
} else if (FACE_EXTRA_NAMES.has(name)) {
|
|
1197
|
+
extrasRaw.push({ name, x: src.x, y: src.y, score });
|
|
1198
|
+
}
|
|
1140
1199
|
}
|
|
1141
1200
|
const videoKeypoints = COCO_KEYPOINT_NAMES.map((name) => {
|
|
1142
1201
|
const k = byName.get(name);
|
|
@@ -1165,6 +1224,19 @@ var BlazePoseAdapter = class {
|
|
|
1165
1224
|
score: k.score
|
|
1166
1225
|
};
|
|
1167
1226
|
});
|
|
1227
|
+
for (const extra of extrasRaw) {
|
|
1228
|
+
const dx = extra.x * scale + ox;
|
|
1229
|
+
const dy = extra.y * scale + oy;
|
|
1230
|
+
let nx = dispW > 0 ? dx / dispW : 0;
|
|
1231
|
+
const ny = dispH > 0 ? dy / dispH : 0;
|
|
1232
|
+
if (options.facingMode === "user") nx = 1 - nx;
|
|
1233
|
+
keypoints.push({
|
|
1234
|
+
name: extra.name,
|
|
1235
|
+
x: Math.min(1, Math.max(0, nx)),
|
|
1236
|
+
y: Math.min(1, Math.max(0, ny)),
|
|
1237
|
+
score: extra.score
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1168
1240
|
return {
|
|
1169
1241
|
keypoints,
|
|
1170
1242
|
score: scoreSum / 17,
|
|
@@ -1180,6 +1252,13 @@ var BlazePoseAdapter = class {
|
|
|
1180
1252
|
}
|
|
1181
1253
|
};
|
|
1182
1254
|
}
|
|
1255
|
+
/** Clear cached ROI / landmark filters (call when the still changes). */
|
|
1256
|
+
resetTemporal() {
|
|
1257
|
+
try {
|
|
1258
|
+
this.detector?.reset?.();
|
|
1259
|
+
} catch {
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1183
1262
|
dispose() {
|
|
1184
1263
|
try {
|
|
1185
1264
|
this.detector?.dispose?.();
|
|
@@ -1203,13 +1282,14 @@ function inputSize(input) {
|
|
|
1203
1282
|
return { vw: input.width || 0, vh: input.height || 0 };
|
|
1204
1283
|
}
|
|
1205
1284
|
var INPUT_SIZE = 192;
|
|
1206
|
-
var SMOOTH_ALPHA = 0.
|
|
1285
|
+
var SMOOTH_ALPHA = 0.45;
|
|
1286
|
+
var STILL_TARGET_FILL = 0.52;
|
|
1207
1287
|
function modelNormToVideo(xNorm, yNorm, lb) {
|
|
1208
1288
|
const xSq = xNorm * INPUT_SIZE;
|
|
1209
1289
|
const ySq = yNorm * INPUT_SIZE;
|
|
1210
1290
|
return {
|
|
1211
|
-
x: (xSq - lb.offsetX) * (lb.vw / lb.drawW),
|
|
1212
|
-
y: (ySq - lb.offsetY) * (lb.vh / lb.drawH)
|
|
1291
|
+
x: (xSq - lb.offsetX) * (lb.vw / lb.drawW) + (lb.originX ?? 0),
|
|
1292
|
+
y: (ySq - lb.offsetY) * (lb.vh / lb.drawH) + (lb.originY ?? 0)
|
|
1213
1293
|
};
|
|
1214
1294
|
}
|
|
1215
1295
|
function videoToCover(vx, vy, dispW, dispH, lb) {
|
|
@@ -1226,6 +1306,18 @@ var MoveNetGraphAdapter = class {
|
|
|
1226
1306
|
this.offscreen = null;
|
|
1227
1307
|
this.offCtx = null;
|
|
1228
1308
|
this.smoothed = null;
|
|
1309
|
+
this.lastVideoKps = null;
|
|
1310
|
+
this.stillPass = 0;
|
|
1311
|
+
this.inferLb = {
|
|
1312
|
+
offsetX: 0,
|
|
1313
|
+
offsetY: 0,
|
|
1314
|
+
drawW: INPUT_SIZE,
|
|
1315
|
+
drawH: INPUT_SIZE,
|
|
1316
|
+
vw: 1,
|
|
1317
|
+
vh: 1,
|
|
1318
|
+
originX: 0,
|
|
1319
|
+
originY: 0
|
|
1320
|
+
};
|
|
1229
1321
|
this.letterbox = {
|
|
1230
1322
|
offsetX: 0,
|
|
1231
1323
|
offsetY: 0,
|
|
@@ -1265,20 +1357,81 @@ var MoveNetGraphAdapter = class {
|
|
|
1265
1357
|
if (!this.offCtx) throw new Error("2d context unavailable");
|
|
1266
1358
|
return this.offCtx;
|
|
1267
1359
|
}
|
|
1268
|
-
prepareInput(input) {
|
|
1360
|
+
prepareInput(input, trackRoi) {
|
|
1269
1361
|
const { vw: rawW, vh: rawH } = inputSize(input);
|
|
1270
1362
|
const vw = rawW || 1;
|
|
1271
1363
|
const vh = rawH || 1;
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1364
|
+
let winX = 0;
|
|
1365
|
+
let winY = 0;
|
|
1366
|
+
let winW = vw;
|
|
1367
|
+
let winH = vh;
|
|
1368
|
+
if (trackRoi && this.lastVideoKps && this.stillPass > 0) {
|
|
1369
|
+
const pts = this.lastVideoKps.filter((k) => k.score >= 0.15);
|
|
1370
|
+
if (pts.length >= 4) {
|
|
1371
|
+
let minx = Infinity;
|
|
1372
|
+
let miny = Infinity;
|
|
1373
|
+
let maxx = -Infinity;
|
|
1374
|
+
let maxy = -Infinity;
|
|
1375
|
+
for (const p of pts) {
|
|
1376
|
+
minx = Math.min(minx, p.xPx);
|
|
1377
|
+
miny = Math.min(miny, p.yPx);
|
|
1378
|
+
maxx = Math.max(maxx, p.xPx);
|
|
1379
|
+
maxy = Math.max(maxy, p.yPx);
|
|
1380
|
+
}
|
|
1381
|
+
const bw = Math.max(8, maxx - minx);
|
|
1382
|
+
const bh = Math.max(8, maxy - miny);
|
|
1383
|
+
const cx = (minx + maxx) / 2;
|
|
1384
|
+
const cy = (miny + maxy) / 2;
|
|
1385
|
+
winW = Math.max(bw / STILL_TARGET_FILL, vw * 0.35);
|
|
1386
|
+
winH = Math.max(bh / STILL_TARGET_FILL, vh * 0.35);
|
|
1387
|
+
const imgAspect = vw / vh;
|
|
1388
|
+
if (winW / winH < imgAspect) winW = winH * imgAspect;
|
|
1389
|
+
else winH = winW / imgAspect;
|
|
1390
|
+
winX = cx - winW / 2;
|
|
1391
|
+
winY = cy - winH / 2;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
const scale = Math.min(INPUT_SIZE / winW, INPUT_SIZE / winH);
|
|
1395
|
+
const drawW = winW * scale;
|
|
1396
|
+
const drawH = winH * scale;
|
|
1275
1397
|
const offsetX = (INPUT_SIZE - drawW) / 2;
|
|
1276
1398
|
const offsetY = (INPUT_SIZE - drawH) / 2;
|
|
1277
|
-
this.
|
|
1399
|
+
this.inferLb = {
|
|
1400
|
+
offsetX,
|
|
1401
|
+
offsetY,
|
|
1402
|
+
drawW,
|
|
1403
|
+
drawH,
|
|
1404
|
+
vw: winW,
|
|
1405
|
+
vh: winH,
|
|
1406
|
+
originX: winX,
|
|
1407
|
+
originY: winY
|
|
1408
|
+
};
|
|
1409
|
+
this.letterbox = { offsetX: 0, offsetY: 0, drawW: vw, drawH: vh, vw, vh };
|
|
1278
1410
|
const c2d = this.poseCanvasCtx();
|
|
1279
1411
|
c2d.fillStyle = "#000";
|
|
1280
1412
|
c2d.fillRect(0, 0, INPUT_SIZE, INPUT_SIZE);
|
|
1281
|
-
|
|
1413
|
+
const srcX = Math.max(0, winX);
|
|
1414
|
+
const srcY = Math.max(0, winY);
|
|
1415
|
+
const srcR = Math.min(vw, winX + winW);
|
|
1416
|
+
const srcB = Math.min(vh, winY + winH);
|
|
1417
|
+
if (srcR > srcX && srcB > srcY) {
|
|
1418
|
+
const dx = offsetX + (srcX - winX) * (drawW / winW);
|
|
1419
|
+
const dy = offsetY + (srcY - winY) * (drawH / winH);
|
|
1420
|
+
const dw = (srcR - srcX) * (drawW / winW);
|
|
1421
|
+
const dh = (srcB - srcY) * (drawH / winH);
|
|
1422
|
+
c2d.drawImage(
|
|
1423
|
+
input,
|
|
1424
|
+
srcX,
|
|
1425
|
+
srcY,
|
|
1426
|
+
srcR - srcX,
|
|
1427
|
+
srcB - srcY,
|
|
1428
|
+
dx,
|
|
1429
|
+
dy,
|
|
1430
|
+
dw,
|
|
1431
|
+
dh
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
this.stillPass += 1;
|
|
1282
1435
|
return this.offscreen;
|
|
1283
1436
|
}
|
|
1284
1437
|
smooth(raw) {
|
|
@@ -1299,7 +1452,9 @@ var MoveNetGraphAdapter = class {
|
|
|
1299
1452
|
const size = inputSize(input);
|
|
1300
1453
|
if (!(size.vw > 0 && size.vh > 0)) return null;
|
|
1301
1454
|
const t0 = performance.now();
|
|
1302
|
-
const
|
|
1455
|
+
const isStill = typeof HTMLImageElement !== "undefined" && input instanceof HTMLImageElement;
|
|
1456
|
+
const trackRoi = options.temporalSmooth !== false && isStill;
|
|
1457
|
+
const canvas = this.prepareInput(input, trackRoi);
|
|
1303
1458
|
const tensorIn = this.tf.tidy(
|
|
1304
1459
|
() => this.tf.expandDims(this.tf.browser.fromPixels(canvas), 0)
|
|
1305
1460
|
);
|
|
@@ -1310,7 +1465,8 @@ var MoveNetGraphAdapter = class {
|
|
|
1310
1465
|
if (Array.isArray(out)) out.forEach((t) => t.dispose());
|
|
1311
1466
|
else out.dispose();
|
|
1312
1467
|
const inferenceMs = performance.now() - t0;
|
|
1313
|
-
const lb = this.
|
|
1468
|
+
const lb = this.inferLb;
|
|
1469
|
+
const coverLb = this.letterbox;
|
|
1314
1470
|
const dispW = options.displayWidth || 1;
|
|
1315
1471
|
const dispH = options.displayHeight || 1;
|
|
1316
1472
|
const drawRaw = [];
|
|
@@ -1328,7 +1484,8 @@ var MoveNetGraphAdapter = class {
|
|
|
1328
1484
|
});
|
|
1329
1485
|
scoreSum += score;
|
|
1330
1486
|
}
|
|
1331
|
-
const sm = this.smooth(drawRaw);
|
|
1487
|
+
const sm = options.temporalSmooth === false ? drawRaw : this.smooth(drawRaw);
|
|
1488
|
+
this.lastVideoKps = sm.map((k) => ({ xPx: k.xPx, yPx: k.yPx, score: k.score }));
|
|
1332
1489
|
const videoKeypoints = sm.map((k, i) => ({
|
|
1333
1490
|
name: COCO_KEYPOINT_NAMES[i],
|
|
1334
1491
|
xPx: k.xPx,
|
|
@@ -1336,7 +1493,7 @@ var MoveNetGraphAdapter = class {
|
|
|
1336
1493
|
score: k.score
|
|
1337
1494
|
}));
|
|
1338
1495
|
const keypoints = videoKeypoints.map((k) => {
|
|
1339
|
-
const d = videoToCover(k.xPx, k.yPx, dispW, dispH,
|
|
1496
|
+
const d = videoToCover(k.xPx, k.yPx, dispW, dispH, coverLb);
|
|
1340
1497
|
let nx = dispW > 0 ? d.x / dispW : 0;
|
|
1341
1498
|
const ny = dispH > 0 ? d.y / dispH : 0;
|
|
1342
1499
|
if (options.facingMode === "user") nx = 1 - nx;
|
|
@@ -1353,9 +1510,15 @@ var MoveNetGraphAdapter = class {
|
|
|
1353
1510
|
score: scoreSum / 17,
|
|
1354
1511
|
inferenceMs,
|
|
1355
1512
|
videoKeypoints,
|
|
1356
|
-
letterbox:
|
|
1513
|
+
letterbox: coverLb
|
|
1357
1514
|
};
|
|
1358
1515
|
}
|
|
1516
|
+
/** Clear EMA state (call when the still / clip changes). */
|
|
1517
|
+
resetTemporal() {
|
|
1518
|
+
this.smoothed = null;
|
|
1519
|
+
this.lastVideoKps = null;
|
|
1520
|
+
this.stillPass = 0;
|
|
1521
|
+
}
|
|
1359
1522
|
dispose() {
|
|
1360
1523
|
try {
|
|
1361
1524
|
this.model?.dispose();
|
|
@@ -1363,6 +1526,8 @@ var MoveNetGraphAdapter = class {
|
|
|
1363
1526
|
}
|
|
1364
1527
|
this.model = null;
|
|
1365
1528
|
this.smoothed = null;
|
|
1529
|
+
this.lastVideoKps = null;
|
|
1530
|
+
this.stillPass = 0;
|
|
1366
1531
|
this.offscreen = null;
|
|
1367
1532
|
this.offCtx = null;
|
|
1368
1533
|
}
|
|
@@ -1983,7 +2148,11 @@ var PoseTrackerClient = class {
|
|
|
1983
2148
|
if (this.poseSource.type === "camera" && this.poseSource.facingMode) {
|
|
1984
2149
|
this.opts.facingMode = this.poseSource.facingMode;
|
|
1985
2150
|
}
|
|
2151
|
+
if (this.adapter?.resetTemporal) {
|
|
2152
|
+
this.adapter.resetTemporal();
|
|
2153
|
+
}
|
|
1986
2154
|
if (!this.shell) return;
|
|
2155
|
+
await this.ensureAdapter();
|
|
1987
2156
|
await this.ensureSource();
|
|
1988
2157
|
if (wasRunning || this.poseSource.type === "image") {
|
|
1989
2158
|
if (this.poseSource.type === "image") this.imageShotPending = true;
|
|
@@ -2095,7 +2264,30 @@ var PoseTrackerClient = class {
|
|
|
2095
2264
|
return this.configurePromise;
|
|
2096
2265
|
}
|
|
2097
2266
|
getAvailableExercises() {
|
|
2098
|
-
|
|
2267
|
+
if (this.mode !== "full-engine") return [];
|
|
2268
|
+
if (normalizeEngineChannel(this.opts.engine) === "v4") {
|
|
2269
|
+
const listed = this.engine?.listExercises?.() ?? [];
|
|
2270
|
+
if (listed.length) return listed.map((e) => this.v4ExerciseConfig(e));
|
|
2271
|
+
}
|
|
2272
|
+
return this.manifest?.exercises ?? [];
|
|
2273
|
+
}
|
|
2274
|
+
getEngineChannel() {
|
|
2275
|
+
return normalizeEngineChannel(this.opts.engine);
|
|
2276
|
+
}
|
|
2277
|
+
v4ExerciseConfig(entry) {
|
|
2278
|
+
const type = entry.type === "static" ? "static" : "dynamic";
|
|
2279
|
+
return {
|
|
2280
|
+
id: entry.id,
|
|
2281
|
+
name: entry.displayName || entry.id,
|
|
2282
|
+
type,
|
|
2283
|
+
movement: {
|
|
2284
|
+
name: entry.id,
|
|
2285
|
+
type,
|
|
2286
|
+
scale_acceptance: {},
|
|
2287
|
+
movement_steps: [],
|
|
2288
|
+
movement_initial_posture: null
|
|
2289
|
+
}
|
|
2290
|
+
};
|
|
2099
2291
|
}
|
|
2100
2292
|
getAvailableCustomExercises() {
|
|
2101
2293
|
if (this.mode !== "full-engine" || !this.engine?.listCustomExercises) return [];
|
|
@@ -2131,6 +2323,28 @@ var PoseTrackerClient = class {
|
|
|
2131
2323
|
throw new Error(FREE_PLAN_FEATURES_MESSAGE);
|
|
2132
2324
|
}
|
|
2133
2325
|
}
|
|
2326
|
+
if (requiresEngineV4(exerciseId) && normalizeEngineChannel(this.opts.engine) !== "v4") {
|
|
2327
|
+
const message = `Exercise '${exerciseId}' requires engine: 'v4'`;
|
|
2328
|
+
this.reportError({ type: "error", code: "invalid_exercise", message });
|
|
2329
|
+
throw new Error(message);
|
|
2330
|
+
}
|
|
2331
|
+
if (normalizeEngineChannel(this.opts.engine) === "v4") {
|
|
2332
|
+
const listed = this.engine.listExercises?.() ?? [];
|
|
2333
|
+
const v4Hit = listed.find((e) => e.id === exerciseId);
|
|
2334
|
+
if (v4Hit) {
|
|
2335
|
+
this.beginEngineSession(this.v4ExerciseConfig(v4Hit), options);
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
const customs = this.getAvailableCustomExercises();
|
|
2339
|
+
const custom = customs.find((e) => e.id === exerciseId) ?? findExerciseByIdOrAlias(exerciseId, customs);
|
|
2340
|
+
if (custom) {
|
|
2341
|
+
this.startCustomExercise(custom, options);
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
const message = `Exercise '${exerciseId}' is not available in V4 engine`;
|
|
2345
|
+
this.reportError({ type: "error", code: "invalid_exercise", message });
|
|
2346
|
+
throw new Error(message);
|
|
2347
|
+
}
|
|
2134
2348
|
const available = this.getAvailableExercises();
|
|
2135
2349
|
const exercise = findExerciseByIdOrAlias(exerciseId, available);
|
|
2136
2350
|
if (!exercise) {
|
|
@@ -2144,8 +2358,12 @@ var PoseTrackerClient = class {
|
|
|
2144
2358
|
this.reportError({ type: "error", code: "invalid_exercise", message });
|
|
2145
2359
|
throw new Error(message);
|
|
2146
2360
|
}
|
|
2361
|
+
this.beginEngineSession(exercise, options);
|
|
2362
|
+
}
|
|
2363
|
+
beginEngineSession(exercise, options) {
|
|
2364
|
+
if (!this.engine) return;
|
|
2147
2365
|
this.stopExercise();
|
|
2148
|
-
this.currentExerciseId =
|
|
2366
|
+
this.currentExerciseId = exercise.id;
|
|
2149
2367
|
this.keypointsSuppressionLogged = false;
|
|
2150
2368
|
this.session = this.engine.createSession(
|
|
2151
2369
|
{
|
|
@@ -2512,13 +2730,19 @@ var PoseTrackerClient = class {
|
|
|
2512
2730
|
if (!this.adapter || !this.shell || !this.estimateInput) return null;
|
|
2513
2731
|
if (!this.frameReady(this.estimateInput)) return null;
|
|
2514
2732
|
const input = this.estimateInput;
|
|
2515
|
-
const
|
|
2516
|
-
const
|
|
2733
|
+
const clientW = this.shell.canvas.clientWidth || this.shell.root.clientWidth || 1;
|
|
2734
|
+
const clientH = this.shell.canvas.clientHeight || this.shell.root.clientHeight || 1;
|
|
2735
|
+
const media = mediaSize(input);
|
|
2736
|
+
const useMediaSpace = this.poseSource.type === "image" && media.width > 0 && media.height > 0;
|
|
2737
|
+
const dispW = useMediaSpace ? media.width : clientW;
|
|
2738
|
+
const dispH = useMediaSpace ? media.height : clientH;
|
|
2517
2739
|
const facing = this.poseSource.type === "camera" ? facingFromOptions(this.opts) : "environment";
|
|
2740
|
+
const stillTracked = this.poseSource.type === "image" && this.opts.stillMode === "tracked";
|
|
2518
2741
|
const result = await this.adapter.estimate(input, {
|
|
2519
2742
|
facingMode: facing,
|
|
2520
2743
|
displayWidth: dispW,
|
|
2521
|
-
displayHeight: dispH
|
|
2744
|
+
displayHeight: dispH,
|
|
2745
|
+
temporalSmooth: this.poseSource.type === "camera" || this.poseSource.type === "video" || stillTracked
|
|
2522
2746
|
});
|
|
2523
2747
|
if (!result) return null;
|
|
2524
2748
|
const ctx = this.shell.canvas.getContext("2d");
|
|
@@ -2526,12 +2750,12 @@ var PoseTrackerClient = class {
|
|
|
2526
2750
|
const mapped = mapVideoKeypointsToDisplay(
|
|
2527
2751
|
result.videoKeypoints,
|
|
2528
2752
|
result.letterbox,
|
|
2529
|
-
|
|
2530
|
-
|
|
2753
|
+
clientW,
|
|
2754
|
+
clientH
|
|
2531
2755
|
);
|
|
2532
|
-
drawSkeleton(ctx, mapped,
|
|
2756
|
+
drawSkeleton(ctx, mapped, clientW, clientH, true, this.skeletonDef);
|
|
2533
2757
|
} else if (ctx && this.opts.drawSkeleton === false) {
|
|
2534
|
-
ctx.clearRect(0, 0,
|
|
2758
|
+
ctx.clearRect(0, 0, clientW, clientH);
|
|
2535
2759
|
}
|
|
2536
2760
|
const pose = {
|
|
2537
2761
|
keypoints: result.keypoints,
|
|
@@ -2574,7 +2798,13 @@ var PoseTrackerClient = class {
|
|
|
2574
2798
|
return pose;
|
|
2575
2799
|
}
|
|
2576
2800
|
async loop(token) {
|
|
2577
|
-
if (token !== this.loopToken || !this.running || !this.
|
|
2801
|
+
if (token !== this.loopToken || !this.running || !this.shell || this.disposed) {
|
|
2802
|
+
return;
|
|
2803
|
+
}
|
|
2804
|
+
if (!this.adapter) {
|
|
2805
|
+
requestAnimationFrame(() => {
|
|
2806
|
+
void this.loop(token);
|
|
2807
|
+
});
|
|
2578
2808
|
return;
|
|
2579
2809
|
}
|
|
2580
2810
|
const input = this.estimateInput;
|
|
@@ -2582,8 +2812,8 @@ var PoseTrackerClient = class {
|
|
|
2582
2812
|
if (shouldInfer) {
|
|
2583
2813
|
this.busy = true;
|
|
2584
2814
|
try {
|
|
2585
|
-
await this.runOneFrame();
|
|
2586
|
-
if (this.poseSource.type === "image") {
|
|
2815
|
+
const pose = await this.runOneFrame();
|
|
2816
|
+
if (this.poseSource.type === "image" && pose) {
|
|
2587
2817
|
this.imageShotPending = false;
|
|
2588
2818
|
this.running = false;
|
|
2589
2819
|
this.busy = false;
|
|
@@ -2592,6 +2822,12 @@ var PoseTrackerClient = class {
|
|
|
2592
2822
|
} catch (err) {
|
|
2593
2823
|
const message = err instanceof Error ? err.message : String(err);
|
|
2594
2824
|
this.emit({ type: "error", code: "internal", message: `inference: ${message}` });
|
|
2825
|
+
if (this.poseSource.type === "image") {
|
|
2826
|
+
this.imageShotPending = false;
|
|
2827
|
+
this.running = false;
|
|
2828
|
+
this.busy = false;
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2595
2831
|
}
|
|
2596
2832
|
this.busy = false;
|
|
2597
2833
|
}
|
|
@@ -2953,6 +3189,6 @@ var PoseCamera = {
|
|
|
2953
3189
|
mount: PoseCamera_mount
|
|
2954
3190
|
};
|
|
2955
3191
|
|
|
2956
|
-
export { BRAND_COLORS, BlazePoseAdapter, COCO_KEYPOINT_NAMES, COMBINED_REFERENCE_EXERCISE_MESSAGE, ConfigureError, DEFAULT_BASE_URL, DEFAULT_FEATURES, DEFAULT_LOADING_TEXT, DEFAULT_MOVENET_LIGHTNING_URL, DEFAULT_SKELETON, DEFAULT_SKELETON_DEFINITION, EXERCISE_ALIASES, EngineLoader, FREE_PLAN_FEATURES_MESSAGE, INVALID_TOKEN_MESSAGE, MoveNetGraphAdapter, ONLINE_POSE_DETECTION_VERSION, ONLINE_TFJS_VERSION, POSETRACKER_LOGO_URL, PoseCamera, PoseCamera_mount, PoseTrackerClient, SDK_NAME, SDK_VERSION, SkeletonFetchError, TrackError, UsageTracker, configure, createLocalFileStore, createLocalKeyValueStore, createMemoryFileStore, createPoseAdapter, createPoseTracker, createWebFileStore, defaultPoseDetectionCdnUrl, defaultTfjsCdnUrls, drawSkeleton, featureNotSupportedMessage, fetchSkeletonDefinition, findExerciseByIdOrAlias, freeBlockedFeatures, initWebGlBackend, isPaidPlan, mapVideoKeypointsToDisplay, mountDomShell, normalizePoseSource, openCameraStream, resolveExerciseName, resolveFeatures, resolvePoseDetection, resolvePoseModel, resolveTf, shouldShowWatermark, stopMediaStream, toClassicNativeMessage };
|
|
3192
|
+
export { BRAND_COLORS, BlazePoseAdapter, COCO_KEYPOINT_NAMES, COMBINED_REFERENCE_EXERCISE_MESSAGE, ConfigureError, DEFAULT_BASE_URL, DEFAULT_FEATURES, DEFAULT_LOADING_TEXT, DEFAULT_MOVENET_LIGHTNING_URL, DEFAULT_SKELETON, DEFAULT_SKELETON_DEFINITION, EXERCISE_ALIASES, EngineLoader, FREE_PLAN_FEATURES_MESSAGE, INVALID_TOKEN_MESSAGE, MoveNetGraphAdapter, ONLINE_POSE_DETECTION_VERSION, ONLINE_TFJS_VERSION, POSETRACKER_LOGO_URL, PoseCamera, PoseCamera_mount, PoseTrackerClient, SDK_NAME, SDK_VERSION, SkeletonFetchError, TrackError, UsageTracker, V4_ONLY_EXERCISE_IDS, configure, createLocalFileStore, createLocalKeyValueStore, createMemoryFileStore, createPoseAdapter, createPoseTracker, createWebFileStore, defaultPoseDetectionCdnUrl, defaultTfjsCdnUrls, drawSkeleton, featureNotSupportedMessage, fetchSkeletonDefinition, findExerciseByIdOrAlias, freeBlockedFeatures, initWebGlBackend, isPaidPlan, mapVideoKeypointsToDisplay, mountDomShell, normalizeEngineChannel, normalizePoseSource, openCameraStream, requiresEngineV4, resolveExerciseName, resolveFeatures, resolvePoseDetection, resolvePoseModel, resolveTf, shouldShowWatermark, stopMediaStream, toClassicNativeMessage };
|
|
2957
3193
|
//# sourceMappingURL=index.js.map
|
|
2958
3194
|
//# sourceMappingURL=index.js.map
|