remote-calibrator 0.3.0 → 0.5.0-beta.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +29 -19
  3. package/homepage/example.js +9 -3
  4. package/i18n/fetch-languages-sheets.js +5 -4
  5. package/lib/RemoteCalibrator.min.js +1 -1
  6. package/lib/RemoteCalibrator.min.js.LICENSE.txt +1 -1
  7. package/lib/RemoteCalibrator.min.js.map +1 -1
  8. package/package.json +15 -15
  9. package/src/WebGazer4RC/.gitattributes +10 -0
  10. package/src/WebGazer4RC/LICENSE.md +15 -0
  11. package/src/WebGazer4RC/README.md +142 -0
  12. package/src/WebGazer4RC/gnu-lgpl-v3.0.md +163 -0
  13. package/src/WebGazer4RC/gplv3.md +636 -0
  14. package/src/WebGazer4RC/package-lock.json +1133 -0
  15. package/src/WebGazer4RC/package.json +28 -0
  16. package/src/WebGazer4RC/src/dom_util.mjs +27 -0
  17. package/src/WebGazer4RC/src/facemesh.mjs +150 -0
  18. package/src/WebGazer4RC/src/index.mjs +1235 -0
  19. package/src/WebGazer4RC/src/mat.mjs +301 -0
  20. package/src/WebGazer4RC/src/params.mjs +29 -0
  21. package/src/WebGazer4RC/src/pupil.mjs +109 -0
  22. package/src/WebGazer4RC/src/ridgeReg.mjs +104 -0
  23. package/src/WebGazer4RC/src/ridgeRegThreaded.mjs +161 -0
  24. package/src/WebGazer4RC/src/ridgeWeightedReg.mjs +125 -0
  25. package/src/WebGazer4RC/src/ridgeWorker.mjs +135 -0
  26. package/src/WebGazer4RC/src/util.mjs +348 -0
  27. package/src/WebGazer4RC/src/util_regression.mjs +240 -0
  28. package/src/WebGazer4RC/src/worker_scripts/mat.js +306 -0
  29. package/src/WebGazer4RC/src/worker_scripts/util.js +398 -0
  30. package/src/WebGazer4RC/test/regression_test.js +182 -0
  31. package/src/WebGazer4RC/test/run_tests_and_server.sh +24 -0
  32. package/src/WebGazer4RC/test/util_test.js +60 -0
  33. package/src/WebGazer4RC/test/webgazerExtract_test.js +40 -0
  34. package/src/WebGazer4RC/test/webgazer_test.js +160 -0
  35. package/src/WebGazer4RC/test/www_page_test.js +41 -0
  36. package/src/const.js +3 -0
  37. package/src/core.js +8 -0
  38. package/src/css/distance.scss +40 -0
  39. package/src/css/panel.scss +32 -1
  40. package/src/distance/distance.js +4 -4
  41. package/src/distance/distanceCheck.js +115 -0
  42. package/src/distance/distanceTrack.js +99 -41
  43. package/src/{interpupillaryDistance.js → distance/interPupillaryDistance.js} +14 -12
  44. package/src/gaze/gazeTracker.js +16 -1
  45. package/src/i18n.js +1 -1
  46. package/src/index.js +2 -1
  47. package/src/panel.js +32 -3
  48. package/webpack.config.js +4 -4
