remote-calibrator 0.2.1 → 0.2.2-beta.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +5 -1
  3. package/homepage/example.js +13 -12
  4. package/lib/RemoteCalibrator.min.js +1 -1
  5. package/lib/RemoteCalibrator.min.js.LICENSE.txt +8 -1
  6. package/lib/RemoteCalibrator.min.js.map +1 -1
  7. package/package.json +10 -9
  8. package/src/WebGazer4RC/.gitattributes +10 -0
  9. package/src/WebGazer4RC/LICENSE.md +15 -0
  10. package/src/WebGazer4RC/README.md +142 -0
  11. package/src/WebGazer4RC/gnu-lgpl-v3.0.md +163 -0
  12. package/src/WebGazer4RC/gplv3.md +636 -0
  13. package/src/WebGazer4RC/package-lock.json +1133 -0
  14. package/src/WebGazer4RC/package.json +28 -0
  15. package/src/WebGazer4RC/src/dom_util.mjs +27 -0
  16. package/src/WebGazer4RC/src/facemesh.mjs +150 -0
  17. package/src/WebGazer4RC/src/index.mjs +1213 -0
  18. package/src/WebGazer4RC/src/mat.mjs +301 -0
  19. package/src/WebGazer4RC/src/params.mjs +29 -0
  20. package/src/WebGazer4RC/src/pupil.mjs +109 -0
  21. package/src/WebGazer4RC/src/ridgeReg.mjs +104 -0
  22. package/src/WebGazer4RC/src/ridgeRegThreaded.mjs +161 -0
  23. package/src/WebGazer4RC/src/ridgeWeightedReg.mjs +125 -0
  24. package/src/WebGazer4RC/src/ridgeWorker.mjs +135 -0
  25. package/src/WebGazer4RC/src/util.mjs +348 -0
  26. package/src/WebGazer4RC/src/util_regression.mjs +240 -0
  27. package/src/WebGazer4RC/src/worker_scripts/mat.js +306 -0
  28. package/src/WebGazer4RC/src/worker_scripts/util.js +398 -0
  29. package/src/WebGazer4RC/test/regression_test.js +182 -0
  30. package/src/WebGazer4RC/test/run_tests_and_server.sh +24 -0
  31. package/src/WebGazer4RC/test/util_test.js +60 -0
  32. package/src/WebGazer4RC/test/webgazerExtract_test.js +40 -0
  33. package/src/WebGazer4RC/test/webgazer_test.js +160 -0
  34. package/src/WebGazer4RC/test/www_page_test.js +41 -0
  35. package/src/components/onCanvas.js +1 -2
  36. package/src/components/sound.js +12 -0
  37. package/src/const.js +8 -0
  38. package/src/core.js +36 -0
  39. package/src/css/distance.scss +1 -0
  40. package/src/css/main.css +11 -0
  41. package/src/css/panel.scss +1 -0
  42. package/src/distance/distance.js +8 -1
  43. package/src/distance/distanceTrack.js +2 -2
  44. package/src/index.js +2 -0
  45. package/src/panel/panel.js +53 -21
  46. package/src/screenSize.js +2 -1
  47. package/src/text.json +5 -3
