soiz1 commited on
Commit
1cfb0b0
·
verified ·
1 Parent(s): 99daed9

Create extensions/scratch3_facemesh2scratch/index.js

Browse files
local-scratch-vm/src/extensions/scratch3_facemesh2scratch/index.js ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const ArgumentType = require('../../extension-support/argument-type');
2
+ const BlockType = require('../../extension-support/block-type');
3
+ const Cast = require('../../util/cast');
4
+ const formatMessage = require('format-message');
5
+ const ml5 = require('ml5');
6
+
7
+ const blockIconURI = 'data:image/png;base64,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';
8
+
9
+ const Message = {
10
+ getX: {
11
+ 'ja': '[PERSON_NUMBER] 人目の [KEYPOINT] 番目の部位のx座標',
12
+ 'ja-Hira': '[PERSON_NUMBER] にんめの [KEYPOINT] ばんめのぶいのxざひょう',
13
+ 'en': 'x of person no: [PERSON_NUMBER] , keypoint no: [KEYPOINT]'
14
+ },
15
+ getY: {
16
+ 'ja': '[PERSON_NUMBER] 人目の [KEYPOINT] 番目の部位のy座標',
17
+ 'ja-Hira': '[PERSON_NUMBER] にんめの [KEYPOINT] ばんめのぶいのyざひょう',
18
+ 'en': 'y of person no: [PERSON_NUMBER] , keypoint no: [KEYPOINT]'
19
+ },
20
+ peopleCount: {
21
+ 'ja': '人数',
22
+ 'ja-Hira': 'にんずう',
23
+ 'en': 'people count'
24
+ },
25
+ videoToggle: {
26
+ 'ja': 'ビデオを [VIDEO_STATE] にする',
27
+ 'ja-Hira': 'ビデオを [VIDEO_STATE] にする',
28
+ 'en': 'turn video [VIDEO_STATE]'
29
+ },
30
+ setRatio: {
31
+ 'ja': '倍率を [RATIO] にする',
32
+ 'ja-Hira': 'ばいりつを [RATIO] にする',
33
+ 'en': 'set ratio to [RATIO]'
34
+ },
35
+ setInterval: {
36
+ 'ja': '認識を [INTERVAL] 秒ごとに行う',
37
+ 'ja-Hira': 'にんしきを [INTERVAL] びょうごとにおこなう',
38
+ 'en': 'Label once every [INTERVAL] seconds'
39
+ },
40
+ on: {
41
+ 'ja': '入',
42
+ 'ja-Hira': 'いり',
43
+ 'en': 'on'
44
+ },
45
+ off: {
46
+ 'ja': '切',
47
+ 'ja-Hira': 'きり',
48
+ 'en': 'off'
49
+ },
50
+ video_on_flipped: {
51
+ 'ja': '左右反転',
52
+ 'ja-Hira': 'さゆうはんてん',
53
+ 'en': 'on flipped',
54
+ },
55
+ please_wait: {
56
+ 'ja': '準備に時間がかかります。少しの間、操作ができなくなりますがお待ち下さい。',
57
+ 'ja-Hira': 'じゅんびにじかんがかかります。すこしのあいだ、そうさができなくなりますがおまちください。',
58
+ 'en': 'Setup takes a while. The browser will get stuck, but please wait.'
59
+ }
60
+ }
61
+ const AvailableLocales = ['en', 'ja', 'ja-Hira'];
62
+
63
+ class Scratch3Facemesh2ScratchBlocks {
64
+ get PERSON_NUMBER_MENU () {
65
+ let person_number_menu = []
66
+ for (let i = 1; i <= 10; i++) {
67
+ person_number_menu.push({text: String(i), value: String(i)})
68
+ }
69
+ return person_number_menu;
70
+ }
71
+
72
+ get KEYPOINT_MENU () {
73
+ let keypoint_menu = [];
74
+ for (let i = 1; i <= 468; i++) {
75
+ keypoint_menu.push({text: String(i), value: String(i)})
76
+ }
77
+ return keypoint_menu;
78
+ }
79
+
80
+ get VIDEO_MENU () {
81
+ return [
82
+ {
83
+ text: Message.off[this._locale],
84
+ value: 'off'
85
+ },
86
+ {
87
+ text: Message.on[this._locale],
88
+ value: 'on'
89
+ },
90
+ {
91
+ text: Message.video_on_flipped[this._locale],
92
+ value: 'on-flipped'
93
+ }
94
+ ]
95
+ }
96
+
97
+ get INTERVAL_MENU () {
98
+ return [
99
+ {
100
+ text: '0.1',
101
+ value: '0.1'
102
+ },
103
+ {
104
+ text: '0.2',
105
+ value: '0.2'
106
+ },
107
+ {
108
+ text: '0.5',
109
+ value: '0.5'
110
+ },
111
+ {
112
+ text: '1.0',
113
+ value: '1.0'
114
+ }
115
+ ]
116
+ }
117
+
118
+ get RATIO_MENU () {
119
+ return [
120
+ {
121
+ text: '0.5',
122
+ value: '0.5'
123
+ },
124
+ {
125
+ text: '0.75',
126
+ value: '0.75'
127
+ },
128
+ {
129
+ text: '1',
130
+ value: '1'
131
+ },
132
+ {
133
+ text: '1.5',
134
+ value: '1.5'
135
+ },
136
+ {
137
+ text: '2.0',
138
+ value: '2.0'
139
+ }
140
+ ]
141
+ }
142
+
143
+ constructor (runtime) {
144
+ this.runtime = runtime;
145
+
146
+ this.faces = [];
147
+ this.ratio = 0.75;
148
+
149
+ this.detectFace = () => {
150
+ // We should reuse the video element created by videoProvider instead of creating a new video element
151
+ // This is because iOS or iPad does not allow camera attached to two video elements
152
+ this.video = this.runtime.ioDevices.video.provider.video
153
+
154
+ alert(Message.please_wait[this._locale]);
155
+
156
+
157
+ this.facemesh = ml5.facemesh(this.video, function() {
158
+ console.log("Model loaded!")
159
+ });
160
+
161
+ this.facemesh.on('predict', faces => {
162
+ if (faces.length < this.faces.length) {
163
+ this.faces.splice(faces.length);
164
+ }
165
+ faces.forEach((face, index) => {
166
+ this.faces[index] = {keypoints: face.scaledMesh};
167
+ });
168
+ });
169
+ }
170
+
171
+ this.runtime.ioDevices.video.enableVideo().then(this.detectFace)
172
+ }
173
+
174
+ getInfo () {
175
+ this._locale = this.setLocale();
176
+
177
+ return {
178
+ id: 'facemesh2scratch',
179
+ name: 'Facemesh2Scratch',
180
+ blockIconURI: blockIconURI,
181
+ blocks: [
182
+ {
183
+ opcode: 'getX',
184
+ blockType: BlockType.REPORTER,
185
+ text: Message.getX[this._locale],
186
+ arguments: {
187
+ PERSON_NUMBER: {
188
+ type: ArgumentType.STRING,
189
+ menu: 'personNumberMenu',
190
+ defaultValue: '1'
191
+ },
192
+ KEYPOINT: {
193
+ type: ArgumentType.STRING,
194
+ menu: 'keypointMenu',
195
+ defaultValue: '1'
196
+ }
197
+ }
198
+ },
199
+ {
200
+ opcode: 'getY',
201
+ blockType: BlockType.REPORTER,
202
+ text: Message.getY[this._locale],
203
+ arguments: {
204
+ PERSON_NUMBER: {
205
+ type: ArgumentType.STRING,
206
+ menu: 'personNumberMenu',
207
+ defaultValue: '1'
208
+ },
209
+ KEYPOINT: {
210
+ type: ArgumentType.STRING,
211
+ menu: 'keypointMenu',
212
+ defaultValue: '1'
213
+ }
214
+ }
215
+ },
216
+ {
217
+ opcode: 'getPeopleCount',
218
+ blockType: BlockType.REPORTER,
219
+ text: Message.peopleCount[this._locale]
220
+ },
221
+ {
222
+ opcode: 'videoToggle',
223
+ blockType: BlockType.COMMAND,
224
+ text: Message.videoToggle[this._locale],
225
+ arguments: {
226
+ VIDEO_STATE: {
227
+ type: ArgumentType.STRING,
228
+ menu: 'videoMenu',
229
+ defaultValue: 'off'
230
+ }
231
+ }
232
+ },
233
+ {
234
+ opcode: 'setVideoTransparency',
235
+ text: formatMessage({
236
+ id: 'videoSensing.setVideoTransparency',
237
+ default: 'set video transparency to [TRANSPARENCY]',
238
+ description: 'Controls transparency of the video preview layer'
239
+ }),
240
+ arguments: {
241
+ TRANSPARENCY: {
242
+ type: ArgumentType.NUMBER,
243
+ defaultValue: 50
244
+ }
245
+ }
246
+ },
247
+ {
248
+ opcode: 'setRatio',
249
+ blockType: BlockType.COMMAND,
250
+ text: Message.setRatio[this._locale],
251
+ arguments: {
252
+ RATIO: {
253
+ type: ArgumentType.STRING,
254
+ menu: 'ratioMenu',
255
+ defaultValue: '0.75'
256
+ }
257
+ }
258
+ }
259
+ ],
260
+ menus: {
261
+ personNumberMenu: {
262
+ acceptReporters: true,
263
+ items: this.PERSON_NUMBER_MENU
264
+ },
265
+ keypointMenu: {
266
+ acceptReporters: true,
267
+ items: this.KEYPOINT_MENU
268
+ },
269
+ videoMenu: {
270
+ acceptReporters: true,
271
+ items: this.VIDEO_MENU
272
+ },
273
+ ratioMenu: {
274
+ acceptReporters: true,
275
+ items: this.RATIO_MENU
276
+ },
277
+ intervalMenu: {
278
+ acceptReporters: true,
279
+ items: this.INTERVAL_MENU
280
+ }
281
+ }
282
+ };
283
+ }
284
+
285
+ getX (args) {
286
+ let personNumber = parseInt(args.PERSON_NUMBER, 10) - 1;
287
+ let keypoint = parseInt(args.KEYPOINT, 10) - 1;
288
+
289
+ if (this.faces[personNumber].keypoints && this.faces[personNumber].keypoints[keypoint]) {
290
+ if (this.runtime.ioDevices.video.mirror === false) {
291
+ return -1 * (240 - this.faces[personNumber].keypoints[keypoint][0] * this.ratio);
292
+ } else {
293
+ return 240 - this.faces[personNumber].keypoints[keypoint][0] * this.ratio;
294
+ }
295
+ } else {
296
+ return "";
297
+ }
298
+ }
299
+
300
+ getY (args) {
301
+ let personNumber = parseInt(args.PERSON_NUMBER, 10) - 1;
302
+ let keypoint = parseInt(args.KEYPOINT, 10) - 1;
303
+
304
+ if (this.faces[personNumber].keypoints && this.faces[personNumber].keypoints[keypoint]) {
305
+ return 180 - this.faces[personNumber].keypoints[keypoint][1] * this.ratio;
306
+ } else {
307
+ return "";
308
+ }
309
+ }
310
+
311
+ getPeopleCount () {
312
+ return this.faces.length;
313
+ }
314
+
315
+ videoToggle (args) {
316
+ let state = args.VIDEO_STATE;
317
+ if (state === 'off') {
318
+ this.runtime.ioDevices.video.disableVideo();
319
+ this.facemesh.video = null; // Stop the model prediction if video is off
320
+ } else {
321
+ this.facemesh.removeAllListeners('predict');
322
+ this.runtime.ioDevices.video.enableVideo().then(this.detectFace);
323
+ this.runtime.ioDevices.video.mirror = state === "on";
324
+ }
325
+ }
326
+
327
+ /**
328
+ * A scratch command block handle that configures the video preview's
329
+ * transparency from passed arguments.
330
+ * @param {object} args - the block arguments
331
+ * @param {number} args.TRANSPARENCY - the transparency to set the video
332
+ * preview to
333
+ */
334
+ setVideoTransparency (args) {
335
+ const transparency = Cast.toNumber(args.TRANSPARENCY);
336
+ this.globalVideoTransparency = transparency;
337
+ this.runtime.ioDevices.video.setPreviewGhost(transparency);
338
+ }
339
+
340
+ setRatio (args) {
341
+ this.ratio = parseFloat(args.RATIO);
342
+ }
343
+
344
+ setLocale() {
345
+ let locale = formatMessage.setup().locale;
346
+ if (AvailableLocales.includes(locale)) {
347
+ return locale;
348
+ } else {
349
+ return 'en';
350
+ }
351
+ }
352
+ }
353
+
354
+ module.exports = Scratch3Facemesh2ScratchBlocks;