@@ -0,0 +1,125 @@
1
+ import util from './util.mjs';
2
+ import util_regression from './util_regression.mjs';
3
+ import params from './params.mjs'
4
+
5
+ const reg = {};
6
+
7
+ /**
8
+ * Constructor of RidgeWeightedReg object
9
+ * @constructor
10
+ */
11
+ reg.RidgeWeightedReg = function() {
12
+ this.init();
13
+ };
14
+
15
+ /**
16
+ * Initialize new arrays and initialize Kalman filter.
17
+ */
18
+ reg.RidgeWeightedReg.prototype.init = util_regression.InitRegression
19
+
20
+ /**
21
+ * Add given data from eyes
22
+ * @param {Object} eyes - eyes where extract data to add
23
+ * @param {Object} screenPos - The current screen point
24
+ * @param {Object} type - The type of performed action
25
+ */
26
+ reg.RidgeWeightedReg.prototype.addData = util_regression.addData
27
+
28
+ /**
29
+ * Try to predict coordinates from pupil data
30
+ * after apply linear regression on data set
31
+ * @param {Object} eyesObj - The current user eyes object
32
+ * @returns {Object}
33
+ */
34
+ reg.RidgeWeightedReg.prototype.predict = function(eyesObj) {
35
+ if (!eyesObj || this.eyeFeaturesClicks.length === 0) {
36
+ return null;
37
+ }
38
+ var acceptTime = performance.now() - this.trailTime;
39
+ var trailX = [];
40
+ var trailY = [];
41
+ var trailFeat = [];
42
+ for (var i = 0; i < this.trailDataWindow; i++) {
43
+ if (this.trailTimes.get(i) > acceptTime) {
44
+ trailX.push(this.screenXTrailArray.get(i));
45
+ trailY.push(this.screenYTrailArray.get(i));
46
+ trailFeat.push(this.eyeFeaturesTrail.get(i));
47
+ }
48
+ }
49
+
50
+ var len = this.eyeFeaturesClicks.data.length;
51
+ var weightedEyeFeats = Array(len);
52
+ var weightedXArray = Array(len);
53
+ var weightedYArray = Array(len);
54
+ for (var i = 0; i < len; i++) {
55
+ var weight = Math.sqrt( 1 / (len - i) ); // access from oldest to newest so should start with low weight and increase steadily
56
+ //abstraction is leaking...
57
+ var trueIndex = this.eyeFeaturesClicks.getTrueIndex(i);
58
+ for (var j = 0; j < this.eyeFeaturesClicks.data[trueIndex].length; j++) {
59
+ var val = this.eyeFeaturesClicks.data[trueIndex][j] * weight;
60
+ if (weightedEyeFeats[trueIndex] !== undefined){
61
+ weightedEyeFeats[trueIndex].push(val);
62
+ } else {
63
+ weightedEyeFeats[trueIndex] = [val];
64
+ }
65
+ }
66
+ weightedXArray[trueIndex] = this.screenXClicksArray.get(i).slice(0, this.screenXClicksArray.get(i).length);
67
+ weightedYArray[trueIndex] = this.screenYClicksArray.get(i).slice(0, this.screenYClicksArray.get(i).length);
68
+ weightedXArray[i][0] = weightedXArray[i][0] * weight;
69
+ weightedYArray[i][0] = weightedYArray[i][0] * weight;
70
+ }
71
+
72
+ var screenXArray = weightedXArray.concat(trailX);
73
+ var screenYArray = weightedYArray.concat(trailY);
74
+ var eyeFeatures = weightedEyeFeats.concat(trailFeat);
75
+
76
+ var coefficientsX = util_regression.ridge(screenXArray, eyeFeatures, this.ridgeParameter);
77
+ var coefficientsY = util_regression.ridge(screenYArray, eyeFeatures, this.ridgeParameter);
78
+
79
+ var eyeFeats = util.getEyeFeats(eyesObj);
80
+ var predictedX = 0;
81
+ for(var i=0; i< eyeFeats.length; i++){
82
+ predictedX += eyeFeats[i] * coefficientsX[i];
83
+ }
84
+ var predictedY = 0;
85
+ for(var i=0; i< eyeFeats.length; i++){
86
+ predictedY += eyeFeats[i] * coefficientsY[i];
87
+ }
88
+
89
+ predictedX = Math.floor(predictedX);
90
+ predictedY = Math.floor(predictedY);
91
+
92
+ if (params.applyKalmanFilter) {
93
+ // Update Kalman model, and get prediction
94
+ var newGaze = [predictedX, predictedY]; // [20200607 xk] Should we use a 1x4 vector?
95
+ newGaze = this.kalman.update(newGaze);
96
+
97
+ return {
98
+ x: newGaze[0],
99
+ y: newGaze[1]
100
+ };
101
+ } else {
102
+ return {
103
+ x: predictedX,
104
+ y: predictedY
105
+ };
106
+ }
107
+ };
108
+
109
+ reg.RidgeWeightedReg.prototype.setData = util_regression.setData;
110
+
111
+ /**
112
+ * Return the data
113
+ * @returns {Array.<Object>|*}
114
+ */
115
+ reg.RidgeWeightedReg.prototype.getData = function() {
116
+ return this.dataClicks.data;
117
+ };
118
+
119
+ /**
120
+ * The RidgeWeightedReg object name
121
+ * @type {string}
122
+ */
123
+ reg.RidgeWeightedReg.prototype.name = 'ridgeWeighted';
124
+
125
+ export default reg;
@@ -0,0 +1,135 @@
1
+ 'use strict';
2
+
3
+ console.log('thread starting');
4
+
5
+ // Add src/util.mjs and src/mat.mjs to the same directory as your html file
6
+ importScripts('./worker_scripts/util.js', './worker_scripts/mat.js'); // [20200708] Figure out how to make all of this wrap up neatly
7
+ var ridgeParameter = Math.pow(10,-5);
8
+ var resizeWidth = 10;
9
+ var resizeHeight = 6;
10
+ var dataWindow = 700;
11
+ var trailDataWindow = 10;
12
+ var trainInterval = 500;
13
+
14
+ var screenXClicksArray = new self.webgazer.util.DataWindow(dataWindow);
15
+ var screenYClicksArray = new self.webgazer.util.DataWindow(dataWindow);
16
+ var eyeFeaturesClicks = new self.webgazer.util.DataWindow(dataWindow);
17
+ var dataClicks = new self.webgazer.util.DataWindow(dataWindow);
18
+
19
+ var screenXTrailArray = new self.webgazer.util.DataWindow(trailDataWindow);
20
+ var screenYTrailArray = new self.webgazer.util.DataWindow(trailDataWindow);
21
+ var eyeFeaturesTrail = new self.webgazer.util.DataWindow(trailDataWindow);
22
+ var dataTrail = new self.webgazer.util.DataWindow(trailDataWindow);
23
+
24
+ /**
25
+ * Performs ridge regression, according to the Weka code.
26
+ * @param {Array} y - corresponds to screen coordinates (either x or y) for each of n click events
27
+ * @param {Array.<Array.<Number>>} X - corresponds to gray pixel features (120 pixels for both eyes) for each of n clicks
28
+ * @param {Array} k - ridge parameter
29
+ * @return{Array} regression coefficients
30
+ */
31
+ function ridge(y, X, k){
32
+ var nc = X[0].length;
33
+ var m_Coefficients = new Array(nc);
34
+ var xt = self.webgazer.mat.transpose(X);
35
+ var solution = new Array();
36
+ var success = true;
37
+ do{
38
+ var ss = self.webgazer.mat.mult(xt,X);
39
+ // Set ridge regression adjustment
40
+ for (var i = 0; i < nc; i++) {
41
+ ss[i][i] = ss[i][i] + k;
42
+ }
43
+
44
+ // Carry out the regression
45
+ var bb = self.webgazer.mat.mult(xt,y);
46
+ for(var i = 0; i < nc; i++) {
47
+ m_Coefficients[i] = bb[i][0];
48
+ }
49
+ try{
50
+ var n = (m_Coefficients.length !== 0 ? m_Coefficients.length/m_Coefficients.length: 0);
51
+ if (m_Coefficients.length*n !== m_Coefficients.length){
52
+ console.log('Array length must be a multiple of m')
53
+ }
54
+ solution = (ss.length === ss[0].length ? (self.webgazer.mat.LUDecomposition(ss,bb)) : (self.webgazer.mat.QRDecomposition(ss,bb)));
55
+
56
+ for (var i = 0; i < nc; i++){
57
+ m_Coefficients[i] = solution[i][0];
58
+ }
59
+ success = true;
60
+ }
61
+ catch (ex){
62
+ k *= 10;
63
+ console.log(ex);
64
+ success = false;
65
+ }
66
+ } while (!success);
67
+ return m_Coefficients;
68
+ }
69
+
70
+ //TODO: still usefull ???
71
+ /**
72
+ *
73
+ * @returns {Number}
74
+ */
75
+ function getCurrentFixationIndex() {
76
+ var index = 0;
77
+ var recentX = this.screenXTrailArray.get(0);
78
+ var recentY = this.screenYTrailArray.get(0);
79
+ for (var i = this.screenXTrailArray.length - 1; i >= 0; i--) {
80
+ var currX = this.screenXTrailArray.get(i);
81
+ var currY = this.screenYTrailArray.get(i);
82
+ var euclideanDistance = Math.sqrt(Math.pow((currX-recentX),2)+Math.pow((currY-recentY),2));
83
+ if (euclideanDistance > 72){
84
+ return i+1;
85
+ }
86
+ }
87
+ return i;
88
+ }
89
+
90
+ /**
91
+ * Event handler, it store screen position to allow training
92
+ * @param {Event} event - the receive event
93
+ */
94
+ self.onmessage = function(event) {
95
+ var data = event.data;
96
+ var screenPos = data['screenPos'];
97
+ var eyes = data['eyes'];
98
+ var type = data['type'];
99
+ if (type === 'click') {
100
+ self.screenXClicksArray.push([screenPos[0]]);
101
+ self.screenYClicksArray.push([screenPos[1]]);
102
+
103
+ self.eyeFeaturesClicks.push(eyes);
104
+ } else if (type === 'move') {
105
+ self.screenXTrailArray.push([screenPos[0]]);
106
+ self.screenYTrailArray.push([screenPos[1]]);
107
+
108
+ self.eyeFeaturesTrail.push(eyes);
109
+ self.dataTrail.push({'eyes':eyes, 'screenPos':screenPos, 'type':type});
110
+ }
111
+ self.needsTraining = true;
112
+ };
113
+
114
+ /**
115
+ * Compute coefficient from training data
116
+ */
117
+ function retrain() {
118
+ if (self.screenXClicksArray.length === 0) {
119
+ return;
120
+ }
121
+ if (!self.needsTraining) {
122
+ return;
123
+ }
124
+ var screenXArray = self.screenXClicksArray.data.concat(self.screenXTrailArray.data);
125
+ var screenYArray = self.screenYClicksArray.data.concat(self.screenYTrailArray.data);
126
+ var eyeFeatures = self.eyeFeaturesClicks.data.concat(self.eyeFeaturesTrail.data);
127
+
128
+ var coefficientsX = ridge(screenXArray, eyeFeatures, ridgeParameter);
129
+ var coefficientsY = ridge(screenYArray, eyeFeatures, ridgeParameter);
130
+ self.postMessage({'X':coefficientsX, 'Y': coefficientsY});
131
+ self.needsTraining = false;
132
+ }
133
+
134
+ setInterval(retrain, trainInterval);
135
+
@@ -0,0 +1,348 @@
1
+ const util = {};
2
+
3
+ var resizeWidth = 10;
4
+ var resizeHeight = 6;
5
+
6
+ //not used !?
7
+ /**
8
+ * Eye class, represents an eye patch detected in the video stream
9
+ * @param {ImageData} patch - the image data corresponding to an eye
10
+ * @param {Number} imagex - x-axis offset from the top-left corner of the video canvas
11
+ * @param {Number} imagey - y-axis offset from the top-left corner of the video canvas
12
+ * @param {Number} width - width of the eye patch
13
+ * @param {Number} height - height of the eye patch
14
+ */
15
+ util.Eye = function(patch, imagex, imagey, width, height) {
16
+ this.patch = patch;
17
+ this.imagex = imagex;
18
+ this.imagey = imagey;
19
+ this.width = width;
20
+ this.height = height;
21
+ };
22
+
23
+ /**
24
+ * Compute eyes size as gray histogram
25
+ * @param {Object} eyes - The eyes where looking for gray histogram
26
+ * @returns {Array.<T>} The eyes gray level histogram
27
+ */
28
+ util.getEyeFeats = function(eyes) {
29
+ var resizedLeft = this.resizeEye(eyes.left, resizeWidth, resizeHeight);
30
+ var resizedRight = this.resizeEye(eyes.right, resizeWidth, resizeHeight);
31
+
32
+ var leftGray = this.grayscale(resizedLeft.data, resizedLeft.width, resizedLeft.height);
33
+ var rightGray = this.grayscale(resizedRight.data, resizedRight.width, resizedRight.height);
34
+
35
+ var histLeft = [];
36
+ this.equalizeHistogram(leftGray, 5, histLeft);
37
+ var histRight = [];
38
+ this.equalizeHistogram(rightGray, 5, histRight);
39
+
40
+ // var leftGrayArray = Array.prototype.slice.call(histLeft);
41
+ // var rightGrayArray = Array.prototype.slice.call(histRight);
42
+
43
+ return histLeft.concat(histRight);
44
+ }
45
+ //Data Window class
46
+ //operates like an array but 'wraps' data around to keep the array at a fixed windowSize
47
+ /**
48
+ * DataWindow class - Operates like an array, but 'wraps' data around to keep the array at a fixed windowSize
49
+ * @param {Number} windowSize - defines the maximum size of the window
50
+ * @param {Array} data - optional data to seed the DataWindow with
51
+ **/
52
+ util.DataWindow = function(windowSize, data) {
53
+ this.data = [];
54
+ this.windowSize = windowSize;
55
+ this.index = 0;
56
+ this.length = 0;
57
+ if(data){
58
+ this.data = data.slice(data.length-windowSize,data.length);
59
+ this.length = this.data.length;
60
+ }
61
+ };
62
+
63
+ /**
64
+ * [push description]
65
+ * @param {*} entry - item to be inserted. It either grows the DataWindow or replaces the oldest item
66
+ * @return {DataWindow} this
67
+ */
68
+ util.DataWindow.prototype.push = function(entry) {
69
+ if (this.data.length < this.windowSize) {
70
+ this.data.push(entry);
71
+ this.length = this.data.length;
72
+ return this;
73
+ }
74
+
75
+ //replace oldest entry by wrapping around the DataWindow
76
+ this.data[this.index] = entry;
77
+ this.index = (this.index + 1) % this.windowSize;
78
+ return this;
79
+ };
80
+
81
+ /**
82
+ * Get the element at the ind position by wrapping around the DataWindow
83
+ * @param {Number} ind index of desired entry
84
+ * @return {*}
85
+ */
86
+ util.DataWindow.prototype.get = function(ind) {
87
+ return this.data[this.getTrueIndex(ind)];
88
+ };
89
+
90
+ /**
91
+ * Gets the true this.data array index given an index for a desired element
92
+ * @param {Number} ind - index of desired entry
93
+ * @return {Number} index of desired entry in this.data
94
+ */
95
+ util.DataWindow.prototype.getTrueIndex = function(ind) {
96
+ if (this.data.length < this.windowSize) {
97
+ return ind;
98
+ } else {
99
+ //wrap around ind so that we can traverse from oldest to newest
100
+ return (ind + this.index) % this.windowSize;
101
+ }
102
+ };
103
+
104
+ /**
105
+ * Append all the contents of data
106
+ * @param {Array} data - to be inserted
107
+ */
108
+ util.DataWindow.prototype.addAll = function(data) {
109
+ for (var i = 0; i < data.length; i++) {
110
+ this.push(data[i]);
111
+ }
112
+ };
113
+
114
+
115
+ //Helper functions
116
+ /**
117
+ * Grayscales an image patch. Can be used for the whole canvas, detected face, detected eye, etc.
118
+ *
119
+ * Code from tracking.js by Eduardo Lundgren, et al.
120
+ * https://github.com/eduardolundgren/tracking.js/blob/master/src/tracking.js
121
+ *
122
+ * Software License Agreement (BSD License) Copyright (c) 2014, Eduardo A. Lundgren Melo. All rights reserved.
123
+ * Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
124
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
125
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
126
+ * The name of Eduardo A. Lundgren Melo may not be used to endorse or promote products derived from this software without specific prior written permission of Eduardo A. Lundgren Melo.
127
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
128
+ * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
129
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
130
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
131
+ *
132
+ * @param {Array} pixels - image data to be grayscaled
133
+ * @param {Number} width - width of image data to be grayscaled
134
+ * @param {Number} height - height of image data to be grayscaled
135
+ * @return {Array} grayscaledImage
136
+ */
137
+ util.grayscale = function(pixels, width, height){
138
+ var gray = new Uint8ClampedArray(pixels.length >> 2);
139
+ var p = 0;
140
+ var w = 0;
141
+ for (var i = 0; i < height; i++) {
142
+ for (var j = 0; j < width; j++) {
143
+ var value = pixels[w] * 0.299 + pixels[w + 1] * 0.587 + pixels[w + 2] * 0.114;
144
+ gray[p++] = value;
145
+
146
+ w += 4;
147
+ }
148
+ }
149
+ return gray;
150
+ };
151
+
152
+ /**
153
+ * Increase contrast of an image.
154
+ *
155
+ * Code from Martin Tschirsich, Copyright (c) 2012.
156
+ * https://github.com/mtschirs/js-objectdetect/blob/gh-pages/js/objectdetect.js
157
+ *
158
+ * @param {Array} src - grayscale integer array
159
+ * @param {Number} step - sampling rate, control performance
160
+ * @param {Array} dst - array to hold the resulting image
161
+ */
162
+ util.equalizeHistogram = function(src, step, dst) {
163
+ var srcLength = src.length;
164
+ if (!dst) dst = src;
165
+ if (!step) step = 5;
166
+
167
+ // Compute histogram and histogram sum:
168
+ var hist = Array(256).fill(0);
169
+
170
+ for (var i = 0; i < srcLength; i += step) {
171
+ ++hist[src[i]];
172
+ }
173
+
174
+ // Compute integral histogram:
175
+ var norm = 255 * step / srcLength,
176
+ prev = 0;
177
+ for (var j = 0; j < 256; ++j) {
178
+ var h = hist[j];
179
+ prev = h += prev;
180
+ hist[j] = h * norm; // For non-integer src: ~~(h * norm + 0.5);
181
+ }
182
+
183
+ // Equalize image:
184
+ for (var k = 0; k < srcLength; ++k) {
185
+ dst[k] = hist[src[k]];
186
+ }
187
+ return dst;
188
+ };
189
+
190
+ //not used !?
191
+ util.threshold = function(data, threshold) {
192
+ for (let i = 0; i < data.length; i++) {
193
+ data[i] = (data[i] > threshold) ? 255 : 0;
194
+ }
195
+ return data;
196
+ };
197
+
198
+ //not used !?
199
+ util.correlation = function(data1, data2) {
200
+ const length = Math.min(data1.length, data2.length);
201
+ let count = 0;
202
+ for (let i = 0; i < length; i++) {
203
+ if (data1[i] === data2[i]) {
204
+ count++;
205
+ }
206
+ }
207
+ return count / Math.max(data1.length, data2.length);
208
+ };
209
+
210
+ /**
211
+ * Gets an Eye object and resizes it to the desired resolution
212
+ * @param {webgazer.util.Eye} eye - patch to be resized
213
+ * @param {Number} resizeWidth - desired width
214
+ * @param {Number} resizeHeight - desired height
215
+ * @return {webgazer.util.Eye} resized eye patch
216
+ */
217
+ util.resizeEye = function(eye, resizeWidth, resizeHeight) {
218
+
219
+ var canvas = document.createElement('canvas');
220
+ canvas.width = eye.width;
221
+ canvas.height = eye.height;
222
+
223
+ canvas.getContext('2d').putImageData(eye.patch,0,0);
224
+
225
+ var tempCanvas = document.createElement('canvas');
226
+
227
+ tempCanvas.width = resizeWidth;
228
+ tempCanvas.height = resizeHeight;
229
+
230
+ // save the canvas into temp canvas
231
+ tempCanvas.getContext('2d').drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, resizeWidth, resizeHeight);
232
+
233
+ return tempCanvas.getContext('2d').getImageData(0, 0, resizeWidth, resizeHeight);
234
+ };
235
+
236
+ /**
237
+ * Checks if the prediction is within the boundaries of the viewport and constrains it
238
+ * @param {Array} prediction [x,y] - predicted gaze coordinates
239
+ * @return {Array} constrained coordinates
240
+ */
241
+ util.bound = function(prediction){
242
+ prediction.x = _constrain(prediction.x, 0, window.innerWidth);
243
+ prediction.y = _constrain(prediction.y, 0, window.innerHeight);
244
+ return prediction
245
+ };
246
+
247
+ function _constrain(a, b0, b1) {
248
+ return (a < b0) ? b0 : (a > b1) ? b1 : a
249
+ }
250
+
251
+ //not used !?
252
+ /**
253
+ * Write statistics in debug paragraph panel
254
+ * @param {HTMLElement} para - The <p> tag where write data
255
+ * @param {Object} stats - The stats data to output
256
+ */
257
+ function debugBoxWrite(para, stats) {
258
+ var str = '';
259
+ for (var key in stats) {
260
+ str += key + ': ' + stats[key] + '\n';
261
+ }
262
+ para.innerText = str;
263
+ }
264
+
265
+ //not used !?
266
+ /**
267
+ * Constructor of DebugBox object,
268
+ * it insert an paragraph inside a div to the body, in view to display debug data
269
+ * @param {Number} interval - The log interval
270
+ * @constructor
271
+ */
272
+ util.DebugBox = function(interval) {
273
+ this.para = document.createElement('p');
274
+ this.div = document.createElement('div');
275
+ this.div.appendChild(this.para);
276
+ document.body.appendChild(this.div);
277
+
278
+ this.buttons = {};
279
+ this.canvas = {};
280
+ this.stats = {};
281
+ var updateInterval = interval || 300;
282
+ (function(localThis) {
283
+ setInterval(function() {
284
+ debugBoxWrite(localThis.para, localThis.stats);
285
+ }, updateInterval);
286
+ }(this));
287
+ };
288
+
289
+ //not used !?
290
+ /**
291
+ * Add stat data for log
292
+ * @param {String} key - The data key
293
+ * @param {*} value - The value
294
+ */
295
+ util.DebugBox.prototype.set = function(key, value) {
296
+ this.stats[key] = value;
297
+ };
298
+
299
+ //not used !?
300
+ /**
301
+ * Initialize stats in case where key does not exist, else
302
+ * increment value for key
303
+ * @param {String} key - The key to process
304
+ * @param {Number} incBy - Value to increment for given key (default: 1)
305
+ * @param {Number} init - Initial value in case where key does not exist (default: 0)
306
+ */
307
+ util.DebugBox.prototype.inc = function(key, incBy, init) {
308
+ if (!this.stats[key]) {
309
+ this.stats[key] = init || 0;
310
+ }
311
+ this.stats[key] += incBy || 1;
312
+ };
313
+
314
+ //not used !?
315
+ /**
316
+ * Create a button and register the given function to the button click event
317
+ * @param {String} name - The button name to link
318
+ * @param {Function} func - The onClick callback
319
+ */
320
+ util.DebugBox.prototype.addButton = function(name, func) {
321
+ if (!this.buttons[name]) {
322
+ this.buttons[name] = document.createElement('button');
323
+ this.div.appendChild(this.buttons[name]);
324
+ }
325
+ var button = this.buttons[name];
326
+ this.buttons[name] = button;
327
+ button.addEventListener('click', func);
328
+ button.innerText = name;
329
+ };
330
+
331
+ //not used !?
332
+ /**
333
+ * Search for a canvas elemenet with name, or create on if not exist.
334
+ * Then send the canvas element as callback parameter.
335
+ * @param {String} name - The canvas name to send/create
336
+ * @param {Function} func - The callback function where send canvas
337
+ */
338
+ util.DebugBox.prototype.show = function(name, func) {
339
+ if (!this.canvas[name]) {
340
+ this.canvas[name] = document.createElement('canvas');
341
+ this.div.appendChild(this.canvas[name]);
342
+ }
343
+ var canvas = this.canvas[name];
344
+ canvas.getContext('2d').clearRect(0,0, canvas.width, canvas.height);
345
+ func(canvas);
346
+ };
347
+
348
+ export default util;