@@ -0,0 +1,1213 @@
1
+ /* eslint-disable */
2
+ // Search LOOP for the loop function.
3
+
4
+ import '@tensorflow/tfjs';
5
+ //import(/* webpackPreload: true */ '@tensorflow/tfjs');
6
+ //import(/* webpackChunkName: 'pageA' */ './vendors~main.js')
7
+
8
+ import 'regression';
9
+ import params from './params.mjs';
10
+ import './dom_util.mjs';
11
+ import localforage from 'localforage';
12
+ import TFFaceMesh from './facemesh.mjs';
13
+ import Reg from './ridgeReg.mjs';
14
+ import ridgeRegWeighted from './ridgeWeightedReg.mjs';
15
+ import ridgeRegThreaded from './ridgeRegThreaded.mjs';
16
+ import util from './util.mjs';
17
+
18
+ const webgazer = {};
19
+ webgazer.tracker = {};
20
+ webgazer.tracker.TFFaceMesh = TFFaceMesh;
21
+ webgazer.reg = Reg;
22
+ webgazer.reg.RidgeWeightedReg = ridgeRegWeighted.RidgeWeightedReg;
23
+ webgazer.reg.RidgeRegThreaded = ridgeRegThreaded.RidgeRegThreaded;
24
+ webgazer.util = util;
25
+ webgazer.params = params;
26
+
27
+ //PRIVATE VARIABLES
28
+
29
+ //video elements
30
+ var videoStream = null;
31
+ var videoContainerElement = null;
32
+ var videoElement = null;
33
+ var videoElementCanvas = null;
34
+ var faceOverlay = null;
35
+ var faceFeedbackBox = null;
36
+ var gazeDot = null;
37
+ // Why is this not in webgazer.params ?
38
+ var debugVideoLoc = '';
39
+
40
+ /*
41
+ * Initialises variables used to store accuracy eigenValues
42
+ * This is used by the calibration example file
43
+ */
44
+ var xPast50 = new Array(50);
45
+ var yPast50 = new Array(50);
46
+
47
+ // loop parameters
48
+ var clockStart = performance.now();
49
+ var latestEyeFeatures = null;
50
+ var latestGazeData = null;
51
+ /* -------------------------------------------------------------------------- */
52
+ webgazer.params.paused = false;
53
+
54
+ webgazer.params.greedyLearner = false;
55
+ webgazer.params.framerate = 60;
56
+ webgazer.params.showGazeDot = false
57
+ /* -------------------------------------------------------------------------- */
58
+ // registered callback for loop
59
+ var nopCallback = function(data) {};
60
+ var callback = nopCallback;
61
+
62
+ let learning = false // Regression
63
+
64
+ //Types that regression systems should handle
65
+ //Describes the source of data so that regression systems may ignore or handle differently the various generating events
66
+ var eventTypes = ['click', 'move'];
67
+
68
+ //movelistener timeout clock parameters
69
+ var moveClock = performance.now();
70
+ //currently used tracker and regression models, defaults to clmtrackr and linear regression
71
+ var curTracker = new webgazer.tracker.TFFaceMesh();
72
+ var regs = [new webgazer.reg.RidgeReg()];
73
+ // var blinkDetector = new webgazer.BlinkDetector();
74
+
75
+ //lookup tables
76
+ var curTrackerMap = {
77
+ 'TFFacemesh': function() { return new webgazer.tracker.TFFaceMesh(); },
78
+ };
79
+ var regressionMap = {
80
+ 'ridge': function() { return new webgazer.reg.RidgeReg(); },
81
+ 'weightedRidge': function() { return new webgazer.reg.RidgeWeightedReg(); },
82
+ 'threadedRidge': function() { return new webgazer.reg.RidgeRegThreaded(); },
83
+ };
84
+
85
+ //localstorage name
86
+ var localstorageDataLabel = 'webgazerGlobalData';
87
+ var localstorageSettingsLabel = 'webgazerGlobalSettings';
88
+ //settings object for future storage of settings
89
+ var settings = {};
90
+ var data = [];
91
+ var defaults = {
92
+ 'data': [],
93
+ 'settings': {}
94
+ };
95
+
96
+
97
+ //PRIVATE FUNCTIONS
98
+
99
+ /**
100
+ * Computes the size of the face overlay validation box depending on the size of the video preview window.
101
+ * @returns {Object} The dimensions of the validation box as top, left, width, height.
102
+ */
103
+ webgazer.computeValidationBoxSize = function() {
104
+
105
+ var vw = videoElement.videoWidth;
106
+ var vh = videoElement.videoHeight;
107
+ var pw = parseInt(videoElement.style.width);
108
+ var ph = parseInt(videoElement.style.height);
109
+
110
+ // Find the size of the box.
111
+ // Pick the smaller of the two video preview sizes
112
+ var smaller = Math.min( vw, vh );
113
+ var larger = Math.max( vw, vh );
114
+
115
+ // Overall scalar
116
+ var scalar = ( vw == larger ? pw / vw : ph / vh );
117
+
118
+ // Multiply this by 2/3, then adjust it to the size of the preview
119
+ var boxSize = (smaller * webgazer.params.faceFeedbackBoxRatio) * scalar;
120
+
121
+ // Set the boundaries of the face overlay validation box based on the preview
122
+ var topVal = (ph - boxSize)/2;
123
+ var leftVal = (pw - boxSize)/2;
124
+
125
+ // top, left, width, height
126
+ return [topVal, leftVal, boxSize, boxSize]
127
+ }
128
+
129
+ let _w, _h, _smaller, _boxSize, _topBound, _leftBound, _rightBound, _bottomBound
130
+ let _eyeLX, _eyeLY, _eyeRX, _eyeRY
131
+ let hasBounds = false
132
+ let gettingBounds = false
133
+
134
+ function _helper_getBounds() {
135
+ gettingBounds = true
136
+ setTimeout(() => {
137
+ _w = videoElement.videoWidth
138
+ _h = videoElement.videoHeight
139
+
140
+ // Find the size of the box.
141
+ // Pick the smaller of the two video preview sizes
142
+ _smaller = Math.min(_w, _h)
143
+ _boxSize = _smaller * webgazer.params.faceFeedbackBoxRatio
144
+
145
+ // Set the boundaries of the face overlay validation box based on the preview
146
+ _topBound = (_h - _boxSize) / 2
147
+ _leftBound = (_w - _boxSize) / 2
148
+ _rightBound = _leftBound + _boxSize
149
+ _bottomBound = _topBound + _boxSize
150
+
151
+ hasBounds = true
152
+ gettingBounds = false
153
+ }, 500)
154
+ }
155
+
156
+ // TODO WebGazer doesn't provide correct validation feedback
157
+ /**
158
+ * Checks if the pupils are in the position box on the video
159
+ */
160
+ function checkEyesInValidationBox() {
161
+ if (faceFeedbackBox !== null && latestEyeFeatures) {
162
+ if (!hasBounds && !gettingBounds) _helper_getBounds()
163
+
164
+ // Get the x and y positions of the left and right eyes
165
+ _eyeLX = _w - latestEyeFeatures.left.imagex
166
+ _eyeRX = _w - latestEyeFeatures.right.imagex
167
+ _eyeLY = latestEyeFeatures.left.imagey
168
+ _eyeRY = latestEyeFeatures.right.imagey
169
+
170
+ if (
171
+ hasBounds &&
172
+ _eyeLX > _leftBound &&
173
+ _eyeLX < _rightBound &&
174
+ _eyeRX > _leftBound &&
175
+ _eyeRX < _rightBound &&
176
+ _eyeLY > _topBound &&
177
+ _eyeLY < _bottomBound &&
178
+ _eyeRY > _topBound &&
179
+ _eyeRY < _bottomBound
180
+ ) {
181
+ faceFeedbackBox.style.border = 'solid gray 2px'
182
+ } else {
183
+ faceFeedbackBox.style.border = 'solid red 4px'
184
+ }
185
+ } else faceFeedbackBox.style.border = 'solid red 4px'
186
+ }
187
+
188
+ /**
189
+ * This draws the point (x,y) onto the canvas in the HTML
190
+ * @param {colour} colour - The colour of the circle to plot
191
+ * @param {x} x - The x co-ordinate of the desired point to plot
192
+ * @param {y} y - The y co-ordinate of the desired point to plot
193
+ */
194
+ function drawCoordinates(colour, x, y) {
195
+ var ctx = document.getElementById("gaze-accuracy-canvas").getContext('2d');
196
+ ctx.fillStyle = colour; // Red color
197
+ ctx.beginPath();
198
+ ctx.arc(x, y, 5, 0, Math.PI * 2, true);
199
+ ctx.fill();
200
+ }
201
+
202
+ /**
203
+ * Gets the pupil features by following the pipeline which threads an eyes object through each call:
204
+ * curTracker gets eye patches -> blink detector -> pupil detection
205
+ * @param {Canvas} canvas - a canvas which will have the video drawn onto it
206
+ * @param {Number} width - the width of canvas
207
+ * @param {Number} height - the height of canvas
208
+ */
209
+ function getPupilFeatures(canvas, width, height) {
210
+ if (!canvas) {
211
+ return;
212
+ }
213
+ try {
214
+ return curTracker.getEyePatches(canvas, width, height);
215
+ } catch(err) {
216
+ console.log("can't get pupil features ", err);
217
+ return null;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Gets the most current frame of video and paints it to a resized version of the canvas with width and height
223
+ * @param {Canvas} canvas - the canvas to paint the video on to
224
+ * @param {Number} width - the new width of the canvas
225
+ * @param {Number} height - the new height of the canvas
226
+ */
227
+ function paintCurrentFrame(canvas, width, height) {
228
+ if (canvas.width !== width) {
229
+ canvas.width = width;
230
+ }
231
+ if (canvas.height !== height) {
232
+ canvas.height = height;
233
+ }
234
+
235
+ var ctx = canvas.getContext('2d');
236
+ ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
237
+ }
238
+
239
+ /**
240
+ * Paints the video to a canvas and runs the prediction pipeline to get a prediction
241
+ * @param {Number|undefined} regModelIndex - The prediction index we're looking for
242
+ * @returns {*}
243
+ */
244
+ async function getPrediction(regModelIndex) {
245
+ var predictions = [];
246
+
247
+ if (regs.length === 0) {
248
+ console.log('regression not set, call setRegression()');
249
+ return null;
250
+ }
251
+ for (var reg in regs) {
252
+ predictions.push(regs[reg].predict(latestEyeFeatures));
253
+ }
254
+ if (regModelIndex !== undefined) {
255
+ return predictions[regModelIndex] === null ? null : {
256
+ 'x' : predictions[regModelIndex].x,
257
+ 'y' : predictions[regModelIndex].y,
258
+ 'eyeFeatures': latestEyeFeatures
259
+ };
260
+ } else {
261
+ return predictions.length === 0 || predictions[0] === null ? null : {
262
+ 'x' : predictions[0].x,
263
+ 'y' : predictions[0].y,
264
+ 'eyeFeatures': latestEyeFeatures,
265
+ 'all' : predictions
266
+ };
267
+ }
268
+ }
269
+
270
+ /* -------------------------------------------------------------------------- */
271
+ /* LOOP */
272
+ /* -------------------------------------------------------------------------- */
273
+
274
+ /**
275
+ * Runs every available animation frame if webgazer is not paused
276
+ */
277
+ var smoothingVals = new util.DataWindow(4);
278
+ var k = 0;
279
+
280
+ let _now = null
281
+ let _last = -1
282
+
283
+ async function loop() {
284
+ _now = window.performance.now()
285
+
286
+ if (webgazer.params.videoIsOn) {
287
+ // [20200617 XK] TODO: there is currently lag between the camera input and the face overlay. This behavior
288
+ // is not seen in the facemesh demo. probably need to optimize async implementation. I think the issue lies
289
+ // in the implementation of getPrediction().
290
+
291
+ // Paint the latest video frame into the canvas which will be analyzed by WebGazer
292
+ // [20180729 JT] Why do we need to do this? clmTracker does this itself _already_, which is just duplicating the work.
293
+ // Is it because other trackers need a canvas instead of an img/video element?
294
+ paintCurrentFrame(videoElementCanvas, videoElementCanvas.width, videoElementCanvas.height);
295
+
296
+ // [20200617 xk] TODO: this call should be made async somehow. will take some work.
297
+ latestEyeFeatures = await getPupilFeatures(videoElementCanvas, videoElementCanvas.width, videoElementCanvas.height);
298
+
299
+ // Draw face overlay
300
+ if (webgazer.params.showFaceOverlay) {
301
+ // Get tracker object
302
+ var tracker = webgazer.getTracker();
303
+ faceOverlay.getContext('2d').clearRect(0, 0, videoElement.videoWidth, videoElement.videoHeight);
304
+ tracker.drawFaceOverlay(faceOverlay.getContext('2d'), tracker.getPositions());
305
+ }
306
+
307
+ // Feedback box
308
+ // Check that the eyes are inside of the validation box
309
+ if (webgazer.params.showFaceFeedbackBox) checkEyesInValidationBox();
310
+ }
311
+
312
+ if (!webgazer.params.paused) {
313
+
314
+ if (_now - _last >= 1000. / webgazer.params.framerate) {
315
+ _last = _now
316
+
317
+ // Get gaze prediction (ask clm to track; pass the data to the regressor; get back a prediction)
318
+ latestGazeData = getPrediction();
319
+ // Count time
320
+ // var elapsedTime = performance.now() - clockStart;
321
+
322
+ latestGazeData = await latestGazeData;
323
+
324
+ // [20200623 xk] callback to function passed into setGazeListener(fn)
325
+ callback(latestGazeData);
326
+
327
+ if( latestGazeData ) {
328
+ // [20200608 XK] Smoothing across the most recent 4 predictions, do we need this with Kalman filter?
329
+ smoothingVals.push(latestGazeData);
330
+ var x = 0;
331
+ var y = 0;
332
+ var len = smoothingVals.length;
333
+ for (var d in smoothingVals.data) {
334
+ x += smoothingVals.get(d).x;
335
+ y += smoothingVals.get(d).y;
336
+ }
337
+
338
+ var pred = util.bound({ x: x / len, y: y / len });
339
+
340
+ if (webgazer.params.storingPoints) {
341
+ // drawCoordinates('blue', pred.x, pred.y); //draws the previous predictions
342
+ // store the position of the past fifty occuring tracker preditions
343
+ webgazer.storePoints(pred.x, pred.y, k)
344
+ ++k
345
+ if (k == 50) k = 0
346
+ }
347
+
348
+ gazeDot.style.transform = `translate(${pred.x}px, ${pred.y}px)`;
349
+ }
350
+
351
+ }
352
+ } else {
353
+ gazeDot.style.transform = `translate(-15px, -15px)` // Move out of the display
354
+ }
355
+
356
+ requestAnimationFrame(loop);
357
+ }
358
+
359
+ //is problematic to test
360
+ //because latestEyeFeatures is not set in many cases
361
+
362
+ /**
363
+ * Records screen position data based on current pupil feature and passes it
364
+ * to the regression model.
365
+ * @param {Number} x - The x screen position
366
+ * @param {Number} y - The y screen position
367
+ * @param {String} eventType - The event type to store
368
+ * @returns {null}
369
+ */
370
+ var recordScreenPosition = function(x, y, eventType) {
371
+ if (webgazer.params.paused) {
372
+ return;
373
+ }
374
+ if (regs.length === 0) {
375
+ console.log('regression not set, call setRegression()');
376
+ return null;
377
+ }
378
+ for (var reg in regs) {
379
+ if( latestEyeFeatures )
380
+ regs[reg].addData(latestEyeFeatures, [x, y], eventType);
381
+ }
382
+ };
383
+
384
+ /**
385
+ * Records click data and passes it to the regression model
386
+ * @param {Event} event - The listened event
387
+ */
388
+ var clickListener = async function(event) {
389
+ recordScreenPosition(event.clientX, event.clientY, eventTypes[0]); // eventType[0] === 'click'
390
+
391
+ if (webgazer.params.saveDataAcrossSessions) {
392
+ // Each click stores the next data point into localforage.
393
+ await setGlobalData();
394
+
395
+ // // Debug line
396
+ // console.log('Model size: ' + JSON.stringify(await localforage.getItem(localstorageDataLabel)).length / 1000000 + 'MB');
397
+ }
398
+ };
399
+
400
+ /**
401
+ * Records mouse movement data and passes it to the regression model
402
+ * @param {Event} event - The listened event
403
+ */
404
+ var moveListener = function(event) {
405
+ if (webgazer.params.paused) {
406
+ return;
407
+ }
408
+
409
+ var now = performance.now();
410
+ if (now < moveClock + webgazer.params.moveTickSize) {
411
+ return;
412
+ } else {
413
+ moveClock = now;
414
+ }
415
+ recordScreenPosition(event.clientX, event.clientY, eventTypes[1]); //eventType[1] === 'move'
416
+ };
417
+
418
+ /**
419
+ * Add event listeners for mouse click and move.
420
+ */
421
+ var addMouseEventListeners = function() {
422
+ //third argument set to true so that we get event on 'capture' instead of 'bubbling'
423
+ //this prevents a client using event.stopPropagation() preventing our access to the click
424
+ document.addEventListener('click', clickListener, true);
425
+ document.addEventListener('mousemove', moveListener, true);
426
+ };
427
+
428
+ /**
429
+ * Remove event listeners for mouse click and move.
430
+ */
431
+ var removeMouseEventListeners = function() {
432
+ // must set third argument to same value used in addMouseEventListeners
433
+ // for this to work.
434
+ document.removeEventListener('click', clickListener, true);
435
+ document.removeEventListener('mousemove', moveListener, true);
436
+ };
437
+
438
+ /**
439
+ * Loads the global data and passes it to the regression model
440
+ */
441
+ async function loadGlobalData() {
442
+ // Get settings object from localforage
443
+ // [20200611 xk] still unsure what this does, maybe would be good for Kalman filter settings etc?
444
+ settings = await localforage.getItem(localstorageSettingsLabel);
445
+ settings = settings || defaults;
446
+
447
+ // Get click data from localforage
448
+ var loadData = await localforage.getItem(localstorageDataLabel);
449
+ loadData = loadData || defaults;
450
+
451
+ // Set global var data to newly loaded data
452
+ data = loadData;
453
+
454
+ // Load data into regression model(s)
455
+ for (var reg in regs) {
456
+ regs[reg].setData(loadData);
457
+ }
458
+
459
+ console.log("loaded stored data into regression model");
460
+ }
461
+
462
+ /**
463
+ * Adds data to localforage
464
+ */
465
+ async function setGlobalData() {
466
+ // Grab data from regression model
467
+ var storeData = regs[0].getData() || data; // Array
468
+
469
+ // Store data into localforage
470
+ localforage.setItem(localstorageSettingsLabel, settings) // [20200605 XK] is 'settings' ever being used?
471
+ localforage.setItem(localstorageDataLabel, storeData);
472
+ //TODO data should probably be stored in webgazer object instead of each regression model
473
+ // -> requires duplication of data, but is likely easier on regression model implementors
474
+ }
475
+
476
+ /**
477
+ * Clears data from model and global storage
478
+ */
479
+ function clearData() {
480
+ // Removes data from localforage
481
+ localforage.clear();
482
+
483
+ // Removes data from regression model
484
+ for (var reg in regs) {
485
+ regs[reg].init();
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Initializes all needed dom elements and begins the loop
491
+ * @param {URL} stream - The video stream to use
492
+ */
493
+ async function init(initMode = 'all', stream) {
494
+ //////////////////////////
495
+ // Video and video preview
496
+ //////////////////////////
497
+
498
+ if (!webgazer.params.videoIsOn) {
499
+ // used for webgazer.stopVideo() and webgazer.setCameraConstraints()
500
+ videoStream = stream;
501
+
502
+ // create a video element container to enable customizable placement on the page
503
+ videoContainerElement = document.createElement('div');
504
+ videoContainerElement.id = webgazer.params.videoContainerId;
505
+ videoContainerElement.style.display = 'block';
506
+ videoContainerElement.style.visibility = webgazer.params.showVideo ? 'visible' : 'hidden';
507
+ // videoContainerElement.style.position = 'fixed';
508
+ videoContainerElement.style.left = '10px';
509
+ videoContainerElement.style.bottom = '10px';
510
+ videoContainerElement.style.width = webgazer.params.videoViewerWidth + 'px';
511
+ videoContainerElement.style.height = webgazer.params.videoViewerHeight + 'px';
512
+
513
+ videoElement = document.createElement('video');
514
+ videoElement.setAttribute('playsinline', '');
515
+ videoElement.id = webgazer.params.videoElementId;
516
+ videoElement.srcObject = stream;
517
+ videoElement.autoplay = true;
518
+ videoElement.style.display = 'block';
519
+ videoElement.style.visibility = webgazer.params.showVideo ? 'visible' : 'hidden';
520
+ videoElement.style.position = 'absolute';
521
+ // We set these to stop the video appearing too large when it is added for the very first time
522
+ videoElement.style.width = webgazer.params.videoViewerWidth + 'px';
523
+ videoElement.style.height = webgazer.params.videoViewerHeight + 'px';
524
+
525
+ // Canvas for drawing video to pass to clm tracker
526
+ videoElementCanvas = document.createElement('canvas');
527
+ videoElementCanvas.id = webgazer.params.videoElementCanvasId;
528
+ videoElementCanvas.style.display = 'block';
529
+ videoElementCanvas.style.visibility = 'hidden';
530
+
531
+ // Face overlay
532
+ // Shows the CLM tracking result
533
+ faceOverlay = document.createElement('canvas');
534
+ faceOverlay.id = webgazer.params.faceOverlayId;
535
+ faceOverlay.style.display = webgazer.params.showFaceOverlay ? 'block' : 'none';
536
+ faceOverlay.style.position = 'absolute';
537
+
538
+ // Mirror video feed
539
+ if (webgazer.params.mirrorVideo) {
540
+ videoElement.style.setProperty("-moz-transform", "scale(-1, 1)");
541
+ videoElement.style.setProperty("-webkit-transform", "scale(-1, 1)");
542
+ videoElement.style.setProperty("-o-transform", "scale(-1, 1)");
543
+ videoElement.style.setProperty("transform", "scale(-1, 1)");
544
+ videoElement.style.setProperty("filter", "FlipH");
545
+ faceOverlay.style.setProperty("-moz-transform", "scale(-1, 1)");
546
+ faceOverlay.style.setProperty("-webkit-transform", "scale(-1, 1)");
547
+ faceOverlay.style.setProperty("-o-transform", "scale(-1, 1)");
548
+ faceOverlay.style.setProperty("transform", "scale(-1, 1)");
549
+ faceOverlay.style.setProperty("filter", "FlipH");
550
+ }
551
+
552
+ // Feedback box
553
+ // Lets the user know when their face is in the middle
554
+ faceFeedbackBox = document.createElement('canvas');
555
+ faceFeedbackBox.id = webgazer.params.faceFeedbackBoxId;
556
+ faceFeedbackBox.style.display = webgazer.params.showFaceFeedbackBox ? 'block' : 'none';
557
+ faceFeedbackBox.style.border = 'solid red 4px';
558
+ faceFeedbackBox.style.position = 'absolute';
559
+
560
+ // Add other preview/feedback elements to the screen once the video has shown and its parameters are initialized
561
+ videoContainerElement.appendChild(videoElement);
562
+ document.body.appendChild(videoContainerElement);
563
+ function setupPreviewVideo(e) {
564
+
565
+ // All video preview parts have now been added, so set the size both internally and in the preview window.
566
+ setInternalVideoBufferSizes( videoElement.videoWidth, videoElement.videoHeight );
567
+ webgazer.setVideoViewerSize( webgazer.params.videoViewerWidth, webgazer.params.videoViewerHeight );
568
+
569
+ videoContainerElement.appendChild(videoElementCanvas);
570
+ webgazer.videoCanvas = videoElementCanvas // !
571
+ videoContainerElement.appendChild(faceOverlay);
572
+ videoContainerElement.appendChild(faceFeedbackBox);
573
+
574
+ // Run this only once, so remove the event listener
575
+ e.target.removeEventListener(e.type, setupPreviewVideo);
576
+ };
577
+ videoElement.addEventListener('timeupdate', setupPreviewVideo);
578
+ }
579
+
580
+ if (initMode != 'video') {
581
+ // Gaze dot
582
+ // Starts offscreen
583
+ gazeDot = document.createElement('div');
584
+ gazeDot.id = webgazer.params.gazeDotId;
585
+ gazeDot.style.display = webgazer.params.showGazeDot ? 'block' : 'none';
586
+ // gazeDot.style.position = 'fixed';
587
+ // gazeDot.style.zIndex = 99999;
588
+ // TODO Customizable width and height
589
+ gazeDot.style.width = '10px'
590
+ gazeDot.style.height = '10px'
591
+ gazeDot.style.left = '-5px';
592
+ gazeDot.style.top = '-5px'; // Width and height are 10px by default
593
+ gazeDot.style.transform = `translate(-15px, -15px)`
594
+ // gazeDot.style.background = 'red';
595
+ // gazeDot.style.borderRadius = '100%';
596
+ // gazeDot.style.opacity = '0.5';
597
+ // gazeDot.style.width = '10px';
598
+ // gazeDot.style.height = '10px';
599
+
600
+ document.body.appendChild(gazeDot);
601
+
602
+ addMouseEventListeners();
603
+
604
+ //BEGIN CALLBACK LOOP
605
+ webgazer.params.paused = false;
606
+ clockStart = performance.now();
607
+ }
608
+
609
+ await loop();
610
+ }
611
+
612
+ /**
613
+ * Initializes navigator.mediaDevices.getUserMedia
614
+ * depending on the browser capabilities
615
+ *
616
+ * @return Promise
617
+ */
618
+ function setUserMediaVariable(){
619
+
620
+ if (navigator.mediaDevices === undefined) {
621
+ navigator.mediaDevices = {};
622
+ }
623
+
624
+ if (navigator.mediaDevices.getUserMedia === undefined) {
625
+ navigator.mediaDevices.getUserMedia = function(constraints) {
626
+
627
+ // gets the alternative old getUserMedia is possible
628
+ var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
629
+
630
+ // set an error message if browser doesn't support getUserMedia
631
+ if (!getUserMedia) {
632
+ return Promise.reject(new Error("Unfortunately, your browser does not support access to the webcam through the getUserMedia API. Try to use the latest version of Google Chrome, Mozilla Firefox, Opera, or Microsoft Edge instead."));
633
+ }
634
+
635
+ // uses navigator.getUserMedia for older browsers
636
+ return new Promise(function(resolve, reject) {
637
+ getUserMedia.call(navigator, constraints, resolve, reject);
638
+ });
639
+ }
640
+ }
641
+ }
642
+
643
+ //PUBLIC FUNCTIONS - CONTROL
644
+
645
+ /**
646
+ * Starts all state related to webgazer -> dataLoop, video collection, click listener
647
+ * If starting fails, call `onFail` param function.
648
+ * @param {Function} onFail - Callback to call in case it is impossible to find user camera
649
+ * @returns {*}
650
+ */
651
+ webgazer.begin = function(onFail) {
652
+ // if (window.location.protocol !== 'https:' && window.location.hostname !== 'localhost' && window.chrome){
653
+ // alert("WebGazer works only over https. If you are doing local development, you need to run a local server.");
654
+ // }
655
+
656
+ // Load model data stored in localforage.
657
+ // if (webgazer.params.saveDataAcrossSessions) {
658
+ // loadGlobalData();
659
+ // }
660
+
661
+ onFail = onFail || function() {console.log('No stream')};
662
+
663
+ // if (debugVideoLoc) {
664
+ // init(debugVideoLoc);
665
+ // return webgazer;
666
+ // }
667
+
668
+ return webgazer._begin(false)
669
+ };
670
+
671
+ /**
672
+ * Start the video element.
673
+ */
674
+ webgazer.beginVideo = function () {
675
+ webgazer._begin(true)
676
+ }
677
+
678
+ webgazer._begin = function (videoOnly) {
679
+ // SETUP VIDEO ELEMENTS
680
+ // Sets .mediaDevices.getUserMedia depending on browser
681
+ if (!webgazer.params.videoIsOn) {
682
+ setUserMediaVariable();
683
+
684
+ return new Promise(async (resolve, reject) => {
685
+ let stream;
686
+ try {
687
+ stream = await navigator.mediaDevices.getUserMedia( webgazer.params.camConstraints );
688
+ init(videoOnly ? 'video' : 'all', stream);
689
+ //
690
+ webgazer.params.videoIsOn = true
691
+ //
692
+ if (!videoOnly) resolve(webgazer);
693
+ } catch(err) {
694
+ console.log(err);
695
+ onFail();
696
+ videoElement = null;
697
+ stream = null;
698
+ reject(err);
699
+ }
700
+ });
701
+ } else {
702
+ // Video is ON
703
+ // e.g. tracking viewing distance already
704
+ init('gaze')
705
+ }
706
+ }
707
+
708
+ /**
709
+ * Checks if webgazer has finished initializing after calling begin()
710
+ * [20180729 JT] This seems like a bad idea for how this function should be implemented.
711
+ * @returns {boolean} if webgazer is ready
712
+ */
713
+ webgazer.isReady = function() {
714
+ if (videoElementCanvas === null) {
715
+ return false;
716
+ }
717
+ return videoElementCanvas.width > 0;
718
+ };
719
+
720
+ /**
721
+ * Stops collection of data and predictions
722
+ * @returns {webgazer} this
723
+ */
724
+ webgazer.pause = function() {
725
+ webgazer.params.paused = true;
726
+ return webgazer;
727
+ };
728
+
729
+ /* -------------------------------------------------------------------------- */
730
+
731
+ webgazer.stopLearning = function () {
732
+ removeMouseEventListeners()
733
+ return webgazer
734
+ }
735
+
736
+ webgazer.startLearning = function () {
737
+ addMouseEventListeners()
738
+ return webgazer
739
+ }
740
+
741
+ /* -------------------------------------------------------------------------- */
742
+
743
+ /**
744
+ * Resumes collection of data and predictions if paused
745
+ * @returns {webgazer} this
746
+ */
747
+ webgazer.resume = async function() {
748
+ if (!webgazer.params.paused) {
749
+ return webgazer;
750
+ }
751
+ webgazer.params.paused = false;
752
+ await loop();
753
+ return webgazer;
754
+ };
755
+
756
+ /**
757
+ * stops collection of data and removes dom modifications, must call begin() to reset up
758
+ * @return {webgazer} this
759
+ */
760
+ webgazer.end = function(endAll = false) {
761
+ // loop may run an extra time and fail due to removed elements
762
+ // webgazer.params.paused = true;
763
+ if (endAll) {
764
+ smoothingVals = new util.DataWindow(4);
765
+ k = 0;
766
+ _now = null;
767
+ _last = -1;
768
+
769
+ hasBounds = false
770
+
771
+ webgazer.params.videoIsOn = false
772
+ setTimeout(() => {
773
+ webgazer.stopVideo(); // uncomment if you want to stop the video from streaming
774
+
775
+ //remove video element and canvas
776
+ // document.body.removeChild(videoElement);
777
+ document.body.removeChild(videoContainerElement);
778
+ }, 500);
779
+ }
780
+ return webgazer;
781
+ };
782
+
783
+ /**
784
+ * Stops the video camera from streaming and removes the video outlines
785
+ * @return {webgazer} this
786
+ */
787
+ webgazer.stopVideo = function() {
788
+ // Stops the video from streaming
789
+ videoStream.getTracks()[0].stop();
790
+
791
+ // Removes the outline of the face
792
+ // document.body.removeChild( faceOverlay );
793
+
794
+ // Removes the box around the face
795
+ // document.body.removeChild( faceFeedbackBox );
796
+
797
+ return webgazer;
798
+ }
799
+
800
+
801
+ //PUBLIC FUNCTIONS - DEBUG
802
+
803
+ /**
804
+ * Returns if the browser is compatible with webgazer
805
+ * @return {boolean} if browser is compatible
806
+ */
807
+ webgazer.detectCompatibility = function() {
808
+
809
+ var getUserMedia = navigator.mediaDevices.getUserMedia ||
810
+ navigator.getUserMedia ||
811
+ navigator.webkitGetUserMedia ||
812
+ navigator.mozGetUserMedia;
813
+
814
+ return getUserMedia !== undefined;
815
+ };
816
+
817
+ /**
818
+ * Set whether to show any of the video previews (camera, face overlay, feedback box).
819
+ * If true: visibility depends on corresponding params (default all true).
820
+ * If false: camera, face overlay, feedback box are all hidden
821
+ * @param {bool} val
822
+ * @return {webgazer} this
823
+ */
824
+ webgazer.showVideoPreview = function(val) {
825
+ webgazer.params.showVideoPreview = val;
826
+ webgazer.showVideo(val && webgazer.params.showVideo);
827
+ webgazer.showFaceOverlay(val && webgazer.params.showFaceOverlay);
828
+ webgazer.showFaceFeedbackBox(val && webgazer.params.showFaceFeedbackBox);
829
+ return webgazer;
830
+ }
831
+
832
+ /**
833
+ * Set whether the camera video preview is visible or not (default true).
834
+ * @param {*} bool
835
+ * @return {webgazer} this
836
+ */
837
+ webgazer.showVideo = function(val) {
838
+ webgazer.params.showVideo = val;
839
+ if(videoElement) {
840
+ videoElement.style.visibility = val ? 'visible' : 'hidden';
841
+ }
842
+ if(videoContainerElement) {
843
+ videoContainerElement.style.visibility = val ? 'visible' : 'hidden';
844
+ }
845
+ return webgazer;
846
+ };
847
+
848
+ /**
849
+ * Set whether the face overlay is visible or not (default true).
850
+ * @param {*} bool
851
+ * @return {webgazer} this
852
+ */
853
+ webgazer.showFaceOverlay = function(val) {
854
+ webgazer.params.showFaceOverlay = val;
855
+ if( faceOverlay ) {
856
+ faceOverlay.style.display = val ? 'block' : 'none';
857
+ }
858
+ return webgazer;
859
+ };
860
+
861
+ /**
862
+ * Set whether the face feedback box is visible or not (default true).
863
+ * @param {*} bool
864
+ * @return {webgazer} this
865
+ */
866
+ webgazer.showFaceFeedbackBox = function(val) {
867
+
868
+ webgazer.params.showFaceFeedbackBox = val;
869
+ if( faceFeedbackBox ) {
870
+ faceFeedbackBox.style.display = val ? 'block' : 'none';
871
+ }
872
+ return webgazer;
873
+ };
874
+
875
+ /**
876
+ * Set whether the gaze prediction point(s) are visible or not.
877
+ * Multiple because of a trail of past dots. Default true
878
+ * @return {webgazer} this
879
+ */
880
+ webgazer.showPredictionPoints = function(val) {
881
+ webgazer.params.showGazeDot = val;
882
+ if( gazeDot ) {
883
+ gazeDot.style.display = val ? 'block' : 'none';
884
+ }
885
+ return webgazer;
886
+ };
887
+
888
+ /**
889
+ * Set whether localprevious calibration data (from localforage) should be loaded.
890
+ * Default true.
891
+ *
892
+ * NOTE: Should be called before webgazer.begin() -- see www/js/main.js for example
893
+ *
894
+ * @param val
895
+ * @returns {webgazer} this
896
+ */
897
+ webgazer.saveDataAcrossSessions = function(val) {
898
+ webgazer.params.saveDataAcrossSessions = val;
899
+ return webgazer;
900
+ }
901
+
902
+ /**
903
+ * Set whether a Kalman filter will be applied to gaze predictions (default true);
904
+ * @return {webgazer} this
905
+ */
906
+ webgazer.applyKalmanFilter = function(val) {
907
+ webgazer.params.applyKalmanFilter = val;
908
+ return webgazer;
909
+ }
910
+
911
+ /**
912
+ * Define constraints on the video camera that is used. Useful for non-standard setups.
913
+ * This can be set before calling webgazer.begin(), but also mid stream.
914
+ *
915
+ * @param {Object} constraints Example constraints object:
916
+ * { width: { min: 320, ideal: 1280, max: 1920 }, height: { min: 240, ideal: 720, max: 1080 }, facingMode: "user" };
917
+ *
918
+ * Follows definition here:
919
+ * https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API/Constraints
920
+ *
921
+ * Note: The constraints set here are applied to the video track only. They also _replace_ any constraints, so be sure to set everything you need.
922
+ * Warning: Setting a large video resolution will decrease performance, and may require
923
+ */
924
+ webgazer.setCameraConstraints = async function(constraints) {
925
+ var videoTrack,videoSettings;
926
+ webgazer.params.camConstraints = constraints;
927
+
928
+ // If the camera stream is already up...
929
+ if(videoStream)
930
+ {
931
+ webgazer.pause();
932
+ videoTrack = videoStream.getVideoTracks()[0];
933
+ try {
934
+ await videoTrack.applyConstraints( webgazer.params.camConstraints );
935
+ videoSettings = videoTrack.getSettings();
936
+ setInternalVideoBufferSizes( videoSettings.width, videoSettings.height );
937
+ } catch(err) {
938
+ console.log( err );
939
+ return;
940
+ }
941
+ // Reset and recompute sizes of the video viewer.
942
+ // This is only to adjust the feedback box, say, if the aspect ratio of the video has changed.
943
+ webgazer.setVideoViewerSize( webgazer.params.videoViewerWidth, webgazer.params.videoViewerHeight )
944
+ webgazer.getTracker().reset();
945
+ await webgazer.resume();
946
+ }
947
+ }
948
+
949
+
950
+ /**
951
+ * Does what it says on the tin.
952
+ * @param {*} width
953
+ * @param {*} height
954
+ */
955
+ function setInternalVideoBufferSizes( width, height ) {
956
+ // Re-set the canvas size used by the internal processes
957
+ if( videoElementCanvas )
958
+ {
959
+ videoElementCanvas.width = width;
960
+ videoElementCanvas.height = height;
961
+ }
962
+
963
+ // Re-set the face overlay canvas size
964
+ if( faceOverlay )
965
+ {
966
+ faceOverlay.width = width;
967
+ faceOverlay.height = height;
968
+ }
969
+ }
970
+
971
+ /**
972
+ * Set a static video file to be used instead of webcam video
973
+ * @param {String} videoLoc - video file location
974
+ * @return {webgazer} this
975
+ */
976
+ webgazer.setStaticVideo = function(videoLoc) {
977
+ debugVideoLoc = videoLoc;
978
+ return webgazer;
979
+ };
980
+
981
+ /**
982
+ * Set the size of the video viewer
983
+ */
984
+ webgazer.setVideoViewerSize = function(w, h) {
985
+
986
+ webgazer.params.videoViewerWidth = w;
987
+ webgazer.params.videoViewerHeight = h;
988
+
989
+ // Change the video viewer
990
+ videoElement.style.width = w + 'px';
991
+ videoElement.style.height = h + 'px';
992
+
993
+ // Change the face overlay
994
+ faceOverlay.style.width = w + 'px';
995
+ faceOverlay.style.height = h + 'px';
996
+
997
+ // Change the feedback box size
998
+ // Compute the boundaries of the face overlay validation box based on the video size
999
+ var tlwh = webgazer.computeValidationBoxSize()
1000
+ // Assign them to the object
1001
+ faceFeedbackBox.style.top = tlwh[0] + 'px';
1002
+ faceFeedbackBox.style.left = tlwh[1] + 'px';
1003
+ faceFeedbackBox.style.width = tlwh[2] + 'px';
1004
+ faceFeedbackBox.style.height = tlwh[3] + 'px';
1005
+ };
1006
+
1007
+ /**
1008
+ * Add the mouse click and move listeners that add training data.
1009
+ * @return {webgazer} this
1010
+ */
1011
+ webgazer.addMouseEventListeners = function() {
1012
+ addMouseEventListeners();
1013
+ return webgazer;
1014
+ };
1015
+
1016
+ /**
1017
+ * Remove the mouse click and move listeners that add training data.
1018
+ * @return {webgazer} this
1019
+ */
1020
+ webgazer.removeMouseEventListeners = function() {
1021
+ removeMouseEventListeners();
1022
+ return webgazer;
1023
+ };
1024
+
1025
+ /**
1026
+ * Records current screen position for current pupil features.
1027
+ * @param {String} x - position on screen in the x axis
1028
+ * @param {String} y - position on screen in the y axis
1029
+ * @param {String} eventType - "click" or "move", as per eventTypes
1030
+ * @return {webgazer} this
1031
+ */
1032
+ webgazer.recordScreenPosition = function(x, y, eventType) {
1033
+ // give this the same weight that a click gets.
1034
+ recordScreenPosition(x, y, eventType || eventTypes[0]);
1035
+ return webgazer;
1036
+ };
1037
+
1038
+ /*
1039
+ * Stores the position of the fifty most recent tracker preditions
1040
+ */
1041
+ webgazer.storePoints = function(x, y, k) {
1042
+ xPast50[k] = x;
1043
+ yPast50[k] = y;
1044
+ }
1045
+
1046
+ //SETTERS
1047
+ /**
1048
+ * Sets the tracking module
1049
+ * @param {String} name - The name of the tracking module to use
1050
+ * @return {webgazer} this
1051
+ */
1052
+ webgazer.setTracker = function(name) {
1053
+ if (curTrackerMap[name] === undefined) {
1054
+ console.log('Invalid tracker selection');
1055
+ console.log('Options are: ');
1056
+ for (var t in curTrackerMap) {
1057
+ console.log(t);
1058
+ }
1059
+ return webgazer;
1060
+ }
1061
+ curTracker = curTrackerMap[name]();
1062
+ return webgazer;
1063
+ };
1064
+
1065
+ /**
1066
+ * Sets the regression module and clears any other regression modules
1067
+ * @param {String} name - The name of the regression module to use
1068
+ * @return {webgazer} this
1069
+ */
1070
+ webgazer.setRegression = function(name) {
1071
+ if (regressionMap[name] === undefined) {
1072
+ console.log('Invalid regression selection');
1073
+ console.log('Options are: ');
1074
+ for (var reg in regressionMap) {
1075
+ console.log(reg);
1076
+ }
1077
+ return webgazer;
1078
+ }
1079
+ data = regs[0].getData();
1080
+ regs = [regressionMap[name]()];
1081
+ regs[0].setData(data);
1082
+ return webgazer;
1083
+ };
1084
+
1085
+ /**
1086
+ * Adds a new tracker module so that it can be used by setTracker()
1087
+ * @param {String} name - the new name of the tracker
1088
+ * @param {Function} constructor - the constructor of the curTracker object
1089
+ * @return {webgazer} this
1090
+ */
1091
+ webgazer.addTrackerModule = function(name, constructor) {
1092
+ curTrackerMap[name] = function() {
1093
+ return new constructor();
1094
+ };
1095
+ };
1096
+
1097
+ /**
1098
+ * Adds a new regression module so that it can be used by setRegression() and addRegression()
1099
+ * @param {String} name - the new name of the regression
1100
+ * @param {Function} constructor - the constructor of the regression object
1101
+ */
1102
+ webgazer.addRegressionModule = function(name, constructor) {
1103
+ regressionMap[name] = function() {
1104
+ return new constructor();
1105
+ };
1106
+ };
1107
+
1108
+ /**
1109
+ * Adds a new regression module to the list of regression modules, seeding its data from the first regression module
1110
+ * @param {String} name - the string name of the regression module to add
1111
+ * @return {webgazer} this
1112
+ */
1113
+ webgazer.addRegression = function(name) {
1114
+ var newReg = regressionMap[name]();
1115
+ data = regs[0].getData();
1116
+ newReg.setData(data);
1117
+ regs.push(newReg);
1118
+ return webgazer;
1119
+ };
1120
+
1121
+ /**
1122
+ * Sets a callback to be executed on every gaze event (currently all time steps)
1123
+ * @param {function} listener - The callback function to call (it must be like function(data, elapsedTime))
1124
+ * - No elapsedTime needed for Toolbox
1125
+ * @return {webgazer} this
1126
+ */
1127
+ webgazer.setGazeListener = function(listener) {
1128
+ callback = listener;
1129
+ return webgazer;
1130
+ };
1131
+
1132
+ /**
1133
+ * Removes the callback set by setGazeListener
1134
+ * @return {webgazer} this
1135
+ */
1136
+ webgazer.clearGazeListener = function() {
1137
+ callback = nopCallback;
1138
+ return webgazer;
1139
+ };
1140
+
1141
+ /**
1142
+ * Set the video element canvas; useful if you want to run WebGazer on your own canvas (e.g., on any random image).
1143
+ * @return The current video element canvas
1144
+ */
1145
+ webgazer.setVideoElementCanvas = function(canvas) {
1146
+ videoElementCanvas = canvas;
1147
+ return videoElementCanvas;
1148
+ }
1149
+
1150
+ /**
1151
+ * Clear data from localforage and from regs
1152
+ */
1153
+ webgazer.clearData = async function() {
1154
+ clearData();
1155
+ }
1156
+
1157
+
1158
+ //GETTERS
1159
+ /**
1160
+ * Returns the tracker currently in use
1161
+ * @return {tracker} an object following the tracker interface
1162
+ */
1163
+ webgazer.getTracker = function() {
1164
+ return curTracker;
1165
+ };
1166
+
1167
+ /**
1168
+ * Returns the regression currently in use
1169
+ * @return {Array.<Object>} an array of regression objects following the regression interface
1170
+ */
1171
+ webgazer.getRegression = function() {
1172
+ return regs;
1173
+ };
1174
+
1175
+ /**
1176
+ * Requests an immediate prediction
1177
+ * @return {object} prediction data object
1178
+ */
1179
+ webgazer.getCurrentPrediction = function(regIndex) {
1180
+ return getPrediction(regIndex);
1181
+ };
1182
+
1183
+ /**
1184
+ * returns the different event types that may be passed to regressions when calling regression.addData()
1185
+ * @return {Array} array of strings where each string is an event type
1186
+ */
1187
+ webgazer.params.getEventTypes = function() {
1188
+ return eventTypes.slice();
1189
+ }
1190
+
1191
+ /**
1192
+ * Get the video element canvas that WebGazer uses internally on which to run its face tracker.
1193
+ * @return The current video element canvas
1194
+ */
1195
+ webgazer.getVideoElementCanvas = function() {
1196
+ return videoElementCanvas;
1197
+ }
1198
+
1199
+ /**
1200
+ * @return array [a,b] where a is width ratio and b is height ratio
1201
+ */
1202
+ webgazer.getVideoPreviewToCameraResolutionRatio = function() {
1203
+ return [webgazer.params.videoViewerWidth / videoElement.videoWidth, webgazer.params.videoViewerHeight / videoElement.videoHeight];
1204
+ }
1205
+
1206
+ /*
1207
+ * Gets the fifty most recent tracker preditions
1208
+ */
1209
+ webgazer.getStoredPoints = function() {
1210
+ return [xPast50, yPast50];
1211
+ }
1212
+
1213
+ export default webgazer;