@kubohiroya/turbowarp-teachable-machine 3.5.0 → 3.5.2
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 +17 -6
- package/dist/backend-wasm.js +1 -1
- package/dist/backend-webgpu.js +1 -1
- package/dist/composition.js +666 -8
- package/dist/posenet.js +15 -6
- package/dist/runtime.js +3 -3
- package/dist/tm.js +2723 -2028
- package/dist/types/extension.d.ts +7 -0
- package/dist/types/fixed-input-editors.d.ts +7 -0
- package/dist/types/fixed-input-schema.d.ts +142 -0
- package/docs/fixed-input-editors.ja.md +36 -0
- package/package.json +15 -6
- package/schemas/block-editor-fixed-inputs.schema.json +193 -0
package/dist/tm.js
CHANGED
|
@@ -7,435 +7,1110 @@
|
|
|
7
7
|
(function (Scratch) {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
|
+
//#region node_modules/.pnpm/@kubohiroya+turbowarp-extension-data-editor@0.2.1_react@18.3.1/node_modules/@kubohiroya/turbowarp-extension-data-editor/dist/fixed-input-schema.js
|
|
11
|
+
var numberToken = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/u;
|
|
12
|
+
var invalidNumber = Object.freeze({
|
|
13
|
+
kind: "invalid",
|
|
14
|
+
code: "fixed-number-input-invalid"
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Parse a NEW numeric draft according to NP-finite-new-input-v1.
|
|
18
|
+
* Never apply this to opening/no-op raw values or use it as an owner validator.
|
|
19
|
+
* Keep the validated token so negative zero, precision and exponent spelling survive.
|
|
20
|
+
*/
|
|
21
|
+
function parseFixedNumberInput(input) {
|
|
22
|
+
if (typeof input === "number") {
|
|
23
|
+
if (!Number.isFinite(input)) return invalidNumber;
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
kind: "valid",
|
|
26
|
+
token: Object.is(input, -0) ? "-0" : String(input),
|
|
27
|
+
value: input
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (typeof input !== "string") return invalidNumber;
|
|
31
|
+
const token = input.trim();
|
|
32
|
+
if (!numberToken.test(token)) return invalidNumber;
|
|
33
|
+
const value = Number(token);
|
|
34
|
+
return Number.isFinite(value) ? Object.freeze({
|
|
35
|
+
kind: "valid",
|
|
36
|
+
token,
|
|
37
|
+
value
|
|
38
|
+
}) : invalidNumber;
|
|
39
|
+
}
|
|
40
|
+
var block_editor_fixed_inputs_schema_default = {
|
|
41
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
42
|
+
$id: "https://kubohiroya.github.io/turbowarp-teachable-machine/schemas/block-editor-fixed-inputs.schema.json",
|
|
43
|
+
title: "turbowarp-teachable-machine fixed input registration schema",
|
|
44
|
+
$defs: {
|
|
45
|
+
"setRecognitionMode": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"properties": { "MODE": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"enum": [
|
|
50
|
+
"pose",
|
|
51
|
+
"image",
|
|
52
|
+
"audio"
|
|
53
|
+
]
|
|
54
|
+
} }
|
|
55
|
+
},
|
|
56
|
+
"setComputeMode": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"properties": { "MODE": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"enum": [
|
|
61
|
+
"auto",
|
|
62
|
+
"webgpu",
|
|
63
|
+
"webgl",
|
|
64
|
+
"wasm",
|
|
65
|
+
"cpu"
|
|
66
|
+
]
|
|
67
|
+
} }
|
|
68
|
+
},
|
|
69
|
+
"setPreviewOpacity": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"properties": { "OPACITY": {
|
|
72
|
+
"type": "number",
|
|
73
|
+
"$comment": "New finite numeric input uses NP-finite-new-input-v1. Existing raw is preserved on no-op; runtime clamp/floor is not applied to stored raw."
|
|
74
|
+
} }
|
|
75
|
+
},
|
|
76
|
+
"setPreviewPosition": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"properties": { "POSITION": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"enum": [
|
|
81
|
+
"top-left",
|
|
82
|
+
"top-right",
|
|
83
|
+
"bottom-left",
|
|
84
|
+
"bottom-right",
|
|
85
|
+
"center",
|
|
86
|
+
"full-stage"
|
|
87
|
+
]
|
|
88
|
+
} }
|
|
89
|
+
},
|
|
90
|
+
"setPreviewMirroring": {
|
|
91
|
+
"type": "object",
|
|
92
|
+
"properties": { "MIRRORING": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"enum": ["mirrored", "unmirrored"]
|
|
95
|
+
} }
|
|
96
|
+
},
|
|
97
|
+
"setPoseOverlayVisibility": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"properties": { "VISIBILITY": {
|
|
100
|
+
"type": "string",
|
|
101
|
+
"enum": ["on", "off"]
|
|
102
|
+
} }
|
|
103
|
+
},
|
|
104
|
+
"setPoseJointStyle": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"PART": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"enum": [
|
|
110
|
+
"nose",
|
|
111
|
+
"leftEye",
|
|
112
|
+
"rightEye",
|
|
113
|
+
"leftEar",
|
|
114
|
+
"rightEar",
|
|
115
|
+
"leftShoulder",
|
|
116
|
+
"rightShoulder",
|
|
117
|
+
"leftElbow",
|
|
118
|
+
"rightElbow",
|
|
119
|
+
"leftWrist",
|
|
120
|
+
"rightWrist",
|
|
121
|
+
"leftHip",
|
|
122
|
+
"rightHip",
|
|
123
|
+
"leftKnee",
|
|
124
|
+
"rightKnee",
|
|
125
|
+
"leftAnkle",
|
|
126
|
+
"rightAnkle"
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
"OPACITY": {
|
|
130
|
+
"type": "number",
|
|
131
|
+
"$comment": "New finite numeric input uses NP-finite-new-input-v1. Existing raw is preserved on no-op; runtime clamp/floor is not applied to stored raw."
|
|
132
|
+
},
|
|
133
|
+
"RADIUS": {
|
|
134
|
+
"type": "number",
|
|
135
|
+
"$comment": "New finite numeric input uses NP-finite-new-input-v1. Existing raw is preserved on no-op; runtime clamp/floor is not applied to stored raw."
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"setPoseBoneStyle": {
|
|
140
|
+
"type": "object",
|
|
141
|
+
"properties": {
|
|
142
|
+
"OPACITY": {
|
|
143
|
+
"type": "number",
|
|
144
|
+
"$comment": "New finite numeric input uses NP-finite-new-input-v1. Existing raw is preserved on no-op; runtime clamp/floor is not applied to stored raw."
|
|
145
|
+
},
|
|
146
|
+
"WIDTH": {
|
|
147
|
+
"type": "number",
|
|
148
|
+
"$comment": "New finite numeric input uses NP-finite-new-input-v1. Existing raw is preserved on no-op; runtime clamp/floor is not applied to stored raw."
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"setPoseOverlayMinimumConfidence": {
|
|
153
|
+
"type": "object",
|
|
154
|
+
"properties": { "CONFIDENCE": {
|
|
155
|
+
"type": "number",
|
|
156
|
+
"$comment": "New finite numeric input uses NP-finite-new-input-v1. Existing raw is preserved on no-op; runtime clamp/floor is not applied to stored raw."
|
|
157
|
+
} }
|
|
158
|
+
},
|
|
159
|
+
"setPoseConfidenceScaling": {
|
|
160
|
+
"type": "object",
|
|
161
|
+
"properties": {
|
|
162
|
+
"PROPERTY": {
|
|
163
|
+
"type": "string",
|
|
164
|
+
"enum": [
|
|
165
|
+
"joint-opacity",
|
|
166
|
+
"joint-radius",
|
|
167
|
+
"bone-opacity",
|
|
168
|
+
"bone-width"
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
"STATE": {
|
|
172
|
+
"type": "string",
|
|
173
|
+
"enum": ["on", "off"]
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"setAccumulatedPoseParameters": {
|
|
178
|
+
"type": "object",
|
|
179
|
+
"properties": {
|
|
180
|
+
"ACCUMULATION": {
|
|
181
|
+
"type": "number",
|
|
182
|
+
"$comment": "New finite numeric input uses NP-finite-new-input-v1. Existing raw is preserved on no-op; runtime clamp/floor is not applied to stored raw."
|
|
183
|
+
},
|
|
184
|
+
"DECAY": {
|
|
185
|
+
"type": "number",
|
|
186
|
+
"$comment": "New finite numeric input uses NP-finite-new-input-v1. Existing raw is preserved on no-op; runtime clamp/floor is not applied to stored raw."
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"setAccumulatedPoseThreshold": {
|
|
191
|
+
"type": "object",
|
|
192
|
+
"properties": { "THRESHOLD": {
|
|
193
|
+
"type": "number",
|
|
194
|
+
"$comment": "New finite numeric input uses NP-finite-new-input-v1. Existing raw is preserved on no-op; runtime clamp/floor is not applied to stored raw."
|
|
195
|
+
} }
|
|
196
|
+
},
|
|
197
|
+
"isPoseWithThreshold": {
|
|
198
|
+
"type": "object",
|
|
199
|
+
"properties": { "THRESHOLD": {
|
|
200
|
+
"type": "number",
|
|
201
|
+
"$comment": "New finite numeric input uses NP-finite-new-input-v1. Existing raw is preserved on no-op; runtime clamp/floor is not applied to stored raw."
|
|
202
|
+
} }
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/fixed-input-schema.ts
|
|
208
|
+
/** Owner canonical fragments, distinct from older SOURCE/editor schemas. */
|
|
209
|
+
function fixedInputBundle(opcode, argument) {
|
|
210
|
+
const source = {
|
|
211
|
+
repository: "turbowarp-teachable-machine",
|
|
212
|
+
path: "schemas/block-editor-fixed-inputs.schema.json",
|
|
213
|
+
symbol: `#/$defs/${opcode}/properties/${argument}`
|
|
214
|
+
};
|
|
215
|
+
const fragment = block_editor_fixed_inputs_schema_default.$defs[opcode].properties[argument];
|
|
216
|
+
return {
|
|
217
|
+
identity: {
|
|
218
|
+
ownerId: "turbowarp-teachable-machine",
|
|
219
|
+
contractId: "fixed-inputs",
|
|
220
|
+
revision: "1",
|
|
221
|
+
variantId: `${opcode}.${argument}`
|
|
222
|
+
},
|
|
223
|
+
dialect: "2020-12",
|
|
224
|
+
root: {
|
|
225
|
+
retrievalUri: block_editor_fixed_inputs_schema_default.$id,
|
|
226
|
+
document: fragment,
|
|
227
|
+
artifact: {
|
|
228
|
+
role: "canonical-schema",
|
|
229
|
+
source
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
resources: [],
|
|
233
|
+
provenance: {
|
|
234
|
+
canonicalSources: [source, {
|
|
235
|
+
repository: "turbowarp-teachable-machine",
|
|
236
|
+
path: "src/extension.ts",
|
|
237
|
+
symbol: opcode
|
|
238
|
+
}],
|
|
239
|
+
generatedFiles: [],
|
|
240
|
+
referenceSchemas: [],
|
|
241
|
+
generationSources: []
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
block_editor_fixed_inputs_schema_default.$defs;
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region src/fixed-input-editors.ts
|
|
248
|
+
var existingNumber = (value) => {
|
|
249
|
+
if (typeof value !== "number" && typeof value !== "string" || typeof value === "string" && value.trim() === "") throw new TypeError("Expected a numeric literal.");
|
|
250
|
+
const number = Number(value);
|
|
251
|
+
if (!Number.isFinite(number)) throw new TypeError("Expected a finite number.");
|
|
252
|
+
return number;
|
|
253
|
+
};
|
|
254
|
+
var newNumber = (value) => {
|
|
255
|
+
const parsed = parseFixedNumberInput(value);
|
|
256
|
+
if (parsed.kind !== "valid") throw new TypeError("Invalid new numeric input.");
|
|
257
|
+
return parsed.token;
|
|
258
|
+
};
|
|
259
|
+
var aliases = {
|
|
260
|
+
"setRecognitionMode.MODE": {
|
|
261
|
+
"pose": "pose",
|
|
262
|
+
"poses": "pose",
|
|
263
|
+
"body": "pose",
|
|
264
|
+
"image": "image",
|
|
265
|
+
"images": "image",
|
|
266
|
+
"object": "image",
|
|
267
|
+
"card": "image",
|
|
268
|
+
"marker": "image",
|
|
269
|
+
"audio": "audio",
|
|
270
|
+
"sound": "audio",
|
|
271
|
+
"voice": "audio",
|
|
272
|
+
"microphone": "audio",
|
|
273
|
+
"mic": "audio",
|
|
274
|
+
"ポーズ": "pose",
|
|
275
|
+
"姿勢": "pose",
|
|
276
|
+
"画像": "image",
|
|
277
|
+
"物体": "image",
|
|
278
|
+
"カード": "image",
|
|
279
|
+
"記号": "image",
|
|
280
|
+
"音声": "audio",
|
|
281
|
+
"音": "audio",
|
|
282
|
+
"声": "audio",
|
|
283
|
+
"マイク": "audio"
|
|
284
|
+
},
|
|
285
|
+
"setPreviewPosition.POSITION": {
|
|
286
|
+
"左上": "top-left",
|
|
287
|
+
"右上": "top-right",
|
|
288
|
+
"左下": "bottom-left",
|
|
289
|
+
"右下": "bottom-right",
|
|
290
|
+
"中央": "center",
|
|
291
|
+
"ステージ全体": "full-stage",
|
|
292
|
+
"top-left": "top-left",
|
|
293
|
+
"top-right": "top-right",
|
|
294
|
+
"bottom-left": "bottom-left",
|
|
295
|
+
"bottom-right": "bottom-right",
|
|
296
|
+
"center": "center",
|
|
297
|
+
"full-stage": "full-stage"
|
|
298
|
+
},
|
|
299
|
+
"setPreviewMirroring.MIRRORING": {
|
|
300
|
+
"mirrored": "mirrored",
|
|
301
|
+
"unmirrored": "unmirrored",
|
|
302
|
+
"mirror": "mirrored",
|
|
303
|
+
"normal": "unmirrored",
|
|
304
|
+
"左右反転": "mirrored",
|
|
305
|
+
"そのまま": "unmirrored"
|
|
306
|
+
},
|
|
307
|
+
"setPoseOverlayVisibility.VISIBILITY": {
|
|
308
|
+
"on": "on",
|
|
309
|
+
"off": "off",
|
|
310
|
+
"show": "on",
|
|
311
|
+
"hide": "off",
|
|
312
|
+
"true": "on",
|
|
313
|
+
"false": "off",
|
|
314
|
+
"表示": "on",
|
|
315
|
+
"非表示": "off"
|
|
316
|
+
},
|
|
317
|
+
"setPoseConfidenceScaling.STATE": {
|
|
318
|
+
"on": "on",
|
|
319
|
+
"off": "off",
|
|
320
|
+
"show": "on",
|
|
321
|
+
"hide": "off",
|
|
322
|
+
"true": "on",
|
|
323
|
+
"false": "off",
|
|
324
|
+
"表示": "on",
|
|
325
|
+
"非表示": "off"
|
|
326
|
+
},
|
|
327
|
+
"setComputeMode.MODE": {
|
|
328
|
+
"auto": "auto",
|
|
329
|
+
"automatic": "auto",
|
|
330
|
+
"default": "auto",
|
|
331
|
+
"fastest": "auto",
|
|
332
|
+
"自動": "auto",
|
|
333
|
+
"おまかせ": "auto",
|
|
334
|
+
"webgpu": "webgpu",
|
|
335
|
+
"gpu": "webgpu",
|
|
336
|
+
"webgl": "webgl",
|
|
337
|
+
"wasm": "wasm",
|
|
338
|
+
"webassembly": "wasm",
|
|
339
|
+
"simd": "wasm",
|
|
340
|
+
"cpu": "cpu",
|
|
341
|
+
"javascript": "cpu"
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
function binding(opcode, argument, values) {
|
|
345
|
+
return {
|
|
346
|
+
argument,
|
|
347
|
+
schemaBundle: fixedInputBundle(opcode, argument),
|
|
348
|
+
control: values ? {
|
|
349
|
+
kind: "enum",
|
|
350
|
+
choices: values.map((value) => ({ value }))
|
|
351
|
+
} : { kind: "number" },
|
|
352
|
+
read(value) {
|
|
353
|
+
if (!values) return existingNumber(value);
|
|
354
|
+
if (typeof value !== "string") throw new TypeError("Expected a string.");
|
|
355
|
+
return projectEnum(opcode, argument, value);
|
|
356
|
+
},
|
|
357
|
+
encode(value) {
|
|
358
|
+
if (!values) return newNumber(value);
|
|
359
|
+
if (typeof value !== "string" || !values.includes(value)) throw new TypeError("Invalid selection.");
|
|
360
|
+
return value;
|
|
361
|
+
},
|
|
362
|
+
validateCandidate(value) {
|
|
363
|
+
try {
|
|
364
|
+
if (!values) existingNumber(value);
|
|
365
|
+
else if (typeof value !== "string" || !values.includes(value)) throw new TypeError("Invalid selection.");
|
|
366
|
+
return [];
|
|
367
|
+
} catch {
|
|
368
|
+
return [{ message: "Invalid fixed input." }];
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function projectEnum(opcode, argument, value) {
|
|
374
|
+
const key = `${opcode}.${argument}`;
|
|
375
|
+
const token = argument === "POSITION" ? value : value.trim().toLowerCase();
|
|
376
|
+
return aliases[key]?.[token] ?? value;
|
|
377
|
+
}
|
|
378
|
+
var fixedInputEditors = [
|
|
379
|
+
{
|
|
380
|
+
apiVersion: 1,
|
|
381
|
+
extensionId: "kubohiroyatm",
|
|
382
|
+
editorId: "fixed-setRecognitionMode",
|
|
383
|
+
opcodes: ["kubohiroyatm_setRecognitionMode"],
|
|
384
|
+
contractId: "turbowarp-teachable-machine/setRecognitionMode",
|
|
385
|
+
contractRevision: "1",
|
|
386
|
+
validationContext: () => ({
|
|
387
|
+
consumerId: "turbowarp-teachable-machine",
|
|
388
|
+
nowMs: 0,
|
|
389
|
+
settings: null
|
|
390
|
+
}),
|
|
391
|
+
bindings: [binding("setRecognitionMode", "MODE", [
|
|
392
|
+
"pose",
|
|
393
|
+
"image",
|
|
394
|
+
"audio"
|
|
395
|
+
])]
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
apiVersion: 1,
|
|
399
|
+
extensionId: "kubohiroyatm",
|
|
400
|
+
editorId: "fixed-setComputeMode",
|
|
401
|
+
opcodes: ["kubohiroyatm_setComputeMode"],
|
|
402
|
+
contractId: "turbowarp-teachable-machine/setComputeMode",
|
|
403
|
+
contractRevision: "1",
|
|
404
|
+
validationContext: () => ({
|
|
405
|
+
consumerId: "turbowarp-teachable-machine",
|
|
406
|
+
nowMs: 0,
|
|
407
|
+
settings: null
|
|
408
|
+
}),
|
|
409
|
+
bindings: [binding("setComputeMode", "MODE", [
|
|
410
|
+
"auto",
|
|
411
|
+
"webgpu",
|
|
412
|
+
"webgl",
|
|
413
|
+
"wasm",
|
|
414
|
+
"cpu"
|
|
415
|
+
])]
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
apiVersion: 1,
|
|
419
|
+
extensionId: "kubohiroyatm",
|
|
420
|
+
editorId: "fixed-setPreviewOpacity",
|
|
421
|
+
opcodes: ["kubohiroyatm_setPreviewOpacity"],
|
|
422
|
+
contractId: "turbowarp-teachable-machine/setPreviewOpacity",
|
|
423
|
+
contractRevision: "1",
|
|
424
|
+
validationContext: () => ({
|
|
425
|
+
consumerId: "turbowarp-teachable-machine",
|
|
426
|
+
nowMs: 0,
|
|
427
|
+
settings: null
|
|
428
|
+
}),
|
|
429
|
+
bindings: [binding("setPreviewOpacity", "OPACITY")]
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
apiVersion: 1,
|
|
433
|
+
extensionId: "kubohiroyatm",
|
|
434
|
+
editorId: "fixed-setPreviewPosition",
|
|
435
|
+
opcodes: ["kubohiroyatm_setPreviewPosition"],
|
|
436
|
+
contractId: "turbowarp-teachable-machine/setPreviewPosition",
|
|
437
|
+
contractRevision: "1",
|
|
438
|
+
validationContext: () => ({
|
|
439
|
+
consumerId: "turbowarp-teachable-machine",
|
|
440
|
+
nowMs: 0,
|
|
441
|
+
settings: null
|
|
442
|
+
}),
|
|
443
|
+
bindings: [binding("setPreviewPosition", "POSITION", [
|
|
444
|
+
"top-left",
|
|
445
|
+
"top-right",
|
|
446
|
+
"bottom-left",
|
|
447
|
+
"bottom-right",
|
|
448
|
+
"center",
|
|
449
|
+
"full-stage"
|
|
450
|
+
])]
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
apiVersion: 1,
|
|
454
|
+
extensionId: "kubohiroyatm",
|
|
455
|
+
editorId: "fixed-setPreviewMirroring",
|
|
456
|
+
opcodes: ["kubohiroyatm_setPreviewMirroring"],
|
|
457
|
+
contractId: "turbowarp-teachable-machine/setPreviewMirroring",
|
|
458
|
+
contractRevision: "1",
|
|
459
|
+
validationContext: () => ({
|
|
460
|
+
consumerId: "turbowarp-teachable-machine",
|
|
461
|
+
nowMs: 0,
|
|
462
|
+
settings: null
|
|
463
|
+
}),
|
|
464
|
+
bindings: [binding("setPreviewMirroring", "MIRRORING", ["mirrored", "unmirrored"])]
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
apiVersion: 1,
|
|
468
|
+
extensionId: "kubohiroyatm",
|
|
469
|
+
editorId: "fixed-setPoseOverlayVisibility",
|
|
470
|
+
opcodes: ["kubohiroyatm_setPoseOverlayVisibility"],
|
|
471
|
+
contractId: "turbowarp-teachable-machine/setPoseOverlayVisibility",
|
|
472
|
+
contractRevision: "1",
|
|
473
|
+
validationContext: () => ({
|
|
474
|
+
consumerId: "turbowarp-teachable-machine",
|
|
475
|
+
nowMs: 0,
|
|
476
|
+
settings: null
|
|
477
|
+
}),
|
|
478
|
+
bindings: [binding("setPoseOverlayVisibility", "VISIBILITY", ["on", "off"])]
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
apiVersion: 1,
|
|
482
|
+
extensionId: "kubohiroyatm",
|
|
483
|
+
editorId: "fixed-setPoseJointStyle",
|
|
484
|
+
opcodes: ["kubohiroyatm_setPoseJointStyle"],
|
|
485
|
+
contractId: "turbowarp-teachable-machine/setPoseJointStyle",
|
|
486
|
+
contractRevision: "1",
|
|
487
|
+
validationContext: () => ({
|
|
488
|
+
consumerId: "turbowarp-teachable-machine",
|
|
489
|
+
nowMs: 0,
|
|
490
|
+
settings: null
|
|
491
|
+
}),
|
|
492
|
+
bindings: [
|
|
493
|
+
binding("setPoseJointStyle", "PART", [
|
|
494
|
+
"nose",
|
|
495
|
+
"leftEye",
|
|
496
|
+
"rightEye",
|
|
497
|
+
"leftEar",
|
|
498
|
+
"rightEar",
|
|
499
|
+
"leftShoulder",
|
|
500
|
+
"rightShoulder",
|
|
501
|
+
"leftElbow",
|
|
502
|
+
"rightElbow",
|
|
503
|
+
"leftWrist",
|
|
504
|
+
"rightWrist",
|
|
505
|
+
"leftHip",
|
|
506
|
+
"rightHip",
|
|
507
|
+
"leftKnee",
|
|
508
|
+
"rightKnee",
|
|
509
|
+
"leftAnkle",
|
|
510
|
+
"rightAnkle"
|
|
511
|
+
]),
|
|
512
|
+
binding("setPoseJointStyle", "OPACITY"),
|
|
513
|
+
binding("setPoseJointStyle", "RADIUS")
|
|
514
|
+
]
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
apiVersion: 1,
|
|
518
|
+
extensionId: "kubohiroyatm",
|
|
519
|
+
editorId: "fixed-setPoseBoneStyle",
|
|
520
|
+
opcodes: ["kubohiroyatm_setPoseBoneStyle"],
|
|
521
|
+
contractId: "turbowarp-teachable-machine/setPoseBoneStyle",
|
|
522
|
+
contractRevision: "1",
|
|
523
|
+
validationContext: () => ({
|
|
524
|
+
consumerId: "turbowarp-teachable-machine",
|
|
525
|
+
nowMs: 0,
|
|
526
|
+
settings: null
|
|
527
|
+
}),
|
|
528
|
+
bindings: [binding("setPoseBoneStyle", "OPACITY"), binding("setPoseBoneStyle", "WIDTH")]
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
apiVersion: 1,
|
|
532
|
+
extensionId: "kubohiroyatm",
|
|
533
|
+
editorId: "fixed-setPoseOverlayMinimumConfidence",
|
|
534
|
+
opcodes: ["kubohiroyatm_setPoseOverlayMinimumConfidence"],
|
|
535
|
+
contractId: "turbowarp-teachable-machine/setPoseOverlayMinimumConfidence",
|
|
536
|
+
contractRevision: "1",
|
|
537
|
+
validationContext: () => ({
|
|
538
|
+
consumerId: "turbowarp-teachable-machine",
|
|
539
|
+
nowMs: 0,
|
|
540
|
+
settings: null
|
|
541
|
+
}),
|
|
542
|
+
bindings: [binding("setPoseOverlayMinimumConfidence", "CONFIDENCE")]
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
apiVersion: 1,
|
|
546
|
+
extensionId: "kubohiroyatm",
|
|
547
|
+
editorId: "fixed-setPoseConfidenceScaling",
|
|
548
|
+
opcodes: ["kubohiroyatm_setPoseConfidenceScaling"],
|
|
549
|
+
contractId: "turbowarp-teachable-machine/setPoseConfidenceScaling",
|
|
550
|
+
contractRevision: "1",
|
|
551
|
+
validationContext: () => ({
|
|
552
|
+
consumerId: "turbowarp-teachable-machine",
|
|
553
|
+
nowMs: 0,
|
|
554
|
+
settings: null
|
|
555
|
+
}),
|
|
556
|
+
bindings: [binding("setPoseConfidenceScaling", "PROPERTY", [
|
|
557
|
+
"joint-opacity",
|
|
558
|
+
"joint-radius",
|
|
559
|
+
"bone-opacity",
|
|
560
|
+
"bone-width"
|
|
561
|
+
]), binding("setPoseConfidenceScaling", "STATE", ["on", "off"])]
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
apiVersion: 1,
|
|
565
|
+
extensionId: "kubohiroyatm",
|
|
566
|
+
editorId: "fixed-setAccumulatedPoseParameters",
|
|
567
|
+
opcodes: ["kubohiroyatm_setAccumulatedPoseParameters"],
|
|
568
|
+
contractId: "turbowarp-teachable-machine/setAccumulatedPoseParameters",
|
|
569
|
+
contractRevision: "1",
|
|
570
|
+
validationContext: () => ({
|
|
571
|
+
consumerId: "turbowarp-teachable-machine",
|
|
572
|
+
nowMs: 0,
|
|
573
|
+
settings: null
|
|
574
|
+
}),
|
|
575
|
+
bindings: [binding("setAccumulatedPoseParameters", "ACCUMULATION"), binding("setAccumulatedPoseParameters", "DECAY")]
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
apiVersion: 1,
|
|
579
|
+
extensionId: "kubohiroyatm",
|
|
580
|
+
editorId: "fixed-setAccumulatedPoseThreshold",
|
|
581
|
+
opcodes: ["kubohiroyatm_setAccumulatedPoseThreshold"],
|
|
582
|
+
contractId: "turbowarp-teachable-machine/setAccumulatedPoseThreshold",
|
|
583
|
+
contractRevision: "1",
|
|
584
|
+
validationContext: () => ({
|
|
585
|
+
consumerId: "turbowarp-teachable-machine",
|
|
586
|
+
nowMs: 0,
|
|
587
|
+
settings: null
|
|
588
|
+
}),
|
|
589
|
+
bindings: [binding("setAccumulatedPoseThreshold", "THRESHOLD")]
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
apiVersion: 1,
|
|
593
|
+
extensionId: "kubohiroyatm",
|
|
594
|
+
editorId: "fixed-isPoseWithThreshold",
|
|
595
|
+
opcodes: ["kubohiroyatm_isPoseWithThreshold"],
|
|
596
|
+
contractId: "turbowarp-teachable-machine/isPoseWithThreshold",
|
|
597
|
+
contractRevision: "1",
|
|
598
|
+
validationContext: () => ({
|
|
599
|
+
consumerId: "turbowarp-teachable-machine",
|
|
600
|
+
nowMs: 0,
|
|
601
|
+
settings: null
|
|
602
|
+
}),
|
|
603
|
+
bindings: [binding("isPoseWithThreshold", "THRESHOLD")]
|
|
604
|
+
}
|
|
605
|
+
];
|
|
606
|
+
/** Register only currently declared opcodes; release obsolete feature-flag scopes. */
|
|
607
|
+
function fixedInputRegistration(scratch) {
|
|
608
|
+
const host = typeof scratch === "object" && scratch !== null ? scratch : {};
|
|
609
|
+
let active;
|
|
610
|
+
let activeOpcodes = "";
|
|
611
|
+
const removers = [];
|
|
612
|
+
let disposed = false;
|
|
613
|
+
const dispose = () => {
|
|
614
|
+
host.vm?.runtime?.off?.("RUNTIME_DISPOSED", shutdown);
|
|
615
|
+
for (const remove of removers.splice(0).reverse()) remove();
|
|
616
|
+
active = void 0;
|
|
617
|
+
activeOpcodes = "";
|
|
618
|
+
};
|
|
619
|
+
const shutdown = () => {
|
|
620
|
+
disposed = true;
|
|
621
|
+
dispose();
|
|
622
|
+
};
|
|
623
|
+
return {
|
|
624
|
+
ensure(opcodes) {
|
|
625
|
+
const api = host.gui?.blockEditors;
|
|
626
|
+
const signature = [...new Set(opcodes)].sort().join("\0");
|
|
627
|
+
if (disposed || api === active && signature === activeOpcodes) return;
|
|
628
|
+
dispose();
|
|
629
|
+
if (api?.apiVersion !== 1 || typeof api.registerFixedInputEditor !== "function") return;
|
|
630
|
+
try {
|
|
631
|
+
for (const definition of fixedInputEditors) if (definition.opcodes.every((opcode) => opcodes.includes(opcode))) {
|
|
632
|
+
const remove = api.registerFixedInputEditor.call(api, definition);
|
|
633
|
+
if (typeof remove !== "function") throw new TypeError("Invalid fixed registration cleanup.");
|
|
634
|
+
removers.push(remove);
|
|
635
|
+
}
|
|
636
|
+
active = api;
|
|
637
|
+
activeOpcodes = signature;
|
|
638
|
+
host.vm?.runtime?.on?.("RUNTIME_DISPOSED", shutdown);
|
|
639
|
+
} catch (error) {
|
|
640
|
+
dispose();
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
dispose: shutdown
|
|
645
|
+
};
|
|
646
|
+
}
|
|
10
647
|
var block_definitions_default = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
648
|
+
extensionName: "Teachable Machine",
|
|
649
|
+
blocks: [
|
|
650
|
+
{
|
|
651
|
+
"opcode": "versionReporter",
|
|
652
|
+
"blockType": "REPORTER",
|
|
653
|
+
"text": "Teachable Machine version",
|
|
654
|
+
"description": "Returns the extension version."
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
"opcode": "setRecognitionMode",
|
|
658
|
+
"blockType": "COMMAND",
|
|
659
|
+
"text": "set recognition mode to [MODE]",
|
|
660
|
+
"description": "Selects whether the current Teachable Machine model recognizes poses, images, or audio.",
|
|
661
|
+
"arguments": { "MODE": {
|
|
662
|
+
"type": "STRING",
|
|
663
|
+
"menu": "recognitionModeMenu",
|
|
664
|
+
"defaultValue": "pose"
|
|
665
|
+
} }
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
"opcode": "recognitionModeReporter",
|
|
669
|
+
"blockType": "REPORTER",
|
|
670
|
+
"text": "recognition mode",
|
|
671
|
+
"description": "Returns the current recognition mode."
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
"opcode": "setComputeMode",
|
|
675
|
+
"blockType": "COMMAND",
|
|
676
|
+
"text": "set compute mode to [MODE]",
|
|
677
|
+
"description": "Selects the TensorFlow.js compute backend. \"auto\" negotiates WebGPU, WebGL, WASM, and CPU in that order and falls back automatically when a backend is unusable. Changing the mode releases a model that was loaded from a URL so it can be reloaded on the new backend.",
|
|
678
|
+
"arguments": { "MODE": {
|
|
679
|
+
"type": "STRING",
|
|
680
|
+
"menu": "computeModeMenu",
|
|
681
|
+
"defaultValue": "auto"
|
|
682
|
+
} }
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
"opcode": "computeModeReporter",
|
|
686
|
+
"blockType": "REPORTER",
|
|
687
|
+
"text": "compute mode",
|
|
688
|
+
"description": "Returns the requested compute mode."
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
"opcode": "computeBackendReporter",
|
|
692
|
+
"blockType": "REPORTER",
|
|
693
|
+
"text": "active compute backend",
|
|
694
|
+
"description": "Returns the TensorFlow.js backend recognition is actually running on, which can differ from the requested mode when the browser could not provide it."
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
"opcode": "setModelURL",
|
|
698
|
+
"blockType": "COMMAND",
|
|
699
|
+
"text": "set model URL to [URL]",
|
|
700
|
+
"description": "Sets the Teachable Machine model URL.",
|
|
701
|
+
"arguments": { "URL": {
|
|
702
|
+
"type": "STRING",
|
|
703
|
+
"defaultValue": "https://teachablemachine.withgoogle.com/models/XXXX/"
|
|
704
|
+
} }
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
"opcode": "startCamera",
|
|
708
|
+
"blockType": "COMMAND",
|
|
709
|
+
"text": "start camera",
|
|
710
|
+
"description": "Starts the camera and attaches the preview."
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
"opcode": "stopCamera",
|
|
714
|
+
"blockType": "COMMAND",
|
|
715
|
+
"text": "stop camera",
|
|
716
|
+
"description": "Stops the camera and recognition loop."
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
"opcode": "isCameraRunning",
|
|
720
|
+
"blockType": "BOOLEAN",
|
|
721
|
+
"text": "camera is running?",
|
|
722
|
+
"description": "Reports whether the camera is running."
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
"opcode": "refreshCameraList",
|
|
726
|
+
"blockType": "COMMAND",
|
|
727
|
+
"text": "refresh camera list",
|
|
728
|
+
"description": "Refreshes the list of available video input devices."
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
"opcode": "setCameraSelection",
|
|
732
|
+
"blockType": "COMMAND",
|
|
733
|
+
"text": "set camera to [CAMERA]",
|
|
734
|
+
"description": "Selects the default, front, back, or a detected camera and switches a running camera.",
|
|
735
|
+
"arguments": { "CAMERA": {
|
|
736
|
+
"type": "STRING",
|
|
737
|
+
"menu": "cameraMenu",
|
|
738
|
+
"defaultValue": "default"
|
|
739
|
+
} }
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
"opcode": "cameraCountReporter",
|
|
743
|
+
"blockType": "REPORTER",
|
|
744
|
+
"text": "camera count",
|
|
745
|
+
"description": "Returns the number of video input devices found by the latest refresh."
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
"opcode": "cameraDeviceIdReporter",
|
|
749
|
+
"blockType": "REPORTER",
|
|
750
|
+
"text": "camera device ID",
|
|
751
|
+
"description": "Returns the active camera device ID when available."
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
"opcode": "cameraDeviceNameReporter",
|
|
755
|
+
"blockType": "REPORTER",
|
|
756
|
+
"text": "camera device name",
|
|
757
|
+
"description": "Returns the active camera device name when available."
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
"opcode": "showPreview",
|
|
761
|
+
"blockType": "COMMAND",
|
|
762
|
+
"text": "show camera preview",
|
|
763
|
+
"description": "Shows the camera preview."
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
"opcode": "hidePreview",
|
|
767
|
+
"blockType": "COMMAND",
|
|
768
|
+
"text": "hide camera preview",
|
|
769
|
+
"description": "Hides the camera preview."
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
"opcode": "isPreviewVisible",
|
|
773
|
+
"blockType": "BOOLEAN",
|
|
774
|
+
"text": "camera preview is visible?",
|
|
775
|
+
"description": "Reports whether the preview is configured as visible."
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
"opcode": "setPreviewOpacity",
|
|
779
|
+
"blockType": "COMMAND",
|
|
780
|
+
"text": "set camera preview opacity to [OPACITY]",
|
|
781
|
+
"description": "Sets preview opacity from 0 to 1.",
|
|
782
|
+
"arguments": { "OPACITY": {
|
|
783
|
+
"type": "NUMBER",
|
|
784
|
+
"defaultValue": .6
|
|
785
|
+
} }
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
"opcode": "setPreviewPosition",
|
|
789
|
+
"blockType": "COMMAND",
|
|
790
|
+
"text": "set camera preview position to [POSITION]",
|
|
791
|
+
"description": "Sets the preview position on the stage.",
|
|
792
|
+
"arguments": { "POSITION": {
|
|
793
|
+
"type": "STRING",
|
|
794
|
+
"menu": "positionMenu",
|
|
795
|
+
"defaultValue": "bottom-right"
|
|
796
|
+
} }
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
"opcode": "setPreviewMirroring",
|
|
800
|
+
"blockType": "COMMAND",
|
|
801
|
+
"text": "set camera preview to [MIRRORING]",
|
|
802
|
+
"description": "Sets whether the preview is mirrored without changing the recognition input.",
|
|
803
|
+
"arguments": { "MIRRORING": {
|
|
804
|
+
"type": "STRING",
|
|
805
|
+
"menu": "previewMirroringMenu",
|
|
806
|
+
"defaultValue": "mirrored"
|
|
807
|
+
} }
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
"opcode": "previewMirroringReporter",
|
|
811
|
+
"blockType": "REPORTER",
|
|
812
|
+
"text": "camera preview mirroring",
|
|
813
|
+
"description": "Returns mirrored or unmirrored for the current preview setting."
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
"opcode": "setPoseOverlayVisibility",
|
|
817
|
+
"blockType": "COMMAND",
|
|
818
|
+
"text": "set pose overlay [VISIBILITY]",
|
|
819
|
+
"description": "Shows or hides the SVG pose overlay without stopping recognition.",
|
|
820
|
+
"featureFlag": "poseOverlay",
|
|
821
|
+
"arguments": { "VISIBILITY": {
|
|
822
|
+
"type": "STRING",
|
|
823
|
+
"menu": "poseOverlayVisibilityMenu",
|
|
824
|
+
"defaultValue": "on"
|
|
825
|
+
} }
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
"opcode": "isPoseOverlayVisible",
|
|
829
|
+
"blockType": "BOOLEAN",
|
|
830
|
+
"text": "pose overlay is visible?",
|
|
831
|
+
"description": "Reports whether the SVG pose overlay is configured as visible.",
|
|
832
|
+
"featureFlag": "poseOverlay"
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
"opcode": "setPoseJointStyle",
|
|
836
|
+
"blockType": "COMMAND",
|
|
837
|
+
"text": "set [PART] joint color [COLOR] opacity [OPACITY] radius [RADIUS]",
|
|
838
|
+
"description": "Sets the SVG circle style for one PoseNet joint.",
|
|
839
|
+
"featureFlag": "poseOverlay",
|
|
840
|
+
"arguments": {
|
|
841
|
+
"PART": {
|
|
842
|
+
"type": "STRING",
|
|
843
|
+
"menu": "poseKeypointMenu",
|
|
844
|
+
"defaultValue": "nose"
|
|
845
|
+
},
|
|
846
|
+
"COLOR": {
|
|
847
|
+
"type": "STRING",
|
|
848
|
+
"defaultValue": "#00e5ff"
|
|
849
|
+
},
|
|
850
|
+
"OPACITY": {
|
|
851
|
+
"type": "NUMBER",
|
|
852
|
+
"defaultValue": 1
|
|
853
|
+
},
|
|
854
|
+
"RADIUS": {
|
|
855
|
+
"type": "NUMBER",
|
|
856
|
+
"defaultValue": 4
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
"opcode": "setPoseBoneStyle",
|
|
862
|
+
"blockType": "COMMAND",
|
|
863
|
+
"text": "set pose bone color [COLOR] opacity [OPACITY] width [WIDTH]",
|
|
864
|
+
"description": "Sets the color, opacity, and line width for all SVG pose bones.",
|
|
865
|
+
"featureFlag": "poseOverlay",
|
|
866
|
+
"arguments": {
|
|
867
|
+
"COLOR": {
|
|
868
|
+
"type": "STRING",
|
|
869
|
+
"defaultValue": "#00e5ff"
|
|
870
|
+
},
|
|
871
|
+
"OPACITY": {
|
|
872
|
+
"type": "NUMBER",
|
|
873
|
+
"defaultValue": .9
|
|
874
|
+
},
|
|
875
|
+
"WIDTH": {
|
|
876
|
+
"type": "NUMBER",
|
|
877
|
+
"defaultValue": 3
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
"opcode": "setPoseOverlayMinimumConfidence",
|
|
883
|
+
"blockType": "COMMAND",
|
|
884
|
+
"text": "set pose overlay minimum confidence to [CONFIDENCE]",
|
|
885
|
+
"description": "Hides joints and bones whose keypoint confidence is below the given value.",
|
|
886
|
+
"featureFlag": "poseOverlay",
|
|
887
|
+
"arguments": { "CONFIDENCE": {
|
|
888
|
+
"type": "NUMBER",
|
|
889
|
+
"defaultValue": .5
|
|
890
|
+
} }
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
"opcode": "setPoseConfidenceScaling",
|
|
894
|
+
"blockType": "COMMAND",
|
|
895
|
+
"text": "set pose [PROPERTY] confidence scaling [STATE]",
|
|
896
|
+
"description": "Scales the selected SVG style property from zero to its configured value using keypoint confidence.",
|
|
897
|
+
"featureFlag": "poseOverlay",
|
|
898
|
+
"arguments": {
|
|
899
|
+
"PROPERTY": {
|
|
900
|
+
"type": "STRING",
|
|
901
|
+
"menu": "poseConfidencePropertyMenu",
|
|
902
|
+
"defaultValue": "joint-opacity"
|
|
903
|
+
},
|
|
904
|
+
"STATE": {
|
|
905
|
+
"type": "STRING",
|
|
906
|
+
"menu": "poseOverlayVisibilityMenu",
|
|
907
|
+
"defaultValue": "off"
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
"opcode": "loadModel",
|
|
913
|
+
"blockType": "COMMAND",
|
|
914
|
+
"text": "load model",
|
|
915
|
+
"description": "Loads the configured Teachable Machine model."
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
"opcode": "isModelLoaded",
|
|
919
|
+
"blockType": "BOOLEAN",
|
|
920
|
+
"text": "model is loaded?",
|
|
921
|
+
"description": "Reports whether the model is loaded."
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
"opcode": "startRecognition",
|
|
925
|
+
"blockType": "COMMAND",
|
|
926
|
+
"text": "start recognition",
|
|
927
|
+
"description": "Starts recognition."
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
"opcode": "stopRecognition",
|
|
931
|
+
"blockType": "COMMAND",
|
|
932
|
+
"text": "stop recognition",
|
|
933
|
+
"description": "Stops recognition."
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
"opcode": "isRecognizing",
|
|
937
|
+
"blockType": "BOOLEAN",
|
|
938
|
+
"text": "recognition is running?",
|
|
939
|
+
"description": "Reports whether recognition is running."
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
"opcode": "currentPoseReporter",
|
|
943
|
+
"blockType": "REPORTER",
|
|
944
|
+
"text": "current label",
|
|
945
|
+
"description": "Returns the highest-scoring label."
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
"opcode": "scoreReporter",
|
|
949
|
+
"blockType": "REPORTER",
|
|
950
|
+
"text": "confidence",
|
|
951
|
+
"description": "Returns the confidence of the current label."
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
"opcode": "poseScoreReporter",
|
|
955
|
+
"blockType": "REPORTER",
|
|
956
|
+
"text": "confidence of [NAME]",
|
|
957
|
+
"description": "Returns the confidence for a named label.",
|
|
958
|
+
"arguments": { "NAME": {
|
|
959
|
+
"type": "STRING",
|
|
960
|
+
"defaultValue": "jump"
|
|
961
|
+
} }
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
"opcode": "setAccumulatedPoseParameters",
|
|
965
|
+
"blockType": "COMMAND",
|
|
966
|
+
"text": "set accumulated pose accumulation [ACCUMULATION] decay [DECAY]",
|
|
967
|
+
"description": "Sets the accumulation rate per second and the decay retained per second; decay changes apply to the next recognition session.",
|
|
968
|
+
"featureFlag": "temporalPoseScoring",
|
|
969
|
+
"arguments": {
|
|
970
|
+
"ACCUMULATION": {
|
|
971
|
+
"type": "NUMBER",
|
|
972
|
+
"defaultValue": 1
|
|
973
|
+
},
|
|
974
|
+
"DECAY": {
|
|
975
|
+
"type": "NUMBER",
|
|
976
|
+
"defaultValue": .9
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
"opcode": "setAccumulatedPoseThreshold",
|
|
982
|
+
"blockType": "COMMAND",
|
|
983
|
+
"text": "set accumulated pose threshold [THRESHOLD]",
|
|
984
|
+
"description": "Sets the minimum accumulated score required to report a pose; values below the threshold report an empty string.",
|
|
985
|
+
"featureFlag": "temporalPoseScoring",
|
|
986
|
+
"arguments": { "THRESHOLD": {
|
|
987
|
+
"type": "NUMBER",
|
|
988
|
+
"defaultValue": 0
|
|
989
|
+
} }
|
|
990
|
+
},
|
|
991
|
+
{
|
|
992
|
+
"opcode": "resetAccumulatedPose",
|
|
993
|
+
"blockType": "COMMAND",
|
|
994
|
+
"text": "reset accumulated pose scores",
|
|
995
|
+
"description": "Clears all accumulated pose scores.",
|
|
996
|
+
"featureFlag": "temporalPoseScoring"
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
"opcode": "accumulatedPoseReporter",
|
|
1000
|
+
"blockType": "REPORTER",
|
|
1001
|
+
"text": "accumulated pose",
|
|
1002
|
+
"description": "Returns the pose label whose accumulated score is highest and meets the threshold, or an empty string otherwise.",
|
|
1003
|
+
"featureFlag": "temporalPoseScoring"
|
|
1004
|
+
},
|
|
1005
|
+
{
|
|
1006
|
+
"opcode": "accumulatedScoreReporter",
|
|
1007
|
+
"blockType": "REPORTER",
|
|
1008
|
+
"text": "accumulated score",
|
|
1009
|
+
"description": "Returns the highest accumulated pose score without rounding.",
|
|
1010
|
+
"featureFlag": "temporalPoseScoring"
|
|
1011
|
+
},
|
|
1012
|
+
{
|
|
1013
|
+
"opcode": "accumulatedPoseScoreReporter",
|
|
1014
|
+
"blockType": "REPORTER",
|
|
1015
|
+
"text": "accumulated score of [NAME]",
|
|
1016
|
+
"description": "Returns the accumulated score for a named pose without rounding.",
|
|
1017
|
+
"featureFlag": "temporalPoseScoring",
|
|
1018
|
+
"arguments": { "NAME": {
|
|
1019
|
+
"type": "STRING",
|
|
1020
|
+
"defaultValue": "jump"
|
|
1021
|
+
} }
|
|
1022
|
+
},
|
|
1023
|
+
{
|
|
1024
|
+
"opcode": "isPose",
|
|
1025
|
+
"blockType": "BOOLEAN",
|
|
1026
|
+
"text": "label is [NAME]?",
|
|
1027
|
+
"description": "Reports whether the named label has at least 0.75 confidence.",
|
|
1028
|
+
"arguments": { "NAME": {
|
|
1029
|
+
"type": "STRING",
|
|
1030
|
+
"defaultValue": "jump"
|
|
1031
|
+
} }
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
"opcode": "isPoseWithThreshold",
|
|
1035
|
+
"blockType": "BOOLEAN",
|
|
1036
|
+
"text": "label is [NAME] with confidence at least [THRESHOLD]?",
|
|
1037
|
+
"description": "Reports whether the named label meets the given threshold.",
|
|
1038
|
+
"arguments": {
|
|
1039
|
+
"NAME": {
|
|
1040
|
+
"type": "STRING",
|
|
1041
|
+
"defaultValue": "jump"
|
|
1042
|
+
},
|
|
1043
|
+
"THRESHOLD": {
|
|
1044
|
+
"type": "NUMBER",
|
|
1045
|
+
"defaultValue": .75
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
"opcode": "cameraMsReporter",
|
|
1051
|
+
"blockType": "REPORTER",
|
|
1052
|
+
"text": "camera startup time (ms)",
|
|
1053
|
+
"description": "Returns camera startup time in milliseconds."
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
"opcode": "modelLoadMsReporter",
|
|
1057
|
+
"blockType": "REPORTER",
|
|
1058
|
+
"text": "model load time (ms)",
|
|
1059
|
+
"description": "Returns model load time in milliseconds."
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
"opcode": "firstRecognitionMsReporter",
|
|
1063
|
+
"blockType": "REPORTER",
|
|
1064
|
+
"text": "first recognition time (ms)",
|
|
1065
|
+
"description": "Returns first recognition time in milliseconds."
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
"opcode": "lastErrorReporter",
|
|
1069
|
+
"blockType": "REPORTER",
|
|
1070
|
+
"text": "last error",
|
|
1071
|
+
"description": "Returns the latest recorded error message."
|
|
1072
|
+
}
|
|
1073
|
+
]
|
|
437
1074
|
};
|
|
438
1075
|
//#endregion
|
|
1076
|
+
//#region \0@oxc-project+runtime@0.148.0/helpers/esm/typeof.js
|
|
1077
|
+
function _typeof(o) {
|
|
1078
|
+
"@babel/helpers - typeof";
|
|
1079
|
+
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
1080
|
+
return typeof o;
|
|
1081
|
+
} : function(o) {
|
|
1082
|
+
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
|
|
1083
|
+
}, _typeof(o);
|
|
1084
|
+
}
|
|
1085
|
+
//#endregion
|
|
1086
|
+
//#region \0@oxc-project+runtime@0.148.0/helpers/esm/toPrimitive.js
|
|
1087
|
+
function toPrimitive(t, r) {
|
|
1088
|
+
if ("object" != _typeof(t) || !t) return t;
|
|
1089
|
+
var e = t[Symbol.toPrimitive];
|
|
1090
|
+
if (void 0 !== e) {
|
|
1091
|
+
var i = e.call(t, r || "default");
|
|
1092
|
+
if ("object" != _typeof(i)) return i;
|
|
1093
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
1094
|
+
}
|
|
1095
|
+
return ("string" === r ? String : Number)(t);
|
|
1096
|
+
}
|
|
1097
|
+
//#endregion
|
|
1098
|
+
//#region \0@oxc-project+runtime@0.148.0/helpers/esm/toPropertyKey.js
|
|
1099
|
+
function toPropertyKey(t) {
|
|
1100
|
+
var i = toPrimitive(t, "string");
|
|
1101
|
+
return "symbol" == _typeof(i) ? i : i + "";
|
|
1102
|
+
}
|
|
1103
|
+
//#endregion
|
|
1104
|
+
//#region \0@oxc-project+runtime@0.148.0/helpers/esm/defineProperty.js
|
|
1105
|
+
function _defineProperty(e, r, t) {
|
|
1106
|
+
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
1107
|
+
value: t,
|
|
1108
|
+
enumerable: !0,
|
|
1109
|
+
configurable: !0,
|
|
1110
|
+
writable: !0
|
|
1111
|
+
}) : e[r] = t, e;
|
|
1112
|
+
}
|
|
1113
|
+
//#endregion
|
|
439
1114
|
//#region src/compute-backend.ts
|
|
440
1115
|
/**
|
|
441
1116
|
* Compute backend negotiation for the reviewed TensorFlow.js runtime.
|
|
@@ -446,205 +1121,214 @@
|
|
|
446
1121
|
* free of any TensorFlow.js or DOM dependency so they stay testable.
|
|
447
1122
|
*/
|
|
448
1123
|
var COMPUTE_MODES = [
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
1124
|
+
"auto",
|
|
1125
|
+
"webgpu",
|
|
1126
|
+
"webgl",
|
|
1127
|
+
"wasm",
|
|
1128
|
+
"cpu"
|
|
454
1129
|
];
|
|
455
1130
|
Object.freeze([
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
1131
|
+
"webgpu",
|
|
1132
|
+
"webgl",
|
|
1133
|
+
"wasm",
|
|
1134
|
+
"cpu"
|
|
460
1135
|
]);
|
|
461
1136
|
var COMPUTE_MODE_ALIASES = Object.freeze({
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
1137
|
+
auto: "auto",
|
|
1138
|
+
automatic: "auto",
|
|
1139
|
+
default: "auto",
|
|
1140
|
+
fastest: "auto",
|
|
1141
|
+
自動: "auto",
|
|
1142
|
+
おまかせ: "auto",
|
|
1143
|
+
webgpu: "webgpu",
|
|
1144
|
+
gpu: "webgpu",
|
|
1145
|
+
webgl: "webgl",
|
|
1146
|
+
wasm: "wasm",
|
|
1147
|
+
webassembly: "wasm",
|
|
1148
|
+
simd: "wasm",
|
|
1149
|
+
cpu: "cpu",
|
|
1150
|
+
javascript: "cpu"
|
|
476
1151
|
});
|
|
477
1152
|
function normalizeComputeMode(value, fallback = "auto") {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
1153
|
+
const text = String(value ?? "").trim().toLowerCase();
|
|
1154
|
+
if (text === "") return fallback;
|
|
1155
|
+
return COMPUTE_MODE_ALIASES[text] ?? fallback;
|
|
481
1156
|
}
|
|
482
1157
|
//#endregion
|
|
483
1158
|
//#region src/config/feature-flags.ts
|
|
484
1159
|
var FEATURE_FLAGS = {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
1160
|
+
/**
|
|
1161
|
+
* Per-frame confidence flickers near a decision boundary, so the accumulated
|
|
1162
|
+
* score blocks — which integrate confidence over time and decay it — are the
|
|
1163
|
+
* built-in answer to a noisy recognition result. They are on by default; the
|
|
1164
|
+
* event bridge that republishes those changes to other extensions is not.
|
|
1165
|
+
*/
|
|
1166
|
+
temporalPoseScoring: true,
|
|
1167
|
+
accumulatedPoseEvents: false,
|
|
1168
|
+
poseOverlay: true
|
|
494
1169
|
};
|
|
495
1170
|
//#endregion
|
|
496
1171
|
//#region src/pose-overlay.ts
|
|
497
1172
|
var POSE_KEYPOINT_NAMES = [
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
1173
|
+
"nose",
|
|
1174
|
+
"leftEye",
|
|
1175
|
+
"rightEye",
|
|
1176
|
+
"leftEar",
|
|
1177
|
+
"rightEar",
|
|
1178
|
+
"leftShoulder",
|
|
1179
|
+
"rightShoulder",
|
|
1180
|
+
"leftElbow",
|
|
1181
|
+
"rightElbow",
|
|
1182
|
+
"leftWrist",
|
|
1183
|
+
"rightWrist",
|
|
1184
|
+
"leftHip",
|
|
1185
|
+
"rightHip",
|
|
1186
|
+
"leftKnee",
|
|
1187
|
+
"rightKnee",
|
|
1188
|
+
"leftAnkle",
|
|
1189
|
+
"rightAnkle"
|
|
515
1190
|
];
|
|
516
1191
|
var POSE_BONE_CONNECTIONS = Object.freeze([
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
1192
|
+
["leftHip", "leftShoulder"],
|
|
1193
|
+
["leftElbow", "leftShoulder"],
|
|
1194
|
+
["leftElbow", "leftWrist"],
|
|
1195
|
+
["leftHip", "leftKnee"],
|
|
1196
|
+
["leftKnee", "leftAnkle"],
|
|
1197
|
+
["rightHip", "rightShoulder"],
|
|
1198
|
+
["rightElbow", "rightShoulder"],
|
|
1199
|
+
["rightElbow", "rightWrist"],
|
|
1200
|
+
["rightHip", "rightKnee"],
|
|
1201
|
+
["rightKnee", "rightAnkle"],
|
|
1202
|
+
["leftShoulder", "rightShoulder"],
|
|
1203
|
+
["leftHip", "rightHip"]
|
|
529
1204
|
]);
|
|
530
1205
|
var DEFAULT_POSE_JOINT_STYLE = Object.freeze({
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
1206
|
+
color: "#00e5ff",
|
|
1207
|
+
opacity: 1,
|
|
1208
|
+
radius: 4
|
|
534
1209
|
});
|
|
535
1210
|
var DEFAULT_POSE_BONE_STYLE = Object.freeze({
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
1211
|
+
color: "#00e5ff",
|
|
1212
|
+
opacity: .9,
|
|
1213
|
+
width: 3
|
|
539
1214
|
});
|
|
540
1215
|
var DEFAULT_POSE_OVERLAY_CONFIDENCE_SCALING = Object.freeze({
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
1216
|
+
jointOpacity: false,
|
|
1217
|
+
jointRadius: false,
|
|
1218
|
+
boneOpacity: false,
|
|
1219
|
+
boneWidth: false
|
|
545
1220
|
});
|
|
546
1221
|
function isPoseKeypointName(value) {
|
|
547
|
-
|
|
1222
|
+
return POSE_KEYPOINT_NAMES.includes(value);
|
|
548
1223
|
}
|
|
549
1224
|
function confidenceMultiplier(value) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
1225
|
+
const confidence = Number(value);
|
|
1226
|
+
if (!Number.isFinite(confidence)) return 0;
|
|
1227
|
+
return Math.max(0, Math.min(1, confidence));
|
|
553
1228
|
}
|
|
554
1229
|
var package_default = {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
1230
|
+
name: "@kubohiroya/turbowarp-teachable-machine",
|
|
1231
|
+
version: "3.5.2",
|
|
1232
|
+
description: "A TurboWarp extension for recognition using Teachable Machine pose, image, and audio models.",
|
|
1233
|
+
author: "Hiroya Kubo <hiroya@cuc.ac.jp>",
|
|
1234
|
+
license: "MPL-2.0",
|
|
1235
|
+
homepage: "https://github.com/kubohiroya/turbowarp-teachable-machine#readme",
|
|
1236
|
+
repository: {
|
|
1237
|
+
"type": "git",
|
|
1238
|
+
"url": "git+https://github.com/kubohiroya/turbowarp-teachable-machine.git"
|
|
1239
|
+
},
|
|
1240
|
+
bugs: { "url": "https://github.com/kubohiroya/turbowarp-teachable-machine/issues" },
|
|
1241
|
+
exports: {
|
|
1242
|
+
"./composition": {
|
|
1243
|
+
"types": "./dist/types/composition.d.ts",
|
|
1244
|
+
"import": "./dist/composition.js"
|
|
1245
|
+
},
|
|
1246
|
+
"./posenet": {
|
|
1247
|
+
"types": "./dist/types/posenet.d.ts",
|
|
1248
|
+
"import": "./dist/posenet.js"
|
|
1249
|
+
},
|
|
1250
|
+
"./posenet-assets/*": "./dist/posenet/mobilenet-v1-075-stride16/*",
|
|
1251
|
+
"./wasm-assets/*": "./dist/wasm/*",
|
|
1252
|
+
"./dist/backend-wasm.js": "./dist/backend-wasm.js",
|
|
1253
|
+
"./dist/backend-webgpu.js": "./dist/backend-webgpu.js",
|
|
1254
|
+
"./runtime": "./dist/runtime.js",
|
|
1255
|
+
"./dist/tm.js": "./dist/tm.js",
|
|
1256
|
+
"./package.json": "./package.json"
|
|
1257
|
+
},
|
|
1258
|
+
type: "module",
|
|
1259
|
+
files: [
|
|
1260
|
+
"dist/",
|
|
1261
|
+
"README.md",
|
|
1262
|
+
"LICENSE",
|
|
1263
|
+
"THIRD_PARTY_NOTICES.md",
|
|
1264
|
+
"schemas/",
|
|
1265
|
+
"docs/fixed-input-editors.ja.md"
|
|
1266
|
+
],
|
|
1267
|
+
unpkg: "./dist/tm.js",
|
|
1268
|
+
jsdelivr: "./dist/tm.js",
|
|
1269
|
+
publishConfig: {
|
|
1270
|
+
"access": "public",
|
|
1271
|
+
"registry": "https://registry.npmjs.org/"
|
|
1272
|
+
},
|
|
1273
|
+
packageManager: "pnpm@11.11.0",
|
|
1274
|
+
engines: { "node": ">=22.18.0" },
|
|
1275
|
+
scripts: {
|
|
1276
|
+
"dev": "vite build --watch",
|
|
1277
|
+
"typecheck": "tsc --noEmit",
|
|
1278
|
+
"build": "pnpm build:extension && pnpm build:composition && pnpm build:runtime && pnpm build:backends && pnpm build:posenet && pnpm build:notices && pnpm build:types",
|
|
1279
|
+
"build:extension": "vite build",
|
|
1280
|
+
"build:composition": "vite build --config vite.composition.config.ts",
|
|
1281
|
+
"build:runtime": "vite build --config vite.runtime.config.ts",
|
|
1282
|
+
"build:backends": "vite build --config vite.backend-wasm.config.ts && vite build --config vite.backend-webgpu.config.ts && node scripts/copy-wasm-assets.ts",
|
|
1283
|
+
"build:posenet": "vite build --config vite.posenet.config.ts && node scripts/copy-posenet-assets.ts",
|
|
1284
|
+
"build:notices": "node scripts/copy-third-party-notices.ts",
|
|
1285
|
+
"build:types": "tsc --project tsconfig.build.json",
|
|
1286
|
+
"types:composition:check": "tsc --project tsconfig.composition-consumer.json",
|
|
1287
|
+
"types:posenet:check": "tsc --project tsconfig.posenet-consumer.json",
|
|
1288
|
+
"test": "vitest run",
|
|
1289
|
+
"docs": "node scripts/generate-readme.ts",
|
|
1290
|
+
"docs:check": "npm run docs && git diff --exit-code -- README.md",
|
|
1291
|
+
"docs:pages:check": "node scripts/check-pages.ts",
|
|
1292
|
+
"release:consistency:check": "node scripts/check-release-consistency.ts",
|
|
1293
|
+
"check:dist": "npm run build && git diff --exit-code -- dist",
|
|
1294
|
+
"archive:check": "node scripts/check-github-archive.ts",
|
|
1295
|
+
"pack:check": "npm pack --dry-run --ignore-scripts",
|
|
1296
|
+
"release:check": "npm publish --dry-run --access public --tag latest",
|
|
1297
|
+
"check": "npm run typecheck && npm run test && npm run build && npm run types:composition:check && npm run types:posenet:check && npm run docs:check && npm run docs:pages:check && npm run release:consistency:check && npm run check:dist && npm run archive:check && npm run pack:check",
|
|
1298
|
+
"prepack": "npm run build && npm run release:consistency:check"
|
|
1299
|
+
},
|
|
1300
|
+
devDependencies: {
|
|
1301
|
+
"@kubohiroya/turbowarp-camera-source": "0.17.1",
|
|
1302
|
+
"@kubohiroya/vite-plugin-turbowarp-extension": "0.6.1",
|
|
1303
|
+
"@teachablemachine/image": "0.8.5",
|
|
1304
|
+
"@teachablemachine/pose": "0.8.6",
|
|
1305
|
+
"@tensorflow-models/posenet": "2.2.2",
|
|
1306
|
+
"@tensorflow-models/speech-commands": "0.5.4",
|
|
1307
|
+
"@tensorflow/tfjs": "4.22.0",
|
|
1308
|
+
"@tensorflow/tfjs-backend-wasm": "4.22.0",
|
|
1309
|
+
"@tensorflow/tfjs-backend-webgpu": "4.22.0",
|
|
1310
|
+
"@types/node": "^22.20.1",
|
|
1311
|
+
"typescript": "^5.9.3",
|
|
1312
|
+
"vite": "^8.2.2",
|
|
1313
|
+
"vitest": "^4.0.14",
|
|
1314
|
+
"@kubohiroya/turbowarp-extension-data-editor": "0.2.1"
|
|
1315
|
+
},
|
|
1316
|
+
keywords: [
|
|
1317
|
+
"turbowarp",
|
|
1318
|
+
"scratch",
|
|
1319
|
+
"extension",
|
|
1320
|
+
"pose",
|
|
1321
|
+
"image",
|
|
1322
|
+
"audio",
|
|
1323
|
+
"camera",
|
|
1324
|
+
"teachable-machine"
|
|
1325
|
+
],
|
|
1326
|
+
gitHead: "97ef12018789316d6170a7aab29d7c5b0a4c90f4",
|
|
1327
|
+
"x-turbowarp-source": {
|
|
1328
|
+
"revision": "97ef12018789316d6170a7aab29d7c5b0a4c90f4",
|
|
1329
|
+
"revisionRole": "base-commit",
|
|
1330
|
+
"sourceState": "local-release-overlay"
|
|
1331
|
+
}
|
|
648
1332
|
};
|
|
649
1333
|
//#endregion
|
|
650
1334
|
//#region src/extension.ts
|
|
@@ -659,11 +1343,11 @@
|
|
|
659
1343
|
*/
|
|
660
1344
|
var CAMERA_SOURCE_RUNTIME_KEY = "ext_kubohiroyacamerasource";
|
|
661
1345
|
function readCameraSource(runtime) {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1346
|
+
if (typeof runtime !== "object" || runtime === null) return void 0;
|
|
1347
|
+
const candidate = runtime[CAMERA_SOURCE_RUNTIME_KEY];
|
|
1348
|
+
if (typeof candidate !== "object" || candidate === null) return void 0;
|
|
1349
|
+
const { acquireCamera } = candidate;
|
|
1350
|
+
return typeof acquireCamera === "function" ? candidate : void 0;
|
|
667
1351
|
}
|
|
668
1352
|
var EXTENSION_ID = "kubohiroyatm";
|
|
669
1353
|
var VERSION = `${package_default.version}-typescript`;
|
|
@@ -680,267 +1364,267 @@
|
|
|
680
1364
|
* coordinates stay in frame space either way.
|
|
681
1365
|
*/
|
|
682
1366
|
function mapKeypointsToFrame(keypoints, crop) {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
1367
|
+
if (!Array.isArray(keypoints) || !(crop.size > 0)) return keypoints;
|
|
1368
|
+
return keypoints.map((keypoint) => {
|
|
1369
|
+
if (typeof keypoint !== "object" || keypoint === null) return keypoint;
|
|
1370
|
+
const position = keypoint.position;
|
|
1371
|
+
const x = Number(position?.x);
|
|
1372
|
+
const y = Number(position?.y);
|
|
1373
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return keypoint;
|
|
1374
|
+
return {
|
|
1375
|
+
...keypoint,
|
|
1376
|
+
position: {
|
|
1377
|
+
...position,
|
|
1378
|
+
x: crop.left + (crop.size - x),
|
|
1379
|
+
y: crop.top + y
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
});
|
|
699
1383
|
}
|
|
700
1384
|
var BROWSER_RUNTIME_URL = `https://cdn.jsdelivr.net/npm/${package_default.name}@${package_default.version}/dist/runtime.js`;
|
|
701
1385
|
var POSITION_ITEMS = [
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
1386
|
+
{
|
|
1387
|
+
text: "top left",
|
|
1388
|
+
value: "top-left"
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
text: "top right",
|
|
1392
|
+
value: "top-right"
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
text: "bottom left",
|
|
1396
|
+
value: "bottom-left"
|
|
1397
|
+
},
|
|
1398
|
+
{
|
|
1399
|
+
text: "bottom right",
|
|
1400
|
+
value: "bottom-right"
|
|
1401
|
+
},
|
|
1402
|
+
{
|
|
1403
|
+
text: "center",
|
|
1404
|
+
value: "center"
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
text: "full stage",
|
|
1408
|
+
value: "full-stage"
|
|
1409
|
+
}
|
|
726
1410
|
];
|
|
727
1411
|
var POSITION_ALIASES = {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1412
|
+
左上: "top-left",
|
|
1413
|
+
右上: "top-right",
|
|
1414
|
+
左下: "bottom-left",
|
|
1415
|
+
右下: "bottom-right",
|
|
1416
|
+
中央: "center",
|
|
1417
|
+
ステージ全体: "full-stage",
|
|
1418
|
+
"top-left": "top-left",
|
|
1419
|
+
"top-right": "top-right",
|
|
1420
|
+
"bottom-left": "bottom-left",
|
|
1421
|
+
"bottom-right": "bottom-right",
|
|
1422
|
+
center: "center",
|
|
1423
|
+
"full-stage": "full-stage"
|
|
740
1424
|
};
|
|
741
1425
|
var PREVIEW_MIRRORING_ITEMS = [{
|
|
742
|
-
|
|
743
|
-
|
|
1426
|
+
text: "mirrored",
|
|
1427
|
+
value: "mirrored"
|
|
744
1428
|
}, {
|
|
745
|
-
|
|
746
|
-
|
|
1429
|
+
text: "unmirrored",
|
|
1430
|
+
value: "unmirrored"
|
|
747
1431
|
}];
|
|
748
1432
|
var PREVIEW_MIRRORING_ALIASES = {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1433
|
+
mirrored: true,
|
|
1434
|
+
unmirrored: false,
|
|
1435
|
+
mirror: true,
|
|
1436
|
+
normal: false,
|
|
1437
|
+
"左右反転": true,
|
|
1438
|
+
"そのまま": false
|
|
755
1439
|
};
|
|
756
1440
|
var COMPUTE_MODE_ITEMS = COMPUTE_MODES.map((value) => ({
|
|
757
|
-
|
|
758
|
-
|
|
1441
|
+
text: value,
|
|
1442
|
+
value
|
|
759
1443
|
}));
|
|
760
1444
|
var RECOGNITION_MODE_ITEMS = [
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
1445
|
+
{
|
|
1446
|
+
text: "pose",
|
|
1447
|
+
value: "pose"
|
|
1448
|
+
},
|
|
1449
|
+
{
|
|
1450
|
+
text: "image",
|
|
1451
|
+
value: "image"
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
text: "audio",
|
|
1455
|
+
value: "audio"
|
|
1456
|
+
}
|
|
773
1457
|
];
|
|
774
1458
|
var RECOGNITION_MODE_ALIASES = {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1459
|
+
pose: "pose",
|
|
1460
|
+
poses: "pose",
|
|
1461
|
+
body: "pose",
|
|
1462
|
+
image: "image",
|
|
1463
|
+
images: "image",
|
|
1464
|
+
object: "image",
|
|
1465
|
+
card: "image",
|
|
1466
|
+
marker: "image",
|
|
1467
|
+
audio: "audio",
|
|
1468
|
+
sound: "audio",
|
|
1469
|
+
voice: "audio",
|
|
1470
|
+
microphone: "audio",
|
|
1471
|
+
mic: "audio",
|
|
1472
|
+
"ポーズ": "pose",
|
|
1473
|
+
"姿勢": "pose",
|
|
1474
|
+
"画像": "image",
|
|
1475
|
+
"物体": "image",
|
|
1476
|
+
"カード": "image",
|
|
1477
|
+
"記号": "image",
|
|
1478
|
+
"音声": "audio",
|
|
1479
|
+
"音": "audio",
|
|
1480
|
+
"声": "audio",
|
|
1481
|
+
"マイク": "audio"
|
|
798
1482
|
};
|
|
799
1483
|
var POSE_OVERLAY_VISIBILITY_ITEMS = [{
|
|
800
|
-
|
|
801
|
-
|
|
1484
|
+
text: "on",
|
|
1485
|
+
value: "on"
|
|
802
1486
|
}, {
|
|
803
|
-
|
|
804
|
-
|
|
1487
|
+
text: "off",
|
|
1488
|
+
value: "off"
|
|
805
1489
|
}];
|
|
806
1490
|
var POSE_OVERLAY_VISIBILITY_ALIASES = {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1491
|
+
on: true,
|
|
1492
|
+
off: false,
|
|
1493
|
+
show: true,
|
|
1494
|
+
hide: false,
|
|
1495
|
+
true: true,
|
|
1496
|
+
false: false,
|
|
1497
|
+
表示: true,
|
|
1498
|
+
非表示: false
|
|
815
1499
|
};
|
|
816
1500
|
var POSE_CONFIDENCE_PROPERTY_ITEMS = [
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1501
|
+
{
|
|
1502
|
+
text: "joint opacity",
|
|
1503
|
+
value: "joint-opacity"
|
|
1504
|
+
},
|
|
1505
|
+
{
|
|
1506
|
+
text: "joint radius",
|
|
1507
|
+
value: "joint-radius"
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
text: "bone opacity",
|
|
1511
|
+
value: "bone-opacity"
|
|
1512
|
+
},
|
|
1513
|
+
{
|
|
1514
|
+
text: "bone width",
|
|
1515
|
+
value: "bone-width"
|
|
1516
|
+
}
|
|
833
1517
|
];
|
|
834
1518
|
var CAMERA_SELECTION_ITEMS = [
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1519
|
+
{
|
|
1520
|
+
text: "default camera",
|
|
1521
|
+
value: "default"
|
|
1522
|
+
},
|
|
1523
|
+
{
|
|
1524
|
+
text: "front camera",
|
|
1525
|
+
value: "front"
|
|
1526
|
+
},
|
|
1527
|
+
{
|
|
1528
|
+
text: "back camera",
|
|
1529
|
+
value: "back"
|
|
1530
|
+
}
|
|
847
1531
|
];
|
|
848
1532
|
var CAMERA_SELECTION_ALIASES = {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1533
|
+
default: "default",
|
|
1534
|
+
front: "front",
|
|
1535
|
+
back: "back",
|
|
1536
|
+
user: "front",
|
|
1537
|
+
environment: "back",
|
|
1538
|
+
"既定": "default",
|
|
1539
|
+
"インカメラ": "front",
|
|
1540
|
+
"前面カメラ": "front",
|
|
1541
|
+
"背面カメラ": "back"
|
|
858
1542
|
};
|
|
859
1543
|
var loadingPromises = /* @__PURE__ */ new Map();
|
|
860
1544
|
function normalizePosition(value) {
|
|
861
|
-
|
|
1545
|
+
return POSITION_ALIASES[String(value ?? "bottom-right")] ?? "bottom-right";
|
|
862
1546
|
}
|
|
863
1547
|
function normalizePreviewMirroring(value) {
|
|
864
|
-
|
|
1548
|
+
return PREVIEW_MIRRORING_ALIASES[String(value ?? "mirrored").trim().toLowerCase()] ?? true;
|
|
865
1549
|
}
|
|
866
1550
|
function normalizeRecognitionMode(value) {
|
|
867
|
-
|
|
1551
|
+
return RECOGNITION_MODE_ALIASES[String(value ?? "pose").trim().toLowerCase()] ?? "pose";
|
|
868
1552
|
}
|
|
869
1553
|
function normalizeCameraSelection(value) {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1554
|
+
const selection = String(value ?? "default").trim();
|
|
1555
|
+
const normalized = CAMERA_SELECTION_ALIASES[selection.toLowerCase() || "default"];
|
|
1556
|
+
if (normalized === "default" || normalized === "front" || normalized === "back") return {
|
|
1557
|
+
kind: "preference",
|
|
1558
|
+
value: normalized
|
|
1559
|
+
};
|
|
1560
|
+
return {
|
|
1561
|
+
kind: "device",
|
|
1562
|
+
value: selection
|
|
1563
|
+
};
|
|
880
1564
|
}
|
|
881
1565
|
function normalizePoseOverlayVisibility(value) {
|
|
882
|
-
|
|
1566
|
+
return POSE_OVERLAY_VISIBILITY_ALIASES[String(value ?? "on").trim().toLowerCase()] ?? true;
|
|
883
1567
|
}
|
|
884
1568
|
function normalizePoseStyleNumber(value, fallback, maximum) {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1569
|
+
const number = value === "" ? fallback : Number(value);
|
|
1570
|
+
if (!Number.isFinite(number)) return fallback;
|
|
1571
|
+
return Math.max(0, maximum === void 0 ? number : Math.min(maximum, number));
|
|
888
1572
|
}
|
|
889
1573
|
function normalizePoseColor(value, fallback) {
|
|
890
|
-
|
|
1574
|
+
return String(value ?? "").trim() || fallback;
|
|
891
1575
|
}
|
|
892
1576
|
function cameraConstraints(selection) {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1577
|
+
if (selection.kind === "device") return { deviceId: { exact: selection.value } };
|
|
1578
|
+
if (selection.value === "front") return { facingMode: { ideal: "user" } };
|
|
1579
|
+
if (selection.value === "back") return { facingMode: { ideal: "environment" } };
|
|
896
1580
|
}
|
|
897
1581
|
function scriptLoadedFor(src) {
|
|
898
|
-
|
|
899
|
-
|
|
1582
|
+
if (src === BROWSER_RUNTIME_URL) return typeof globalThis.tf !== "undefined" && typeof globalThis.tmPose !== "undefined" && typeof globalThis.tmImage !== "undefined" && typeof globalThis.tmAudio !== "undefined";
|
|
1583
|
+
return false;
|
|
900
1584
|
}
|
|
901
1585
|
function loadScript(src) {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1586
|
+
if (scriptLoadedFor(src)) return Promise.resolve();
|
|
1587
|
+
const active = loadingPromises.get(src);
|
|
1588
|
+
if (active) return active;
|
|
1589
|
+
const existing = Array.from(document.scripts).find((script) => script.src === src);
|
|
1590
|
+
if (existing?.dataset.kubohiroyatmLoaded === "true") return Promise.resolve();
|
|
1591
|
+
const promise = new Promise((resolve, reject) => {
|
|
1592
|
+
const script = existing ?? document.createElement("script");
|
|
1593
|
+
const cleanup = () => {
|
|
1594
|
+
script.removeEventListener("load", handleLoad);
|
|
1595
|
+
script.removeEventListener("error", handleError);
|
|
1596
|
+
};
|
|
1597
|
+
const handleLoad = () => {
|
|
1598
|
+
cleanup();
|
|
1599
|
+
script.dataset.kubohiroyatmLoaded = "true";
|
|
1600
|
+
resolve();
|
|
1601
|
+
};
|
|
1602
|
+
const handleError = () => {
|
|
1603
|
+
cleanup();
|
|
1604
|
+
loadingPromises.delete(src);
|
|
1605
|
+
reject(/* @__PURE__ */ new Error("TM: Failed to load script: " + src));
|
|
1606
|
+
};
|
|
1607
|
+
script.addEventListener("load", handleLoad, { once: true });
|
|
1608
|
+
script.addEventListener("error", handleError, { once: true });
|
|
1609
|
+
if (!existing) {
|
|
1610
|
+
script.src = src;
|
|
1611
|
+
document.head.appendChild(script);
|
|
1612
|
+
} else queueMicrotask(() => {
|
|
1613
|
+
if (scriptLoadedFor(src)) handleLoad();
|
|
1614
|
+
});
|
|
1615
|
+
});
|
|
1616
|
+
loadingPromises.set(src, promise);
|
|
1617
|
+
return promise;
|
|
934
1618
|
}
|
|
935
1619
|
function rectanglesIntersect(left, right) {
|
|
936
|
-
|
|
1620
|
+
return left.right > right.left && left.left < right.right && left.bottom > right.top && left.top < right.bottom;
|
|
937
1621
|
}
|
|
938
1622
|
function canvasScore(width, height) {
|
|
939
|
-
|
|
940
|
-
|
|
1623
|
+
if (width <= 0 || height <= 0) return Number.NEGATIVE_INFINITY;
|
|
1624
|
+
return width * height / (1 + Math.abs(width / height - 4 / 3) * 4);
|
|
941
1625
|
}
|
|
942
1626
|
function isDocumentHidden() {
|
|
943
|
-
|
|
1627
|
+
return typeof document !== "undefined" && document.visibilityState === "hidden";
|
|
944
1628
|
}
|
|
945
1629
|
/**
|
|
946
1630
|
* Initialize the camera canvas before Teachable Machine or TensorFlow.js requests its context.
|
|
@@ -950,1227 +1634,1238 @@
|
|
|
950
1634
|
* readback-optimized context, and the legacy backend parameter remains accepted for compatibility.
|
|
951
1635
|
*/
|
|
952
1636
|
function initializeCameraReadbackContext(canvas, _tensorflowBackend) {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1637
|
+
if (typeof canvas !== "object" || canvas === null || typeof canvas.getContext !== "function") throw new Error("TM: Webcam canvas does not provide a 2D context.");
|
|
1638
|
+
const context = canvas.getContext("2d");
|
|
1639
|
+
if (!context) throw new Error("TM: Webcam canvas 2D context is unavailable.");
|
|
1640
|
+
return context;
|
|
957
1641
|
}
|
|
958
1642
|
var TMExtension = class {
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
1643
|
+
constructor(featureFlags = {}, dependencies = {}) {
|
|
1644
|
+
_defineProperty(this, "fixedEditors", fixedInputRegistration(typeof Scratch === "undefined" ? void 0 : Scratch));
|
|
1645
|
+
this.featureFlags = {
|
|
1646
|
+
...FEATURE_FLAGS,
|
|
1647
|
+
...featureFlags
|
|
1648
|
+
};
|
|
1649
|
+
this.tmPoseRuntime = dependencies.poseRuntime ?? dependencies.runtime ?? null;
|
|
1650
|
+
this.tmImageRuntime = dependencies.imageRuntime ?? null;
|
|
1651
|
+
this.tmAudioRuntime = dependencies.audioRuntime ?? null;
|
|
1652
|
+
this.tmComputeRuntime = dependencies.compute ?? null;
|
|
1653
|
+
this.computeMode = normalizeComputeMode(dependencies.computeMode);
|
|
1654
|
+
this.computeSelection = null;
|
|
1655
|
+
this.modelOwnedByExtension = false;
|
|
1656
|
+
this.allowRemoteLibraries = dependencies.allowRemoteLibraries ?? true;
|
|
1657
|
+
this.onAccumulatedPoseChanged = dependencies.onAccumulatedPoseChanged ?? null;
|
|
1658
|
+
this.recognitionMode = "pose";
|
|
1659
|
+
this.modelURL = "";
|
|
1660
|
+
this.model = null;
|
|
1661
|
+
this.webcam = null;
|
|
1662
|
+
this.cameraLease = null;
|
|
1663
|
+
this.cameraRunning = false;
|
|
1664
|
+
this.cameraSelection = "default";
|
|
1665
|
+
this.cameraSelectionIsDeviceId = false;
|
|
1666
|
+
this.cameraDevices = [];
|
|
1667
|
+
this.activeCameraDeviceId = "";
|
|
1668
|
+
this.activeCameraDeviceName = "";
|
|
1669
|
+
this.cameraSelectionQueue = Promise.resolve();
|
|
1670
|
+
this.recognizing = false;
|
|
1671
|
+
this.audioListening = false;
|
|
1672
|
+
this.loopStarted = false;
|
|
1673
|
+
this.loopGeneration = 0;
|
|
1674
|
+
this.activeModelOperations = /* @__PURE__ */ new Map();
|
|
1675
|
+
this.currentPoseName = "";
|
|
1676
|
+
this.score = 0;
|
|
1677
|
+
this.predictions = {};
|
|
1678
|
+
this.accumulationCoefficient = 1;
|
|
1679
|
+
this.decayCoefficient = .9;
|
|
1680
|
+
this.activeDecayCoefficient = .9;
|
|
1681
|
+
this.accumulatedPoseThreshold = 0;
|
|
1682
|
+
this.accumulatedPoseName = "";
|
|
1683
|
+
this.accumulatedScore = 0;
|
|
1684
|
+
this.accumulatedPredictions = {};
|
|
1685
|
+
this.lastAccumulationTime = null;
|
|
1686
|
+
this.previewOpacity = .6;
|
|
1687
|
+
this.previewPosition = "bottom-right";
|
|
1688
|
+
this.previewMirrored = true;
|
|
1689
|
+
this.previewVisible = true;
|
|
1690
|
+
this.previewCanvas = null;
|
|
1691
|
+
this.previewStageElement = null;
|
|
1692
|
+
this.poseOverlayVisible = true;
|
|
1693
|
+
this.poseOverlayMinimumConfidence = .5;
|
|
1694
|
+
this.poseJointStyles = Object.fromEntries(POSE_KEYPOINT_NAMES.map((part) => [part, { ...DEFAULT_POSE_JOINT_STYLE }]));
|
|
1695
|
+
this.poseBoneStyle = { ...DEFAULT_POSE_BONE_STYLE };
|
|
1696
|
+
this.poseConfidenceScaling = { ...DEFAULT_POSE_OVERLAY_CONFIDENCE_SCALING };
|
|
1697
|
+
this.poseOverlaySvg = null;
|
|
1698
|
+
this.poseOverlayJointElements = /* @__PURE__ */ new Map();
|
|
1699
|
+
this.poseOverlayBoneElements = [];
|
|
1700
|
+
this.latestPoseKeypoints = [];
|
|
1701
|
+
this.cameraMs = 0;
|
|
1702
|
+
this.modelLoadMs = 0;
|
|
1703
|
+
this.firstRecognitionMs = 0;
|
|
1704
|
+
this.lastError = "";
|
|
1705
|
+
this.accumulatedPosePausedForBackground = isDocumentHidden();
|
|
1706
|
+
this.visibilityChangeListener = () => this.handleDocumentVisibilityChange();
|
|
1707
|
+
if (this.featureFlags.temporalPoseScoring && typeof document !== "undefined") document.addEventListener("visibilitychange", this.visibilityChangeListener);
|
|
1708
|
+
}
|
|
1709
|
+
getInfo() {
|
|
1710
|
+
const visibleOpcodes = block_definitions_default.blocks.filter((block) => !block.featureFlag || this.featureFlags[block.featureFlag]).map((block) => `${EXTENSION_ID}_${block.opcode}`);
|
|
1711
|
+
this.fixedEditors.ensure(visibleOpcodes);
|
|
1712
|
+
return {
|
|
1713
|
+
id: EXTENSION_ID,
|
|
1714
|
+
name: Scratch.translate(block_definitions_default.extensionName),
|
|
1715
|
+
docsURI: DOCS_URI,
|
|
1716
|
+
blockIconURI: BLOCK_ICON_URI,
|
|
1717
|
+
blocks: block_definitions_default.blocks.filter((block) => !block.featureFlag || this.featureFlags[block.featureFlag]).map((block) => ({
|
|
1718
|
+
opcode: block.opcode,
|
|
1719
|
+
blockType: Scratch.BlockType[block.blockType],
|
|
1720
|
+
text: Scratch.translate(block.text),
|
|
1721
|
+
...block.disableMonitor ? { disableMonitor: true } : {},
|
|
1722
|
+
...block.arguments ? { arguments: Object.fromEntries(Object.entries(block.arguments).map(([name, argument]) => [name, {
|
|
1723
|
+
type: Scratch.ArgumentType[argument.type],
|
|
1724
|
+
defaultValue: argument.defaultValue,
|
|
1725
|
+
...argument.menu ? { menu: argument.menu } : {}
|
|
1726
|
+
}])) } : {}
|
|
1727
|
+
})),
|
|
1728
|
+
menus: {
|
|
1729
|
+
positionMenu: {
|
|
1730
|
+
acceptReporters: true,
|
|
1731
|
+
items: POSITION_ITEMS.map((item) => ({
|
|
1732
|
+
text: Scratch.translate(item.text),
|
|
1733
|
+
value: item.value
|
|
1734
|
+
}))
|
|
1735
|
+
},
|
|
1736
|
+
previewMirroringMenu: {
|
|
1737
|
+
acceptReporters: true,
|
|
1738
|
+
items: PREVIEW_MIRRORING_ITEMS.map((item) => ({
|
|
1739
|
+
text: Scratch.translate(item.text),
|
|
1740
|
+
value: item.value
|
|
1741
|
+
}))
|
|
1742
|
+
},
|
|
1743
|
+
recognitionModeMenu: {
|
|
1744
|
+
acceptReporters: true,
|
|
1745
|
+
items: RECOGNITION_MODE_ITEMS.map((item) => ({
|
|
1746
|
+
text: Scratch.translate(item.text),
|
|
1747
|
+
value: item.value
|
|
1748
|
+
}))
|
|
1749
|
+
},
|
|
1750
|
+
computeModeMenu: {
|
|
1751
|
+
acceptReporters: true,
|
|
1752
|
+
items: COMPUTE_MODE_ITEMS.map((item) => ({
|
|
1753
|
+
text: Scratch.translate(item.text),
|
|
1754
|
+
value: item.value
|
|
1755
|
+
}))
|
|
1756
|
+
},
|
|
1757
|
+
poseOverlayVisibilityMenu: {
|
|
1758
|
+
acceptReporters: true,
|
|
1759
|
+
items: POSE_OVERLAY_VISIBILITY_ITEMS.map((item) => ({
|
|
1760
|
+
text: Scratch.translate(item.text),
|
|
1761
|
+
value: item.value
|
|
1762
|
+
}))
|
|
1763
|
+
},
|
|
1764
|
+
poseKeypointMenu: {
|
|
1765
|
+
acceptReporters: true,
|
|
1766
|
+
items: POSE_KEYPOINT_NAMES.map((part) => ({
|
|
1767
|
+
text: part,
|
|
1768
|
+
value: part
|
|
1769
|
+
}))
|
|
1770
|
+
},
|
|
1771
|
+
poseConfidencePropertyMenu: {
|
|
1772
|
+
acceptReporters: true,
|
|
1773
|
+
items: POSE_CONFIDENCE_PROPERTY_ITEMS.map((item) => ({
|
|
1774
|
+
text: Scratch.translate(item.text),
|
|
1775
|
+
value: item.value
|
|
1776
|
+
}))
|
|
1777
|
+
},
|
|
1778
|
+
cameraMenu: {
|
|
1779
|
+
acceptReporters: true,
|
|
1780
|
+
items: "getCameraMenuItems"
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
versionReporter() {
|
|
1786
|
+
return VERSION;
|
|
1787
|
+
}
|
|
1788
|
+
setLastError(error) {
|
|
1789
|
+
this.lastError = String(error?.message ?? error);
|
|
1790
|
+
}
|
|
1791
|
+
setRecognitionMode(args) {
|
|
1792
|
+
const mode = normalizeRecognitionMode(args.MODE);
|
|
1793
|
+
if (mode === this.recognitionMode) return;
|
|
1794
|
+
if (this.recognizing) throw new Error("Teachable Machine: Stop recognition before changing recognition mode.");
|
|
1795
|
+
if (this.cameraRunning) throw new Error("Teachable Machine: Stop the camera before changing recognition mode.");
|
|
1796
|
+
this.recognitionMode = mode;
|
|
1797
|
+
this.model = null;
|
|
1798
|
+
this.modelOwnedByExtension = false;
|
|
1799
|
+
this.modelURL = "";
|
|
1800
|
+
this.modelLoadMs = 0;
|
|
1801
|
+
this.firstRecognitionMs = 0;
|
|
1802
|
+
this.currentPoseName = "";
|
|
1803
|
+
this.score = 0;
|
|
1804
|
+
this.predictions = {};
|
|
1805
|
+
this.clearPoseOverlay();
|
|
1806
|
+
this.resetAccumulatedPose();
|
|
1807
|
+
}
|
|
1808
|
+
recognitionModeReporter() {
|
|
1809
|
+
return this.recognitionMode;
|
|
1810
|
+
}
|
|
1811
|
+
activeComputeRuntime() {
|
|
1812
|
+
if (this.tmComputeRuntime) return this.tmComputeRuntime;
|
|
1813
|
+
const runtime = globalThis.tmCompute;
|
|
1814
|
+
return runtime && typeof runtime.select === "function" ? runtime : null;
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* TensorFlow.js binds every tensor to the backend that was active when the
|
|
1818
|
+
* tensor was created, so the backend is negotiated before any model loads. A
|
|
1819
|
+
* host that preloads a runtime without a compute controller keeps whatever
|
|
1820
|
+
* backend TensorFlow.js selected for itself.
|
|
1821
|
+
*/
|
|
1822
|
+
async ensureComputeBackend() {
|
|
1823
|
+
const compute = this.activeComputeRuntime();
|
|
1824
|
+
if (!compute) return null;
|
|
1825
|
+
const selection = await compute.select(this.computeMode);
|
|
1826
|
+
this.computeSelection = selection;
|
|
1827
|
+
if (selection.fallback) this.setLastError(/* @__PURE__ */ new Error(`Teachable Machine: The ${selection.requested} compute backend was unavailable, so ${selection.backend} is in use.`));
|
|
1828
|
+
return selection;
|
|
1829
|
+
}
|
|
1830
|
+
async setComputeMode(args) {
|
|
1831
|
+
const mode = normalizeComputeMode(args.MODE, this.computeMode);
|
|
1832
|
+
if (mode === this.computeMode) return;
|
|
1833
|
+
if (this.recognizing) throw new Error("Teachable Machine: Stop recognition before changing the compute mode.");
|
|
1834
|
+
if (this.model && !this.modelOwnedByExtension) throw new Error("Teachable Machine: Release the prepared model before changing the compute mode.");
|
|
1835
|
+
const previousModel = this.model;
|
|
1836
|
+
this.computeMode = mode;
|
|
1837
|
+
this.computeSelection = null;
|
|
1838
|
+
this.model = null;
|
|
1839
|
+
this.modelOwnedByExtension = false;
|
|
1840
|
+
this.modelLoadMs = 0;
|
|
1841
|
+
this.firstRecognitionMs = 0;
|
|
1842
|
+
if (previousModel) await this.releaseOwnedModel(previousModel);
|
|
1843
|
+
if (this.activeComputeRuntime()) await this.ensureComputeBackend();
|
|
1844
|
+
}
|
|
1845
|
+
async releaseOwnedModel(model) {
|
|
1846
|
+
try {
|
|
1847
|
+
await this.waitForPreparedModelIdle(model);
|
|
1848
|
+
await model.dispose?.();
|
|
1849
|
+
} catch (error) {
|
|
1850
|
+
this.setLastError(error);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
computeModeReporter() {
|
|
1854
|
+
return this.computeMode;
|
|
1855
|
+
}
|
|
1856
|
+
computeBackendReporter() {
|
|
1857
|
+
const compute = this.activeComputeRuntime();
|
|
1858
|
+
if (!compute) return "";
|
|
1859
|
+
return this.computeSelection?.backend ?? compute.getBackend() ?? "";
|
|
1860
|
+
}
|
|
1861
|
+
setModelURL(args) {
|
|
1862
|
+
this.modelURL = String(args.URL || "").trim();
|
|
1863
|
+
if (this.modelURL && !this.modelURL.endsWith("/")) this.modelURL += "/";
|
|
1864
|
+
this.model = null;
|
|
1865
|
+
this.modelLoadMs = 0;
|
|
1866
|
+
this.firstRecognitionMs = 0;
|
|
1867
|
+
}
|
|
1868
|
+
activeRuntime() {
|
|
1869
|
+
if (this.recognitionMode === "audio") return this.tmAudioRuntime;
|
|
1870
|
+
return this.recognitionMode === "image" ? this.tmImageRuntime : this.tmPoseRuntime;
|
|
1871
|
+
}
|
|
1872
|
+
async ensureLibrariesLoaded() {
|
|
1873
|
+
if (this.activeRuntime()) {
|
|
1874
|
+
if (this.activeComputeRuntime()) await this.ensureComputeBackend();
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
if (!this.allowRemoteLibraries) throw new Error("Teachable Machine: A preloaded runtime is required.");
|
|
1878
|
+
if (typeof globalThis.tf === "undefined" || typeof globalThis.tmPose === "undefined" || typeof globalThis.tmImage === "undefined" || typeof globalThis.tmAudio === "undefined") await loadScript(BROWSER_RUNTIME_URL);
|
|
1879
|
+
if (typeof globalThis.tf === "undefined" || typeof globalThis.tmPose === "undefined" || typeof globalThis.tmImage === "undefined" || typeof globalThis.tmAudio === "undefined") throw new Error("Teachable Machine: The reviewed browser runtime could not be loaded.");
|
|
1880
|
+
this.tmPoseRuntime = globalThis.tmPose;
|
|
1881
|
+
this.tmImageRuntime = globalThis.tmImage;
|
|
1882
|
+
this.tmAudioRuntime = globalThis.tmAudio;
|
|
1883
|
+
await this.ensureComputeBackend();
|
|
1884
|
+
}
|
|
1885
|
+
cleanupCameraResources() {
|
|
1886
|
+
const lease = this.cameraLease;
|
|
1887
|
+
this.cameraLease = null;
|
|
1888
|
+
const video = this.webcam?.webcam;
|
|
1889
|
+
if (lease) lease.release();
|
|
1890
|
+
else if (video?.srcObject) {
|
|
1891
|
+
video.srcObject.getTracks().forEach((track) => track.stop());
|
|
1892
|
+
video.srcObject = null;
|
|
1893
|
+
}
|
|
1894
|
+
this.previewCanvas?.parentNode?.removeChild(this.previewCanvas);
|
|
1895
|
+
this.poseOverlaySvg?.parentNode?.removeChild(this.poseOverlaySvg);
|
|
1896
|
+
this.previewCanvas = null;
|
|
1897
|
+
this.previewStageElement = null;
|
|
1898
|
+
this.poseOverlaySvg = null;
|
|
1899
|
+
this.poseOverlayJointElements = /* @__PURE__ */ new Map();
|
|
1900
|
+
this.poseOverlayBoneElements = [];
|
|
1901
|
+
this.latestPoseKeypoints = [];
|
|
1902
|
+
this.webcam = null;
|
|
1903
|
+
this.cameraRunning = false;
|
|
1904
|
+
this.activeCameraDeviceId = "";
|
|
1905
|
+
this.activeCameraDeviceName = "";
|
|
1906
|
+
this.loopStarted = false;
|
|
1907
|
+
this.loopGeneration += 1;
|
|
1908
|
+
}
|
|
1909
|
+
async startCamera() {
|
|
1910
|
+
if (this.recognitionMode === "audio") throw new Error("Teachable Machine: Audio mode uses the microphone through start recognition.");
|
|
1911
|
+
if (this.cameraRunning && this.webcam) {
|
|
1912
|
+
this.attachPreviewToStage();
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
try {
|
|
1916
|
+
this.lastError = "";
|
|
1917
|
+
const startedAt = performance.now();
|
|
1918
|
+
await this.ensureLibrariesLoaded();
|
|
1919
|
+
const runtime = this.activeRuntime();
|
|
1920
|
+
if (!runtime) throw new Error("Teachable Machine: Runtime is unavailable.");
|
|
1921
|
+
if (!("Webcam" in runtime)) throw new Error("Teachable Machine: Camera runtime is unavailable in the current mode.");
|
|
1922
|
+
this.webcam = new runtime.Webcam(320, 240, true);
|
|
1923
|
+
const lease = await this.acquireSharedCamera();
|
|
1924
|
+
if (lease) {
|
|
1925
|
+
this.cameraLease = lease;
|
|
1926
|
+
this.webcam.webcam = lease.getFrameSource().element;
|
|
1927
|
+
}
|
|
1928
|
+
const constraints = lease ? void 0 : cameraConstraints(this.resolvedCameraSelection());
|
|
1929
|
+
if (constraints) await this.webcam.setup(constraints);
|
|
1930
|
+
else await this.webcam.setup();
|
|
1931
|
+
this.ensureCameraCanvas();
|
|
1932
|
+
initializeCameraReadbackContext(this.webcam.canvas);
|
|
1933
|
+
if (!lease) await this.webcam.play();
|
|
1934
|
+
this.attachPreviewToStage();
|
|
1935
|
+
this.cameraRunning = true;
|
|
1936
|
+
try {
|
|
1937
|
+
await this.refreshCameraDevices();
|
|
1938
|
+
} catch {}
|
|
1939
|
+
this.updateActiveCameraInfo();
|
|
1940
|
+
this.cameraMs = Math.round(performance.now() - startedAt);
|
|
1941
|
+
this.startLoopIfNeeded();
|
|
1942
|
+
} catch (error) {
|
|
1943
|
+
this.cleanupCameraResources();
|
|
1944
|
+
this.setLastError(error);
|
|
1945
|
+
throw error;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Give the camera the readback canvas `setup()` did not build.
|
|
1950
|
+
*
|
|
1951
|
+
* Upstream creates that canvas inside the same branch that opens the camera, so an injected
|
|
1952
|
+
* element skips the canvas with the `getUserMedia` call. 3.4.0 shipped assuming the two were
|
|
1953
|
+
* separate, and every leased start then failed at the first context request with "Webcam canvas
|
|
1954
|
+
* does not provide a 2D context" -- which is to say pose recognition did not start at all
|
|
1955
|
+
* wherever Camera Source was present. The canvas built here is the one upstream would have
|
|
1956
|
+
* built, so the mirrored centre crop every downstream reader expects is unchanged.
|
|
1957
|
+
*/
|
|
1958
|
+
ensureCameraCanvas() {
|
|
1959
|
+
if (!this.webcam || this.webcam.canvas) return;
|
|
1960
|
+
const canvas = document.createElement("canvas");
|
|
1961
|
+
canvas.width = this.webcam.width ?? 320;
|
|
1962
|
+
canvas.height = this.webcam.height ?? 240;
|
|
1963
|
+
this.webcam.canvas = canvas;
|
|
1964
|
+
}
|
|
1965
|
+
/**
|
|
1966
|
+
* A lease on the shared camera, or null when Camera Source is not loaded.
|
|
1967
|
+
*
|
|
1968
|
+
* Camera Source owns the device and decides when a stream stops, which is what lets one camera
|
|
1969
|
+
* serve pose recognition and anything else reading frames at the same time. It is a separate
|
|
1970
|
+
* TurboWarp extension, and this one is also distributed for standalone URL loading, so its
|
|
1971
|
+
* absence is ordinary rather than a failure: the self-acquired path stays for it.
|
|
1972
|
+
*
|
|
1973
|
+
* One camera id for every visual recognition mode. Opening a second device for a work that
|
|
1974
|
+
* recognises both poses and images is not what sharing is for, and `pose` is the role name
|
|
1975
|
+
* Camera Source's own documentation uses for this consumer.
|
|
1976
|
+
*/
|
|
1977
|
+
async acquireSharedCamera() {
|
|
1978
|
+
const cameraSource = readCameraSource(Scratch.vm?.runtime);
|
|
1979
|
+
if (!cameraSource) return null;
|
|
1980
|
+
const selection = this.resolvedCameraSelection();
|
|
1981
|
+
const options = {
|
|
1982
|
+
owner: package_default.name,
|
|
1983
|
+
cameraId: "pose"
|
|
1984
|
+
};
|
|
1985
|
+
if (selection.kind === "device") options.deviceId = selection.value;
|
|
1986
|
+
return cameraSource.acquireCamera(options);
|
|
1987
|
+
}
|
|
1988
|
+
stopCamera() {
|
|
1989
|
+
try {
|
|
1990
|
+
this.stopRecognition();
|
|
1991
|
+
this.cleanupCameraResources();
|
|
1992
|
+
this.currentPoseName = "";
|
|
1993
|
+
this.score = 0;
|
|
1994
|
+
this.predictions = {};
|
|
1995
|
+
} catch (error) {
|
|
1996
|
+
this.setLastError(error);
|
|
1997
|
+
throw error;
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
isCameraRunning() {
|
|
2001
|
+
return this.cameraRunning;
|
|
2002
|
+
}
|
|
2003
|
+
getCameraMenuItems() {
|
|
2004
|
+
const fixedItems = CAMERA_SELECTION_ITEMS.map((item) => ({
|
|
2005
|
+
text: Scratch.translate(item.text),
|
|
2006
|
+
value: item.value
|
|
2007
|
+
}));
|
|
2008
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2009
|
+
const deviceItems = [];
|
|
2010
|
+
this.cameraDevices.forEach((device, index) => {
|
|
2011
|
+
if (!device.deviceId || seen.has(device.deviceId)) return;
|
|
2012
|
+
seen.add(device.deviceId);
|
|
2013
|
+
deviceItems.push({
|
|
2014
|
+
text: device.label || `${Scratch.translate("camera")} ${index + 1}`,
|
|
2015
|
+
value: device.deviceId
|
|
2016
|
+
});
|
|
2017
|
+
});
|
|
2018
|
+
return [...fixedItems, ...deviceItems];
|
|
2019
|
+
}
|
|
2020
|
+
async refreshCameraDevices() {
|
|
2021
|
+
const mediaDevices = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
|
|
2022
|
+
if (!mediaDevices || typeof mediaDevices.enumerateDevices !== "function") throw new Error("TM: Camera enumeration is not available in this browser.");
|
|
2023
|
+
const devices = await mediaDevices.enumerateDevices();
|
|
2024
|
+
this.cameraDevices = devices.filter((device) => device.kind === "videoinput").map((device) => ({
|
|
2025
|
+
deviceId: device.deviceId,
|
|
2026
|
+
label: device.label
|
|
2027
|
+
}));
|
|
2028
|
+
return this.cameraDevices;
|
|
2029
|
+
}
|
|
2030
|
+
async refreshCameraList() {
|
|
2031
|
+
try {
|
|
2032
|
+
this.lastError = "";
|
|
2033
|
+
await this.refreshCameraDevices();
|
|
2034
|
+
if (this.cameraRunning) this.updateActiveCameraInfo();
|
|
2035
|
+
} catch (error) {
|
|
2036
|
+
this.setLastError(error);
|
|
2037
|
+
throw error;
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
setCameraSelection(args) {
|
|
2041
|
+
const selection = normalizeCameraSelection(args.CAMERA);
|
|
2042
|
+
return this.enqueueCameraSelection(selection);
|
|
2043
|
+
}
|
|
2044
|
+
setCameraDeviceId(deviceId) {
|
|
2045
|
+
if (typeof deviceId !== "string" || deviceId.trim().length === 0) return Promise.reject(/* @__PURE__ */ new Error("TM: Camera device ID must be a non-empty string."));
|
|
2046
|
+
return this.enqueueCameraSelection({
|
|
2047
|
+
kind: "device",
|
|
2048
|
+
value: deviceId
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
resolvedCameraSelection() {
|
|
2052
|
+
return this.cameraSelectionIsDeviceId ? {
|
|
2053
|
+
kind: "device",
|
|
2054
|
+
value: this.cameraSelection
|
|
2055
|
+
} : {
|
|
2056
|
+
kind: "preference",
|
|
2057
|
+
value: this.cameraSelection
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
enqueueCameraSelection(selection) {
|
|
2061
|
+
const operation = this.cameraSelectionQueue.then(() => this.applyCameraSelection(selection));
|
|
2062
|
+
this.cameraSelectionQueue = operation.catch(() => void 0);
|
|
2063
|
+
return operation;
|
|
2064
|
+
}
|
|
2065
|
+
async applyCameraSelection(selection) {
|
|
2066
|
+
const previousSelection = this.cameraSelection;
|
|
2067
|
+
const previousSelectionIsDeviceId = this.cameraSelectionIsDeviceId;
|
|
2068
|
+
const wasRunning = this.cameraRunning;
|
|
2069
|
+
this.cameraSelection = selection.value;
|
|
2070
|
+
this.cameraSelectionIsDeviceId = selection.kind === "device";
|
|
2071
|
+
if (!wasRunning) return;
|
|
2072
|
+
this.cleanupCameraResources();
|
|
2073
|
+
try {
|
|
2074
|
+
await this.startCamera();
|
|
2075
|
+
} catch (switchError) {
|
|
2076
|
+
this.cameraSelection = previousSelection;
|
|
2077
|
+
this.cameraSelectionIsDeviceId = previousSelectionIsDeviceId;
|
|
2078
|
+
try {
|
|
2079
|
+
await this.startCamera();
|
|
2080
|
+
} catch (rollbackError) {
|
|
2081
|
+
const error = new AggregateError([switchError, rollbackError], "TM: Camera switch and rollback both failed.");
|
|
2082
|
+
this.setLastError(error);
|
|
2083
|
+
throw error;
|
|
2084
|
+
}
|
|
2085
|
+
this.setLastError(switchError);
|
|
2086
|
+
throw switchError;
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
updateActiveCameraInfo() {
|
|
2090
|
+
const stream = this.webcam?.webcam?.srcObject;
|
|
2091
|
+
const videoTrack = stream?.getVideoTracks?.()[0] ?? stream?.getTracks?.().find((track) => track.kind === "video" || track.kind === void 0);
|
|
2092
|
+
const settings = videoTrack?.getSettings?.() ?? {};
|
|
2093
|
+
const selectedDeviceId = this.cameraSelectionIsDeviceId ? this.cameraSelection : "";
|
|
2094
|
+
this.activeCameraDeviceId = String(settings.deviceId || selectedDeviceId);
|
|
2095
|
+
const device = this.cameraDevices.find((candidate) => candidate.deviceId === this.activeCameraDeviceId);
|
|
2096
|
+
this.activeCameraDeviceName = String(videoTrack?.label || device?.label || "");
|
|
2097
|
+
}
|
|
2098
|
+
cameraCountReporter() {
|
|
2099
|
+
return this.cameraDevices.length;
|
|
2100
|
+
}
|
|
2101
|
+
cameraDeviceIdReporter() {
|
|
2102
|
+
return this.activeCameraDeviceId;
|
|
2103
|
+
}
|
|
2104
|
+
cameraDeviceNameReporter() {
|
|
2105
|
+
return this.activeCameraDeviceName;
|
|
2106
|
+
}
|
|
2107
|
+
showPreview() {
|
|
2108
|
+
try {
|
|
2109
|
+
this.previewVisible = true;
|
|
2110
|
+
if (!this.webcam?.canvas) throw new Error("TM: Start the camera before showing the preview.");
|
|
2111
|
+
this.attachPreviewToStage();
|
|
2112
|
+
this.previewCanvas.style.display = "block";
|
|
2113
|
+
this.updatePoseOverlayVisibility();
|
|
2114
|
+
this.validatePreviewAttachment(this.previewStageElement, this.previewCanvas);
|
|
2115
|
+
} catch (error) {
|
|
2116
|
+
this.setLastError(error);
|
|
2117
|
+
throw error;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
hidePreview() {
|
|
2121
|
+
this.previewVisible = false;
|
|
2122
|
+
if (this.previewCanvas) this.previewCanvas.style.display = "none";
|
|
2123
|
+
this.updatePoseOverlayVisibility();
|
|
2124
|
+
}
|
|
2125
|
+
isPreviewVisible() {
|
|
2126
|
+
return this.previewVisible;
|
|
2127
|
+
}
|
|
2128
|
+
setPreviewOpacity(args) {
|
|
2129
|
+
let opacity = args.OPACITY === "" ? .6 : Number(args.OPACITY);
|
|
2130
|
+
if (Number.isNaN(opacity)) opacity = .6;
|
|
2131
|
+
this.previewOpacity = Math.max(0, Math.min(1, opacity));
|
|
2132
|
+
if (this.previewCanvas) this.previewCanvas.style.opacity = String(this.previewOpacity);
|
|
2133
|
+
}
|
|
2134
|
+
setPreviewPosition(args) {
|
|
2135
|
+
this.previewPosition = normalizePosition(args.POSITION);
|
|
2136
|
+
if (this.previewCanvas) {
|
|
2137
|
+
this.updatePreviewStyle();
|
|
2138
|
+
this.validatePreviewAttachment(this.previewStageElement, this.previewCanvas);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
setPreviewMirroring(args) {
|
|
2142
|
+
this.previewMirrored = normalizePreviewMirroring(args.MIRRORING);
|
|
2143
|
+
if (this.previewCanvas) this.updatePreviewStyle();
|
|
2144
|
+
}
|
|
2145
|
+
previewMirroringReporter() {
|
|
2146
|
+
return this.previewMirrored ? "mirrored" : "unmirrored";
|
|
2147
|
+
}
|
|
2148
|
+
setPoseOverlayVisibility(args) {
|
|
2149
|
+
this.poseOverlayVisible = normalizePoseOverlayVisibility(args.VISIBILITY);
|
|
2150
|
+
this.updatePoseOverlayVisibility();
|
|
2151
|
+
}
|
|
2152
|
+
showPoseOverlay() {
|
|
2153
|
+
this.poseOverlayVisible = true;
|
|
2154
|
+
this.updatePoseOverlayVisibility();
|
|
2155
|
+
}
|
|
2156
|
+
hidePoseOverlay() {
|
|
2157
|
+
this.poseOverlayVisible = false;
|
|
2158
|
+
this.updatePoseOverlayVisibility();
|
|
2159
|
+
}
|
|
2160
|
+
isPoseOverlayVisible() {
|
|
2161
|
+
return this.featureFlags.poseOverlay && this.poseOverlayVisible;
|
|
2162
|
+
}
|
|
2163
|
+
setPoseJointStyle(args) {
|
|
2164
|
+
const part = String(args.PART ?? "");
|
|
2165
|
+
if (!isPoseKeypointName(part)) throw new Error(`TM: Unknown PoseNet joint: ${part}`);
|
|
2166
|
+
const previous = this.poseJointStyles[part];
|
|
2167
|
+
this.poseJointStyles[part] = {
|
|
2168
|
+
color: normalizePoseColor(args.COLOR, previous.color),
|
|
2169
|
+
opacity: normalizePoseStyleNumber(args.OPACITY, previous.opacity, 1),
|
|
2170
|
+
radius: normalizePoseStyleNumber(args.RADIUS, previous.radius)
|
|
2171
|
+
};
|
|
2172
|
+
this.redrawPoseOverlay();
|
|
2173
|
+
}
|
|
2174
|
+
setPoseBoneStyle(args) {
|
|
2175
|
+
this.poseBoneStyle = {
|
|
2176
|
+
color: normalizePoseColor(args.COLOR, this.poseBoneStyle.color),
|
|
2177
|
+
opacity: normalizePoseStyleNumber(args.OPACITY, this.poseBoneStyle.opacity, 1),
|
|
2178
|
+
width: normalizePoseStyleNumber(args.WIDTH, this.poseBoneStyle.width)
|
|
2179
|
+
};
|
|
2180
|
+
this.redrawPoseOverlay();
|
|
2181
|
+
}
|
|
2182
|
+
setPoseOverlayMinimumConfidence(args) {
|
|
2183
|
+
this.poseOverlayMinimumConfidence = normalizePoseStyleNumber(args.CONFIDENCE, this.poseOverlayMinimumConfidence, 1);
|
|
2184
|
+
this.redrawPoseOverlay();
|
|
2185
|
+
}
|
|
2186
|
+
setPoseConfidenceScaling(args) {
|
|
2187
|
+
const property = String(args.PROPERTY ?? "");
|
|
2188
|
+
const key = {
|
|
2189
|
+
"joint-opacity": "jointOpacity",
|
|
2190
|
+
"joint-radius": "jointRadius",
|
|
2191
|
+
"bone-opacity": "boneOpacity",
|
|
2192
|
+
"bone-width": "boneWidth"
|
|
2193
|
+
}[property];
|
|
2194
|
+
if (!key) throw new Error(`TM: Unknown confidence-scaled property: ${property}`);
|
|
2195
|
+
this.poseConfidenceScaling[key] = normalizePoseOverlayVisibility(args.STATE);
|
|
2196
|
+
this.redrawPoseOverlay();
|
|
2197
|
+
}
|
|
2198
|
+
async loadModel() {
|
|
2199
|
+
if (this.model) return;
|
|
2200
|
+
if (!this.modelURL) throw new Error("Teachable Machine: Set the model URL first.");
|
|
2201
|
+
try {
|
|
2202
|
+
this.lastError = "";
|
|
2203
|
+
const startedAt = performance.now();
|
|
2204
|
+
await this.ensureLibrariesLoaded();
|
|
2205
|
+
const runtime = this.activeRuntime();
|
|
2206
|
+
if (!runtime || typeof runtime.load !== "function") throw new Error("Teachable Machine: The URL loader is not available.");
|
|
2207
|
+
this.model = await runtime.load(this.modelURL + "model.json", this.modelURL + "metadata.json");
|
|
2208
|
+
this.modelOwnedByExtension = true;
|
|
2209
|
+
this.modelLoadMs = Math.round(performance.now() - startedAt);
|
|
2210
|
+
} catch (error) {
|
|
2211
|
+
this.setLastError(error);
|
|
2212
|
+
throw error;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
isModelLoaded() {
|
|
2216
|
+
return Boolean(this.model);
|
|
2217
|
+
}
|
|
2218
|
+
usePreparedModel(model) {
|
|
2219
|
+
if (!model || typeof model !== "object") throw new TypeError("TM: Prepared model must be an object.");
|
|
2220
|
+
if (this.recognizing && this.model !== model) throw new Error("TM: Stop recognition before changing the active model.");
|
|
2221
|
+
this.model = model;
|
|
2222
|
+
this.modelOwnedByExtension = false;
|
|
2223
|
+
this.modelURL = "";
|
|
2224
|
+
this.modelLoadMs = 0;
|
|
2225
|
+
this.firstRecognitionMs = 0;
|
|
2226
|
+
}
|
|
2227
|
+
clearPreparedModel(model) {
|
|
2228
|
+
if (model !== void 0 && this.model !== model) return;
|
|
2229
|
+
this.stopRecognition();
|
|
2230
|
+
this.model = null;
|
|
2231
|
+
this.modelOwnedByExtension = false;
|
|
2232
|
+
this.modelURL = "";
|
|
2233
|
+
this.modelLoadMs = 0;
|
|
2234
|
+
this.firstRecognitionMs = 0;
|
|
2235
|
+
}
|
|
2236
|
+
async startRecognition() {
|
|
2237
|
+
try {
|
|
2238
|
+
this.lastError = "";
|
|
2239
|
+
const startingNewSession = !this.recognizing;
|
|
2240
|
+
if (this.recognitionMode !== "audio" && !this.cameraRunning) await this.startCamera();
|
|
2241
|
+
await this.loadModel();
|
|
2242
|
+
if (startingNewSession && this.featureFlags.temporalPoseScoring) this.startAccumulatedPoseSession();
|
|
2243
|
+
this.recognizing = true;
|
|
2244
|
+
if (this.recognitionMode === "audio") await this.startAudioRecognition();
|
|
2245
|
+
else this.startLoopIfNeeded();
|
|
2246
|
+
} catch (error) {
|
|
2247
|
+
this.setLastError(error);
|
|
2248
|
+
throw error;
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
stopRecognition() {
|
|
2252
|
+
this.recognizing = false;
|
|
2253
|
+
this.stopAudioRecognition();
|
|
2254
|
+
this.currentPoseName = "";
|
|
2255
|
+
this.score = 0;
|
|
2256
|
+
this.predictions = {};
|
|
2257
|
+
this.clearPoseOverlay();
|
|
2258
|
+
this.resetAccumulatedPose("stop");
|
|
2259
|
+
}
|
|
2260
|
+
isRecognizing() {
|
|
2261
|
+
return this.recognizing;
|
|
2262
|
+
}
|
|
2263
|
+
async startAudioRecognition() {
|
|
2264
|
+
if (!this.model || typeof this.model.listen !== "function") throw new Error("Teachable Machine: The active model does not support audio recognition.");
|
|
2265
|
+
if (typeof this.model.isListening === "function" && this.model.isListening()) {
|
|
2266
|
+
this.audioListening = true;
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
const first = this.firstRecognitionMs === 0;
|
|
2270
|
+
const startedAt = first ? performance.now() : 0;
|
|
2271
|
+
await this.model.listen(async (result) => {
|
|
2272
|
+
if (!this.recognizing || this.recognitionMode !== "audio" || this.model === null) return;
|
|
2273
|
+
if (first && this.firstRecognitionMs === 0) this.firstRecognitionMs = Math.round(performance.now() - startedAt);
|
|
2274
|
+
this.applyPredictions(this.audioPredictionFromResult(this.model, result));
|
|
2275
|
+
}, {
|
|
2276
|
+
probabilityThreshold: 0,
|
|
2277
|
+
overlapFactor: .5,
|
|
2278
|
+
invokeCallbackOnNoiseAndUnknown: true
|
|
2279
|
+
});
|
|
2280
|
+
this.audioListening = true;
|
|
2281
|
+
}
|
|
2282
|
+
stopAudioRecognition() {
|
|
2283
|
+
if (!this.audioListening || !this.model || typeof this.model.stopListening !== "function") {
|
|
2284
|
+
this.audioListening = false;
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
this.audioListening = false;
|
|
2288
|
+
this.model.stopListening().catch((error) => this.setLastError(error));
|
|
2289
|
+
}
|
|
2290
|
+
audioPredictionFromResult(model, result) {
|
|
2291
|
+
const labels = typeof model.wordLabels === "function" ? model.wordLabels() : [];
|
|
2292
|
+
const scores = Array.isArray(result?.scores) ? result.scores[0] : result?.scores;
|
|
2293
|
+
if (!labels.length || !scores || typeof scores.length !== "number") throw new Error("Teachable Machine: Audio recognition did not return labels and scores.");
|
|
2294
|
+
return labels.map((className, index) => ({
|
|
2295
|
+
className,
|
|
2296
|
+
probability: Number(scores[index] ?? 0)
|
|
2297
|
+
}));
|
|
2298
|
+
}
|
|
2299
|
+
applyPredictions(prediction) {
|
|
2300
|
+
let best = {
|
|
2301
|
+
className: "",
|
|
2302
|
+
probability: 0
|
|
2303
|
+
};
|
|
2304
|
+
this.predictions = {};
|
|
2305
|
+
for (const result of prediction) {
|
|
2306
|
+
this.predictions[result.className] = result.probability;
|
|
2307
|
+
if (result.probability > best.probability) best = result;
|
|
2308
|
+
}
|
|
2309
|
+
this.currentPoseName = best.className;
|
|
2310
|
+
this.score = best.probability;
|
|
2311
|
+
if (this.featureFlags.temporalPoseScoring) this.updateAccumulatedPose(prediction);
|
|
2312
|
+
}
|
|
2313
|
+
findStageElement() {
|
|
2314
|
+
try {
|
|
2315
|
+
const stageCanvas = this.findLikelyStageCanvas();
|
|
2316
|
+
if (stageCanvas.parentElement) return stageCanvas.parentElement;
|
|
2317
|
+
} catch {}
|
|
2318
|
+
const editorStage = document.querySelector(".stage_stage-wrapper_2bejr") || document.querySelector("[class*=\"stage_stage-wrapper\"]") || document.querySelector("[class*=\"stage-wrapper\"]") || document.querySelector("[class*=\"stage-wrapper_stage-wrapper\"]");
|
|
2319
|
+
if (editorStage) return editorStage;
|
|
2320
|
+
throw new Error("TM: TurboWarp stage element was not found.");
|
|
2321
|
+
}
|
|
2322
|
+
findLikelyStageCanvas() {
|
|
2323
|
+
const webcamCanvas = this.webcam?.canvas ?? null;
|
|
2324
|
+
const allCanvases = Array.from(document.querySelectorAll("canvas")).filter((canvas) => canvas !== webcamCanvas && canvas !== this.previewCanvas);
|
|
2325
|
+
const visibleCandidates = allCanvases.map((canvas) => {
|
|
2326
|
+
const rect = canvas.getBoundingClientRect();
|
|
2327
|
+
return {
|
|
2328
|
+
canvas,
|
|
2329
|
+
rect,
|
|
2330
|
+
style: window.getComputedStyle(canvas),
|
|
2331
|
+
score: canvasScore(rect.width, rect.height)
|
|
2332
|
+
};
|
|
2333
|
+
}).filter((item) => item.rect.width >= 200 && item.rect.height >= 150).filter((item) => item.style.display !== "none").filter((item) => item.style.visibility !== "hidden").filter((item) => Number(item.style.opacity || 1) !== 0).sort((left, right) => right.score - left.score);
|
|
2334
|
+
if (visibleCandidates[0]) return visibleCandidates[0].canvas;
|
|
2335
|
+
const fallbackCandidates = allCanvases.map((canvas) => ({
|
|
2336
|
+
canvas,
|
|
2337
|
+
score: canvasScore(canvas.width, canvas.height)
|
|
2338
|
+
})).filter((item) => item.canvas.width >= 200 && item.canvas.height >= 150).sort((left, right) => right.score - left.score);
|
|
2339
|
+
if (fallbackCandidates[0]) return fallbackCandidates[0].canvas;
|
|
2340
|
+
throw new Error("TM: No likely stage canvas was found. The editor or packager DOM may be unsupported.");
|
|
2341
|
+
}
|
|
2342
|
+
validatePreviewAttachment(stage, canvas) {
|
|
2343
|
+
if (!stage || !canvas || canvas.parentElement !== stage) throw new Error("TM: Preview canvas was not attached to the stage.");
|
|
2344
|
+
const canvasStyle = window.getComputedStyle(canvas);
|
|
2345
|
+
if (canvasStyle.display === "none" && this.previewVisible) throw new Error("TM: Preview canvas is hidden by display:none.");
|
|
2346
|
+
if (canvasStyle.visibility === "hidden" && this.previewVisible) throw new Error("TM: Preview canvas is hidden by visibility:hidden.");
|
|
2347
|
+
const stageRect = stage.getBoundingClientRect();
|
|
2348
|
+
const canvasRect = canvas.getBoundingClientRect();
|
|
2349
|
+
const documentHidden = typeof document.visibilityState === "string" && document.visibilityState === "hidden";
|
|
2350
|
+
const layoutUnavailable = stageRect.width === 0 || stageRect.height === 0;
|
|
2351
|
+
if (!documentHidden && !layoutUnavailable && this.previewVisible) {
|
|
2352
|
+
if (canvasRect.width <= 0 || canvasRect.height <= 0) throw new Error("TM: Preview canvas was attached but has zero size.");
|
|
2353
|
+
if (!rectanglesIntersect(stageRect, canvasRect)) throw new Error("TM: Preview canvas does not intersect the stage.");
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
createSvgElement(name) {
|
|
2357
|
+
if (typeof document.createElementNS === "function") return document.createElementNS("http://www.w3.org/2000/svg", name);
|
|
2358
|
+
return document.createElement(name);
|
|
2359
|
+
}
|
|
2360
|
+
ensurePoseOverlayElement() {
|
|
2361
|
+
if (!this.featureFlags.poseOverlay) return null;
|
|
2362
|
+
if (this.poseOverlaySvg) return this.poseOverlaySvg;
|
|
2363
|
+
const svg = this.createSvgElement("svg");
|
|
2364
|
+
svg.setAttribute("viewBox", `0 0 320 240`);
|
|
2365
|
+
svg.setAttribute("width", "320");
|
|
2366
|
+
svg.setAttribute("height", "240");
|
|
2367
|
+
svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
2368
|
+
svg.setAttribute("aria-hidden", "true");
|
|
2369
|
+
svg.setAttribute("focusable", "false");
|
|
2370
|
+
const boneGroup = this.createSvgElement("g");
|
|
2371
|
+
boneGroup.setAttribute("data-layer", "bones");
|
|
2372
|
+
this.poseOverlayBoneElements = POSE_BONE_CONNECTIONS.map(([first, second]) => {
|
|
2373
|
+
const line = this.createSvgElement("line");
|
|
2374
|
+
line.setAttribute("data-bone", `${first}-${second}`);
|
|
2375
|
+
line.setAttribute("stroke-linecap", "round");
|
|
2376
|
+
line.style.display = "none";
|
|
2377
|
+
boneGroup.appendChild(line);
|
|
2378
|
+
return {
|
|
2379
|
+
first,
|
|
2380
|
+
second,
|
|
2381
|
+
line
|
|
2382
|
+
};
|
|
2383
|
+
});
|
|
2384
|
+
svg.appendChild(boneGroup);
|
|
2385
|
+
const jointGroup = this.createSvgElement("g");
|
|
2386
|
+
jointGroup.setAttribute("data-layer", "joints");
|
|
2387
|
+
this.poseOverlayJointElements = /* @__PURE__ */ new Map();
|
|
2388
|
+
for (const part of POSE_KEYPOINT_NAMES) {
|
|
2389
|
+
const circle = this.createSvgElement("circle");
|
|
2390
|
+
circle.setAttribute("data-joint", part);
|
|
2391
|
+
circle.style.display = "none";
|
|
2392
|
+
jointGroup.appendChild(circle);
|
|
2393
|
+
this.poseOverlayJointElements.set(part, circle);
|
|
2394
|
+
}
|
|
2395
|
+
svg.appendChild(jointGroup);
|
|
2396
|
+
this.poseOverlaySvg = svg;
|
|
2397
|
+
return svg;
|
|
2398
|
+
}
|
|
2399
|
+
updatePoseOverlayVisibility() {
|
|
2400
|
+
if (!this.poseOverlaySvg) return;
|
|
2401
|
+
this.poseOverlaySvg.style.display = this.previewVisible && this.poseOverlayVisible ? "block" : "none";
|
|
2402
|
+
}
|
|
2403
|
+
clearPoseOverlay() {
|
|
2404
|
+
this.latestPoseKeypoints = [];
|
|
2405
|
+
for (const circle of this.poseOverlayJointElements.values()) circle.style.display = "none";
|
|
2406
|
+
for (const { line } of this.poseOverlayBoneElements) line.style.display = "none";
|
|
2407
|
+
}
|
|
2408
|
+
redrawPoseOverlay() {
|
|
2409
|
+
if (this.latestPoseKeypoints.length > 0) this.renderPoseOverlay(this.latestPoseKeypoints);
|
|
2410
|
+
}
|
|
2411
|
+
renderPoseOverlay(keypoints) {
|
|
2412
|
+
if (!this.featureFlags.poseOverlay || !this.poseOverlaySvg || !Array.isArray(keypoints)) {
|
|
2413
|
+
this.clearPoseOverlay();
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
this.latestPoseKeypoints = keypoints;
|
|
2417
|
+
const recognized = /* @__PURE__ */ new Map();
|
|
2418
|
+
for (const candidate of keypoints) {
|
|
2419
|
+
if (typeof candidate !== "object" || candidate === null || !isPoseKeypointName(candidate.part)) continue;
|
|
2420
|
+
const keypoint = candidate;
|
|
2421
|
+
const x = Number(keypoint.position?.x);
|
|
2422
|
+
const y = Number(keypoint.position?.y);
|
|
2423
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) continue;
|
|
2424
|
+
recognized.set(keypoint.part, {
|
|
2425
|
+
part: keypoint.part,
|
|
2426
|
+
score: confidenceMultiplier(keypoint.score),
|
|
2427
|
+
position: {
|
|
2428
|
+
x,
|
|
2429
|
+
y
|
|
2430
|
+
}
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
2433
|
+
for (const part of POSE_KEYPOINT_NAMES) {
|
|
2434
|
+
const circle = this.poseOverlayJointElements.get(part);
|
|
2435
|
+
const keypoint = recognized.get(part);
|
|
2436
|
+
if (!circle || !keypoint || keypoint.score < this.poseOverlayMinimumConfidence) {
|
|
2437
|
+
if (circle) circle.style.display = "none";
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
const style = this.poseJointStyles[part];
|
|
2441
|
+
const opacityMultiplier = this.poseConfidenceScaling.jointOpacity ? keypoint.score : 1;
|
|
2442
|
+
const radiusMultiplier = this.poseConfidenceScaling.jointRadius ? keypoint.score : 1;
|
|
2443
|
+
circle.setAttribute("cx", String(keypoint.position.x));
|
|
2444
|
+
circle.setAttribute("cy", String(keypoint.position.y));
|
|
2445
|
+
circle.setAttribute("r", String(style.radius * radiusMultiplier));
|
|
2446
|
+
circle.setAttribute("fill", style.color);
|
|
2447
|
+
circle.setAttribute("fill-opacity", String(style.opacity * opacityMultiplier));
|
|
2448
|
+
circle.style.display = "block";
|
|
2449
|
+
}
|
|
2450
|
+
for (const { first, second, line } of this.poseOverlayBoneElements) {
|
|
2451
|
+
const firstKeypoint = recognized.get(first);
|
|
2452
|
+
const secondKeypoint = recognized.get(second);
|
|
2453
|
+
if (!firstKeypoint || !secondKeypoint || firstKeypoint.score < this.poseOverlayMinimumConfidence || secondKeypoint.score < this.poseOverlayMinimumConfidence) {
|
|
2454
|
+
line.style.display = "none";
|
|
2455
|
+
continue;
|
|
2456
|
+
}
|
|
2457
|
+
const confidence = Math.min(firstKeypoint.score, secondKeypoint.score);
|
|
2458
|
+
const opacityMultiplier = this.poseConfidenceScaling.boneOpacity ? confidence : 1;
|
|
2459
|
+
const widthMultiplier = this.poseConfidenceScaling.boneWidth ? confidence : 1;
|
|
2460
|
+
line.setAttribute("x1", String(firstKeypoint.position.x));
|
|
2461
|
+
line.setAttribute("y1", String(firstKeypoint.position.y));
|
|
2462
|
+
line.setAttribute("x2", String(secondKeypoint.position.x));
|
|
2463
|
+
line.setAttribute("y2", String(secondKeypoint.position.y));
|
|
2464
|
+
line.setAttribute("stroke", this.poseBoneStyle.color);
|
|
2465
|
+
line.setAttribute("stroke-opacity", String(this.poseBoneStyle.opacity * opacityMultiplier));
|
|
2466
|
+
line.setAttribute("stroke-width", String(this.poseBoneStyle.width * widthMultiplier));
|
|
2467
|
+
line.style.display = "block";
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* The element the stage shows.
|
|
2472
|
+
*
|
|
2473
|
+
* The camera's own video, when there is one: it carries the camera's resolution and its aspect,
|
|
2474
|
+
* so the preview stops being a 320x240 readback canvas stretched to fit. The canvas remains the
|
|
2475
|
+
* preview only where there is no video to show, which is the same place inference falls back to
|
|
2476
|
+
* it.
|
|
2477
|
+
*/
|
|
2478
|
+
previewSourceElement() {
|
|
2479
|
+
const video = this.webcam?.webcam;
|
|
2480
|
+
return video && video.videoWidth > 0 ? video : this.webcam?.canvas;
|
|
2481
|
+
}
|
|
2482
|
+
attachPreviewToStage() {
|
|
2483
|
+
if (!this.webcam) throw new Error("TM: Start the camera before attaching the preview.");
|
|
2484
|
+
if (!this.webcam.canvas) throw new Error("TM: webcam.canvas is unavailable.");
|
|
2485
|
+
const stage = this.findStageElement();
|
|
2486
|
+
const canvas = this.previewSourceElement();
|
|
2487
|
+
let stageCanvas = null;
|
|
2488
|
+
try {
|
|
2489
|
+
const candidate = this.findLikelyStageCanvas();
|
|
2490
|
+
if (candidate.parentElement === stage) stageCanvas = candidate;
|
|
2491
|
+
} catch {}
|
|
2492
|
+
this.previewCanvas = canvas;
|
|
2493
|
+
this.previewStageElement = stage;
|
|
2494
|
+
const overlay = this.ensurePoseOverlayElement();
|
|
2495
|
+
if (window.getComputedStyle(stage).position === "static") stage.style.position = "relative";
|
|
2496
|
+
stage.style.overflow = "hidden";
|
|
2497
|
+
Object.assign(canvas.style, {
|
|
2498
|
+
position: "absolute",
|
|
2499
|
+
zIndex: "auto",
|
|
2500
|
+
pointerEvents: "none",
|
|
2501
|
+
border: "2px solid rgba(255, 255, 255, 0.7)",
|
|
2502
|
+
borderRadius: "8px",
|
|
2503
|
+
background: "#000",
|
|
2504
|
+
opacity: String(this.previewOpacity),
|
|
2505
|
+
display: this.previewVisible ? "block" : "none",
|
|
2506
|
+
boxSizing: "border-box"
|
|
2507
|
+
});
|
|
2508
|
+
if (overlay) Object.assign(overlay.style, {
|
|
2509
|
+
position: "absolute",
|
|
2510
|
+
zIndex: "auto",
|
|
2511
|
+
pointerEvents: "none",
|
|
2512
|
+
border: "2px solid transparent",
|
|
2513
|
+
background: "transparent",
|
|
2514
|
+
display: this.previewVisible && this.poseOverlayVisible ? "block" : "none",
|
|
2515
|
+
boxSizing: "border-box",
|
|
2516
|
+
overflow: "hidden"
|
|
2517
|
+
});
|
|
2518
|
+
let insertionPoint = stageCanvas?.nextSibling ?? null;
|
|
2519
|
+
while (insertionPoint && (insertionPoint === canvas || insertionPoint === overlay)) insertionPoint = insertionPoint.nextSibling;
|
|
2520
|
+
if (stageCanvas && typeof stage.insertBefore === "function") stage.insertBefore(canvas, insertionPoint);
|
|
2521
|
+
else if (canvas.parentNode !== stage) stage.appendChild(canvas);
|
|
2522
|
+
if (overlay) {
|
|
2523
|
+
if (stageCanvas && typeof stage.insertBefore === "function") stage.insertBefore(overlay, insertionPoint);
|
|
2524
|
+
else if (overlay.parentNode !== stage) stage.appendChild(overlay);
|
|
2525
|
+
}
|
|
2526
|
+
this.updatePreviewStyle();
|
|
2527
|
+
this.updatePoseOverlayVisibility();
|
|
2528
|
+
this.validatePreviewAttachment(stage, canvas);
|
|
2529
|
+
}
|
|
2530
|
+
/**
|
|
2531
|
+
* Span the overlay over what the preview is showing.
|
|
2532
|
+
*
|
|
2533
|
+
* Keypoints are in the camera frame's space, so the viewBox has to be the frame -- the video's
|
|
2534
|
+
* own width and height, not the readback canvas's fixed 320x240. On the canvas fallback the two
|
|
2535
|
+
* are the same thing.
|
|
2536
|
+
*/
|
|
2537
|
+
updatePoseOverlayViewBox() {
|
|
2538
|
+
const svg = this.poseOverlaySvg;
|
|
2539
|
+
const preview = this.previewCanvas;
|
|
2540
|
+
if (!svg || !preview) return;
|
|
2541
|
+
const width = preview.videoWidth ?? preview.width;
|
|
2542
|
+
const height = preview.videoHeight ?? preview.height;
|
|
2543
|
+
if (!(width > 0) || !(height > 0)) return;
|
|
2544
|
+
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
2545
|
+
}
|
|
2546
|
+
updatePreviewStyle() {
|
|
2547
|
+
const canvas = this.previewCanvas;
|
|
2548
|
+
if (!canvas) throw new Error("TM: Start the camera before positioning the preview.");
|
|
2549
|
+
const targets = [canvas, this.poseOverlaySvg].filter(Boolean);
|
|
2550
|
+
for (const target of targets) Object.assign(target.style, {
|
|
2551
|
+
left: "",
|
|
2552
|
+
right: "",
|
|
2553
|
+
top: "",
|
|
2554
|
+
bottom: "",
|
|
2555
|
+
transform: "",
|
|
2556
|
+
objectFit: "",
|
|
2557
|
+
width: "35%",
|
|
2558
|
+
height: "auto",
|
|
2559
|
+
borderRadius: "8px"
|
|
2560
|
+
});
|
|
2561
|
+
let positionTransform = "";
|
|
2562
|
+
switch (this.previewPosition) {
|
|
2563
|
+
case "top-left":
|
|
2564
|
+
for (const target of targets) {
|
|
2565
|
+
target.style.left = "8px";
|
|
2566
|
+
target.style.top = "8px";
|
|
2567
|
+
}
|
|
2568
|
+
break;
|
|
2569
|
+
case "top-right":
|
|
2570
|
+
for (const target of targets) {
|
|
2571
|
+
target.style.right = "8px";
|
|
2572
|
+
target.style.top = "8px";
|
|
2573
|
+
}
|
|
2574
|
+
break;
|
|
2575
|
+
case "bottom-left":
|
|
2576
|
+
for (const target of targets) {
|
|
2577
|
+
target.style.left = "8px";
|
|
2578
|
+
target.style.bottom = "8px";
|
|
2579
|
+
}
|
|
2580
|
+
break;
|
|
2581
|
+
case "center":
|
|
2582
|
+
for (const target of targets) {
|
|
2583
|
+
target.style.left = "50%";
|
|
2584
|
+
target.style.top = "50%";
|
|
2585
|
+
}
|
|
2586
|
+
positionTransform = "translate(-50%, -50%)";
|
|
2587
|
+
break;
|
|
2588
|
+
case "full-stage":
|
|
2589
|
+
for (const target of targets) {
|
|
2590
|
+
target.style.left = "0";
|
|
2591
|
+
target.style.top = "0";
|
|
2592
|
+
target.style.width = "100%";
|
|
2593
|
+
target.style.height = "100%";
|
|
2594
|
+
target.style.objectFit = "cover";
|
|
2595
|
+
target.style.borderRadius = "0";
|
|
2596
|
+
}
|
|
2597
|
+
break;
|
|
2598
|
+
default: for (const target of targets) {
|
|
2599
|
+
target.style.right = "8px";
|
|
2600
|
+
target.style.bottom = "8px";
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
const mirroringTransform = this.previewCanvas === this.webcam?.webcam ? this.previewMirrored ? "scaleX(-1)" : "" : this.previewMirrored ? "" : "scaleX(-1)";
|
|
2604
|
+
for (const target of targets) target.style.transform = [positionTransform, mirroringTransform].filter(Boolean).join(" ");
|
|
2605
|
+
this.poseOverlaySvg?.setAttribute("preserveAspectRatio", this.previewPosition === "full-stage" ? "xMidYMid slice" : "xMidYMid meet");
|
|
2606
|
+
this.updatePoseOverlayViewBox();
|
|
2607
|
+
}
|
|
2608
|
+
startLoopIfNeeded() {
|
|
2609
|
+
if (this.loopStarted || !this.cameraRunning || !this.webcam) return;
|
|
2610
|
+
this.loopStarted = true;
|
|
2611
|
+
const generation = ++this.loopGeneration;
|
|
2612
|
+
this.loop(generation);
|
|
2613
|
+
}
|
|
2614
|
+
trackPreparedModelOperation(model, operation) {
|
|
2615
|
+
let active = this.activeModelOperations.get(model);
|
|
2616
|
+
if (!active) {
|
|
2617
|
+
active = /* @__PURE__ */ new Set();
|
|
2618
|
+
this.activeModelOperations.set(model, active);
|
|
2619
|
+
}
|
|
2620
|
+
active.add(operation);
|
|
2621
|
+
operation.then(() => {
|
|
2622
|
+
active.delete(operation);
|
|
2623
|
+
if (active.size === 0) this.activeModelOperations.delete(model);
|
|
2624
|
+
}, () => {
|
|
2625
|
+
active.delete(operation);
|
|
2626
|
+
if (active.size === 0) this.activeModelOperations.delete(model);
|
|
2627
|
+
});
|
|
2628
|
+
return operation;
|
|
2629
|
+
}
|
|
2630
|
+
async waitForPreparedModelIdle(model) {
|
|
2631
|
+
await Promise.allSettled([...this.activeModelOperations.get(model) ?? []]);
|
|
2632
|
+
}
|
|
2633
|
+
async loop(generation = this.loopGeneration) {
|
|
2634
|
+
if (generation !== this.loopGeneration || !this.cameraRunning || !this.webcam) {
|
|
2635
|
+
if (generation === this.loopGeneration) this.loopStarted = false;
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2638
|
+
try {
|
|
2639
|
+
if (this.previewSourceElement() !== this.webcam.webcam) this.webcam.update();
|
|
2640
|
+
if (this.recognizing && this.model) {
|
|
2641
|
+
const model = this.model;
|
|
2642
|
+
const recognitionMode = this.recognitionMode;
|
|
2643
|
+
const first = this.firstRecognitionMs === 0;
|
|
2644
|
+
const startedAt = first ? performance.now() : 0;
|
|
2645
|
+
const recognition = await this.trackPreparedModelOperation(model, this.recognizeFrame(model, recognitionMode));
|
|
2646
|
+
if (generation !== this.loopGeneration || !this.cameraRunning || !this.recognizing || this.model !== model || this.recognitionMode !== recognitionMode) {} else {
|
|
2647
|
+
if (first) this.firstRecognitionMs = Math.round(performance.now() - startedAt);
|
|
2648
|
+
this.renderPoseOverlay(recognition.keypoints);
|
|
2649
|
+
this.applyPredictions(recognition.prediction);
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
} catch (error) {
|
|
2653
|
+
this.setLastError(error);
|
|
2654
|
+
}
|
|
2655
|
+
if (generation === this.loopGeneration && this.cameraRunning && this.webcam) requestAnimationFrame(() => void this.loop(generation));
|
|
2656
|
+
else if (generation === this.loopGeneration) this.loopStarted = false;
|
|
2657
|
+
}
|
|
2658
|
+
/**
|
|
2659
|
+
* The square the model looks at, as a tensor, taken straight off the camera.
|
|
2660
|
+
*
|
|
2661
|
+
* The readback canvas is 320x240 and holds a 320x320 centre crop drawn into it, so its bottom
|
|
2662
|
+
* quarter -- 25% of the subject's height -- never reaches the model. Cropping the frame itself
|
|
2663
|
+
* keeps the whole square. The crop and the mirroring both run as tensor ops, which is where the
|
|
2664
|
+
* backend can keep them on the GPU; `fromPixels` uploads a video element directly to a texture
|
|
2665
|
+
* on the WebGL and WebGPU backends, so no Canvas2D stage sits in the path.
|
|
2666
|
+
*
|
|
2667
|
+
* The pixels are mirrored here rather than through `estimatePose(sample, flipHorizontal)`:
|
|
2668
|
+
* that flag flips the returned keypoints, not the image, and the classifier head was trained on
|
|
2669
|
+
* mirrored frames. Flipping the pixels is what keeps the model's input the same as before.
|
|
2670
|
+
*
|
|
2671
|
+
* The WASM and CPU backends have no texture to upload to, so `fromPixels` reads the frame back
|
|
2672
|
+
* through Canvas2D there and this path loses its advantage -- but not its correctness. Driving
|
|
2673
|
+
* one deterministic frame through every backend returns the same probabilities to two decimals
|
|
2674
|
+
* (webgl / wasm / cpu: 0.08, 0.90, 0.02, 0.00) and `tf.memory().numTensors` stays flat over a
|
|
2675
|
+
* six-second run on each, so nothing here depends on which backend `compute-backend.ts` picked.
|
|
2676
|
+
*
|
|
2677
|
+
* Returns null when there is no frame to take, and the canvas path answers instead.
|
|
2678
|
+
*/
|
|
2679
|
+
captureSquareFrame() {
|
|
2680
|
+
const tensorflow = globalThis.tf;
|
|
2681
|
+
const video = this.webcam?.webcam;
|
|
2682
|
+
if (typeof tensorflow?.browser?.fromPixels !== "function" || typeof tensorflow.tidy !== "function" || !video || !(video.videoWidth > 0) || !(video.videoHeight > 0)) return null;
|
|
2683
|
+
const size = Math.min(video.videoWidth, video.videoHeight);
|
|
2684
|
+
const frame = tensorflow.browser.fromPixels(video);
|
|
2685
|
+
try {
|
|
2686
|
+
const top = frame.shape[0] - size >> 1;
|
|
2687
|
+
const left = frame.shape[1] - size >> 1;
|
|
2688
|
+
return {
|
|
2689
|
+
tensor: tensorflow.tidy(() => tensorflow.reverse(tensorflow.slice(frame, [
|
|
2690
|
+
top,
|
|
2691
|
+
left,
|
|
2692
|
+
0
|
|
2693
|
+
], [
|
|
2694
|
+
size,
|
|
2695
|
+
size,
|
|
2696
|
+
3
|
|
2697
|
+
]), [1])),
|
|
2698
|
+
crop: {
|
|
2699
|
+
left,
|
|
2700
|
+
top,
|
|
2701
|
+
size
|
|
2702
|
+
}
|
|
2703
|
+
};
|
|
2704
|
+
} finally {
|
|
2705
|
+
frame.dispose();
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
async recognizeFrame(model, recognitionMode) {
|
|
2709
|
+
if (recognitionMode === "pose") {
|
|
2710
|
+
if (typeof model.estimatePose !== "function") throw new Error("Teachable Machine: The active model does not support pose recognition.");
|
|
2711
|
+
const square = this.captureSquareFrame();
|
|
2712
|
+
if (!square) {
|
|
2713
|
+
this.webcam.update?.();
|
|
2714
|
+
const estimate = await model.estimatePose(this.webcam.canvas);
|
|
2715
|
+
const prediction = await model.predict(estimate.posenetOutput);
|
|
2716
|
+
return {
|
|
2717
|
+
keypoints: estimate.pose?.keypoints,
|
|
2718
|
+
prediction
|
|
2719
|
+
};
|
|
2720
|
+
}
|
|
2721
|
+
try {
|
|
2722
|
+
const estimate = await model.estimatePose(square.tensor);
|
|
2723
|
+
const prediction = await model.predict(estimate.posenetOutput);
|
|
2724
|
+
return {
|
|
2725
|
+
keypoints: mapKeypointsToFrame(estimate.pose?.keypoints, square.crop),
|
|
2726
|
+
prediction
|
|
2727
|
+
};
|
|
2728
|
+
} finally {
|
|
2729
|
+
square.tensor.dispose();
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
const video = this.webcam.webcam;
|
|
2733
|
+
const source = video ?? this.webcam.canvas;
|
|
2734
|
+
return {
|
|
2735
|
+
keypoints: [],
|
|
2736
|
+
prediction: await model.predict(source, Boolean(video))
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2739
|
+
currentPoseReporter() {
|
|
2740
|
+
return this.currentPoseName;
|
|
2741
|
+
}
|
|
2742
|
+
scoreReporter() {
|
|
2743
|
+
return Math.round(this.score * 100) / 100;
|
|
2744
|
+
}
|
|
2745
|
+
poseScoreReporter(args) {
|
|
2746
|
+
const value = this.predictions[String(args.NAME || "")] || 0;
|
|
2747
|
+
return Math.round(value * 100) / 100;
|
|
2748
|
+
}
|
|
2749
|
+
setAccumulatedPoseParameters(args) {
|
|
2750
|
+
const accumulation = args.ACCUMULATION === "" ? 1 : Number(args.ACCUMULATION);
|
|
2751
|
+
const decay = args.DECAY === "" ? .9 : Number(args.DECAY);
|
|
2752
|
+
this.accumulationCoefficient = Number.isFinite(accumulation) ? Math.max(0, accumulation) : 1;
|
|
2753
|
+
this.decayCoefficient = Number.isFinite(decay) ? Math.max(0, Math.min(1, decay)) : .9;
|
|
2754
|
+
}
|
|
2755
|
+
setAccumulatedPoseThreshold(args) {
|
|
2756
|
+
const threshold = args.THRESHOLD === "" ? 0 : Number(args.THRESHOLD);
|
|
2757
|
+
this.accumulatedPoseThreshold = Number.isFinite(threshold) ? Math.max(0, threshold) : 0;
|
|
2758
|
+
this.updateAccumulatedPoseSelection();
|
|
2759
|
+
}
|
|
2760
|
+
startAccumulatedPoseSession(now = performance.now()) {
|
|
2761
|
+
this.activeDecayCoefficient = this.decayCoefficient;
|
|
2762
|
+
this.lastAccumulationTime = now;
|
|
2763
|
+
}
|
|
2764
|
+
supportsAccumulatedPoseEvents() {
|
|
2765
|
+
return this.featureFlags.temporalPoseScoring && this.featureFlags.accumulatedPoseEvents;
|
|
2766
|
+
}
|
|
2767
|
+
emitAccumulatedPoseChanged(previousPoseName, reason) {
|
|
2768
|
+
if (!this.supportsAccumulatedPoseEvents() || previousPoseName === this.accumulatedPoseName) return;
|
|
2769
|
+
const payload = {
|
|
2770
|
+
version: 2,
|
|
2771
|
+
poseName: this.accumulatedPoseName,
|
|
2772
|
+
previousPoseName,
|
|
2773
|
+
score: this.accumulatedScore,
|
|
2774
|
+
reason,
|
|
2775
|
+
timestamp: performance.now()
|
|
2776
|
+
};
|
|
2777
|
+
if (this.onAccumulatedPoseChanged) try {
|
|
2778
|
+
this.onAccumulatedPoseChanged(payload);
|
|
2779
|
+
} catch {}
|
|
2780
|
+
else if (typeof Scratch !== "undefined") Scratch.vm?.runtime?.emit(ACCUMULATED_POSE_CHANGED_EVENT, payload);
|
|
2781
|
+
}
|
|
2782
|
+
dispose() {
|
|
2783
|
+
this.fixedEditors.dispose();
|
|
2784
|
+
this.stopCamera();
|
|
2785
|
+
if (this.featureFlags.temporalPoseScoring && typeof document !== "undefined") document.removeEventListener("visibilitychange", this.visibilityChangeListener);
|
|
2786
|
+
this.onAccumulatedPoseChanged = null;
|
|
2787
|
+
}
|
|
2788
|
+
resetAccumulatedPose(reason = "reset") {
|
|
2789
|
+
const previousPoseName = this.accumulatedPoseName;
|
|
2790
|
+
this.accumulatedPoseName = "";
|
|
2791
|
+
this.accumulatedScore = 0;
|
|
2792
|
+
this.accumulatedPredictions = {};
|
|
2793
|
+
this.lastAccumulationTime = this.recognizing ? performance.now() : null;
|
|
2794
|
+
this.emitAccumulatedPoseChanged(previousPoseName, reason);
|
|
2795
|
+
}
|
|
2796
|
+
handleDocumentVisibilityChange() {
|
|
2797
|
+
if (typeof document === "undefined") return;
|
|
2798
|
+
if (isDocumentHidden()) {
|
|
2799
|
+
this.accumulatedPosePausedForBackground = true;
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2802
|
+
if (this.accumulatedPosePausedForBackground) {
|
|
2803
|
+
this.accumulatedPosePausedForBackground = false;
|
|
2804
|
+
this.lastAccumulationTime = this.recognizing ? performance.now() : null;
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
updateAccumulatedPose(prediction, now = performance.now()) {
|
|
2808
|
+
if (isDocumentHidden()) {
|
|
2809
|
+
this.accumulatedPosePausedForBackground = true;
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
if (this.accumulatedPosePausedForBackground) return;
|
|
2813
|
+
const elapsedSeconds = this.lastAccumulationTime === null ? 0 : Math.max(0, (now - this.lastAccumulationTime) / 1e3);
|
|
2814
|
+
const decayMultiplier = elapsedSeconds === 0 ? 1 : Math.pow(this.activeDecayCoefficient, elapsedSeconds);
|
|
2815
|
+
for (const name of Object.keys(this.accumulatedPredictions)) this.accumulatedPredictions[name] *= decayMultiplier;
|
|
2816
|
+
for (const result of prediction) {
|
|
2817
|
+
const name = String(result.className || "");
|
|
2818
|
+
const probability = Number(result.probability);
|
|
2819
|
+
if (!name || !Number.isFinite(probability)) continue;
|
|
2820
|
+
const contribution = Math.max(0, Math.min(1, probability)) * this.accumulationCoefficient * elapsedSeconds;
|
|
2821
|
+
this.accumulatedPredictions[name] = (this.accumulatedPredictions[name] || 0) + contribution;
|
|
2822
|
+
}
|
|
2823
|
+
this.lastAccumulationTime = now;
|
|
2824
|
+
this.updateAccumulatedPoseSelection();
|
|
2825
|
+
}
|
|
2826
|
+
updateAccumulatedPoseSelection() {
|
|
2827
|
+
const previousPoseName = this.accumulatedPoseName;
|
|
2828
|
+
this.accumulatedPoseName = "";
|
|
2829
|
+
this.accumulatedScore = 0;
|
|
2830
|
+
let bestPoseName = "";
|
|
2831
|
+
for (const [name, value] of Object.entries(this.accumulatedPredictions)) if (value > this.accumulatedScore) {
|
|
2832
|
+
bestPoseName = name;
|
|
2833
|
+
this.accumulatedScore = value;
|
|
2834
|
+
}
|
|
2835
|
+
if (bestPoseName && this.accumulatedScore >= this.accumulatedPoseThreshold) this.accumulatedPoseName = bestPoseName;
|
|
2836
|
+
this.emitAccumulatedPoseChanged(previousPoseName, "recognition");
|
|
2837
|
+
}
|
|
2838
|
+
accumulatedPoseReporter() {
|
|
2839
|
+
return this.accumulatedPoseName;
|
|
2840
|
+
}
|
|
2841
|
+
accumulatedScoreReporter() {
|
|
2842
|
+
return this.accumulatedScore;
|
|
2843
|
+
}
|
|
2844
|
+
accumulatedPoseScoreReporter(args) {
|
|
2845
|
+
return this.accumulatedPredictions[String(args.NAME || "")] || 0;
|
|
2846
|
+
}
|
|
2847
|
+
isPose(args) {
|
|
2848
|
+
return (this.predictions[String(args.NAME || "")] || 0) >= .75;
|
|
2849
|
+
}
|
|
2850
|
+
isPoseWithThreshold(args) {
|
|
2851
|
+
const name = String(args.NAME || "");
|
|
2852
|
+
let threshold = args.THRESHOLD === "" ? .75 : Number(args.THRESHOLD);
|
|
2853
|
+
if (Number.isNaN(threshold)) threshold = .75;
|
|
2854
|
+
threshold = Math.max(0, Math.min(1, threshold));
|
|
2855
|
+
return (this.predictions[name] || 0) >= threshold;
|
|
2856
|
+
}
|
|
2857
|
+
cameraMsReporter() {
|
|
2858
|
+
return this.cameraMs;
|
|
2859
|
+
}
|
|
2860
|
+
modelLoadMsReporter() {
|
|
2861
|
+
return this.modelLoadMs;
|
|
2862
|
+
}
|
|
2863
|
+
firstRecognitionMsReporter() {
|
|
2864
|
+
return this.firstRecognitionMs;
|
|
2865
|
+
}
|
|
2866
|
+
lastErrorReporter() {
|
|
2867
|
+
return this.lastError;
|
|
2868
|
+
}
|
|
2174
2869
|
};
|
|
2175
2870
|
//#endregion
|
|
2176
2871
|
//#region src/index.ts
|