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,301 @@
1
+ const mat = {};
2
+ /**
3
+ * Transposes an mxn array
4
+ * @param {Array.<Array.<Number>>} matrix - of 'M x N' dimensionality
5
+ * @return {Array.<Array.<Number>>} transposed matrix
6
+ */
7
+ mat.transpose = function(matrix){
8
+ var m = matrix.length;
9
+ var n = matrix[0].length;
10
+ var transposedMatrix = new Array(n);
11
+
12
+ for (var i = 0; i < m; i++){
13
+ for (var j = 0; j < n; j++){
14
+ if (i === 0) transposedMatrix[j] = new Array(m);
15
+ transposedMatrix[j][i] = matrix[i][j];
16
+ }
17
+ }
18
+
19
+ return transposedMatrix;
20
+ };
21
+
22
+ /**
23
+ * Get a sub-matrix of matrix
24
+ * @param {Array.<Array.<Number>>} matrix - original matrix
25
+ * @param {Array.<Number>} r - Array of row indices
26
+ * @param {Number} j0 - Initial column index
27
+ * @param {Number} j1 - Final column index
28
+ * @returns {Array} The sub-matrix matrix(r(:),j0:j1)
29
+ */
30
+ mat.getMatrix = function(matrix, r, j0, j1){
31
+ var X = new Array(r.length),
32
+ m = j1-j0+1;
33
+
34
+ for (var i = 0; i < r.length; i++){
35
+ X[i] = new Array(m);
36
+ for (var j = j0; j <= j1; j++){
37
+ X[i][j-j0] = matrix[r[i]][j];
38
+ }
39
+ }
40
+ return X;
41
+ };
42
+
43
+ /**
44
+ * Get a submatrix of matrix
45
+ * @param {Array.<Array.<Number>>} matrix - original matrix
46
+ * @param {Number} i0 - Initial row index
47
+ * @param {Number} i1 - Final row index
48
+ * @param {Number} j0 - Initial column index
49
+ * @param {Number} j1 - Final column index
50
+ * @return {Array} The sub-matrix matrix(i0:i1,j0:j1)
51
+ */
52
+ mat.getSubMatrix = function(matrix, i0, i1, j0, j1){
53
+ var size = j1 - j0 + 1,
54
+ X = new Array(i1-i0+1);
55
+
56
+ for (var i = i0; i <= i1; i++){
57
+ var subI = i-i0;
58
+
59
+ X[subI] = new Array(size);
60
+
61
+ for (var j = j0; j <= j1; j++){
62
+ X[subI][j-j0] = matrix[i][j];
63
+ }
64
+ }
65
+ return X;
66
+ };
67
+
68
+ /**
69
+ * Linear algebraic matrix multiplication, matrix1 * matrix2
70
+ * @param {Array.<Array.<Number>>} matrix1
71
+ * @param {Array.<Array.<Number>>} matrix2
72
+ * @return {Array.<Array.<Number>>} Matrix product, matrix1 * matrix2
73
+ */
74
+ mat.mult = function(matrix1, matrix2){
75
+
76
+ if (matrix2.length != matrix1[0].length){
77
+ console.log('Matrix inner dimensions must agree:');
78
+
79
+ }
80
+
81
+ var X = new Array(matrix1.length),
82
+ Bcolj = new Array(matrix1[0].length);
83
+
84
+ for (var j = 0; j < matrix2[0].length; j++){
85
+ for (var k = 0; k < matrix1[0].length; k++){
86
+ Bcolj[k] = matrix2[k][j];
87
+ }
88
+ for (var i = 0; i < matrix1.length; i++){
89
+
90
+ if (j === 0)
91
+ X[i] = new Array(matrix2[0].length);
92
+
93
+ var Arowi = matrix1[i];
94
+ var s = 0;
95
+ for (var k = 0; k < matrix1[0].length; k++){
96
+ s += Arowi[k]*Bcolj[k];
97
+ }
98
+ X[i][j] = s;
99
+ }
100
+ }
101
+ return X;
102
+ };
103
+
104
+
105
+ /**
106
+ * LUDecomposition to solve A*X = B, based on WEKA code
107
+ * @param {Array.<Array.<Number>>} A - left matrix of equation to be solved
108
+ * @param {Array.<Array.<Number>>} B - right matrix of equation to be solved
109
+ * @return {Array.<Array.<Number>>} X so that L*U*X = B(piv,:)
110
+ */
111
+ mat.LUDecomposition = function(A,B){
112
+ var LU = new Array(A.length);
113
+
114
+ for (var i = 0; i < A.length; i++){
115
+ LU[i] = new Array(A[0].length);
116
+ for (var j = 0; j < A[0].length; j++){
117
+ LU[i][j] = A[i][j];
118
+ }
119
+ }
120
+
121
+ var m = A.length;
122
+ var n = A[0].length;
123
+ var piv = new Array(m);
124
+ for (var i = 0; i < m; i++){
125
+ piv[i] = i;
126
+ }
127
+ var pivsign = 1;
128
+ var LUrowi = new Array();
129
+ var LUcolj = new Array(m);
130
+ // Outer loop.
131
+ for (var j = 0; j < n; j++){
132
+ // Make a copy of the j-th column to localize references.
133
+ for (var i = 0; i < m; i++){
134
+ LUcolj[i] = LU[i][j];
135
+ }
136
+ // Apply previous transformations.
137
+ for (var i = 0; i < m; i++){
138
+ LUrowi = LU[i];
139
+ // Most of the time is spent in the following dot product.
140
+ var kmax = Math.min(i,j);
141
+ var s = 0;
142
+ for (var k = 0; k < kmax; k++){
143
+ s += LUrowi[k]*LUcolj[k];
144
+ }
145
+ LUrowi[j] = LUcolj[i] -= s;
146
+ }
147
+ // Find pivot and exchange if necessary.
148
+ var p = j;
149
+ for (var i = j+1; i < m; i++){
150
+ if (Math.abs(LUcolj[i]) > Math.abs(LUcolj[p])){
151
+ p = i;
152
+ }
153
+ }
154
+ if (p != j){
155
+ for (var k = 0; k < n; k++){
156
+ var t = LU[p][k];
157
+ LU[p][k] = LU[j][k];
158
+ LU[j][k] = t;
159
+ }
160
+ var k = piv[p];
161
+ piv[p] = piv[j];
162
+ piv[j] = k;
163
+ pivsign = -pivsign;
164
+ }
165
+ // Compute multipliers.
166
+ if (j < m & LU[j][j] != 0){
167
+ for (var i = j+1; i < m; i++){
168
+ LU[i][j] /= LU[j][j];
169
+ }
170
+ }
171
+ }
172
+ if (B.length != m){
173
+ console.log('Matrix row dimensions must agree.');
174
+ }
175
+ for (var j = 0; j < n; j++){
176
+ if (LU[j][j] === 0){
177
+ console.log('Matrix is singular.')
178
+ }
179
+ }
180
+ var nx = B[0].length;
181
+ var X = self.webgazer.mat.getMatrix(B,piv,0,nx-1);
182
+ // Solve L*Y = B(piv,:)
183
+ for (var k = 0; k < n; k++){
184
+ for (var i = k+1; i < n; i++){
185
+ for (var j = 0; j < nx; j++){
186
+ X[i][j] -= X[k][j]*LU[i][k];
187
+ }
188
+ }
189
+ }
190
+ // Solve U*X = Y;
191
+ for (var k = n-1; k >= 0; k--){
192
+ for (var j = 0; j < nx; j++){
193
+ X[k][j] /= LU[k][k];
194
+ }
195
+ for (var i = 0; i < k; i++){
196
+ for (var j = 0; j < nx; j++){
197
+ X[i][j] -= X[k][j]*LU[i][k];
198
+ }
199
+ }
200
+ }
201
+ return X;
202
+ };
203
+
204
+ /**
205
+ * Least squares solution of A*X = B, based on WEKA code
206
+ * @param {Array.<Array.<Number>>} A - left side matrix to be solved
207
+ * @param {Array.<Array.<Number>>} B - a matrix with as many rows as A and any number of columns.
208
+ * @return {Array.<Array.<Number>>} X - that minimizes the two norms of QR*X-B.
209
+ */
210
+ mat.QRDecomposition = function(A, B){
211
+ // Initialize.
212
+ var QR = new Array(A.length);
213
+
214
+ for (var i = 0; i < A.length; i++){
215
+ QR[i] = new Array(A[0].length);
216
+ for (var j = 0; j < A[0].length; j++){
217
+ QR[i][j] = A[i][j];
218
+ }
219
+ }
220
+ var m = A.length;
221
+ var n = A[0].length;
222
+ var Rdiag = new Array(n);
223
+ var nrm;
224
+
225
+ // Main loop.
226
+ for (var k = 0; k < n; k++){
227
+ // Compute 2-norm of k-th column without under/overflow.
228
+ nrm = 0;
229
+ for (var i = k; i < m; i++){
230
+ nrm = Math.hypot(nrm,QR[i][k]);
231
+ }
232
+ if (nrm != 0){
233
+ // Form k-th Householder vector.
234
+ if (QR[k][k] < 0){
235
+ nrm = -nrm;
236
+ }
237
+ for (var i = k; i < m; i++){
238
+ QR[i][k] /= nrm;
239
+ }
240
+ QR[k][k] += 1;
241
+
242
+ // Apply transformation to remaining columns.
243
+ for (var j = k+1; j < n; j++){
244
+ var s = 0;
245
+ for (var i = k; i < m; i++){
246
+ s += QR[i][k]*QR[i][j];
247
+ }
248
+ s = -s/QR[k][k];
249
+ for (var i = k; i < m; i++){
250
+ QR[i][j] += s*QR[i][k];
251
+ }
252
+ }
253
+ }
254
+ Rdiag[k] = -nrm;
255
+ }
256
+ if (B.length != m){
257
+ console.log('Matrix row dimensions must agree.');
258
+ }
259
+ for (var j = 0; j < n; j++){
260
+ if (Rdiag[j] === 0)
261
+ console.log('Matrix is rank deficient');
262
+ }
263
+ // Copy right hand side
264
+ var nx = B[0].length;
265
+ var X = new Array(B.length);
266
+ for(var i=0; i<B.length; i++){
267
+ X[i] = new Array(B[0].length);
268
+ }
269
+ for (var i = 0; i < B.length; i++){
270
+ for (var j = 0; j < B[0].length; j++){
271
+ X[i][j] = B[i][j];
272
+ }
273
+ }
274
+ // Compute Y = transpose(Q)*B
275
+ for (var k = 0; k < n; k++){
276
+ for (var j = 0; j < nx; j++){
277
+ var s = 0.0;
278
+ for (var i = k; i < m; i++){
279
+ s += QR[i][k]*X[i][j];
280
+ }
281
+ s = -s/QR[k][k];
282
+ for (var i = k; i < m; i++){
283
+ X[i][j] += s*QR[i][k];
284
+ }
285
+ }
286
+ }
287
+ // Solve R*X = Y;
288
+ for (var k = n-1; k >= 0; k--){
289
+ for (var j = 0; j < nx; j++){
290
+ X[k][j] /= Rdiag[k];
291
+ }
292
+ for (var i = 0; i < k; i++){
293
+ for (var j = 0; j < nx; j++){
294
+ X[i][j] -= X[k][j]*QR[i][k];
295
+ }
296
+ }
297
+ }
298
+ return mat.getSubMatrix(X,0,n-1,0,nx-1);
299
+ }
300
+
301
+ export default mat;
@@ -0,0 +1,29 @@
1
+ const params = {
2
+ moveTickSize: 50,
3
+ videoContainerId: 'webgazerVideoContainer',
4
+ videoElementId: 'webgazerVideoFeed',
5
+ videoElementCanvasId: 'webgazerVideoCanvas',
6
+ faceOverlayId: 'webgazerFaceOverlay',
7
+ faceFeedbackBoxId: 'webgazerFaceFeedbackBox',
8
+ gazeDotId: 'webgazerGazeDot',
9
+ videoViewerWidth: 320,
10
+ videoViewerHeight: 240,
11
+ faceFeedbackBoxRatio: 0.66,
12
+ // View options
13
+ showVideo: true,
14
+ mirrorVideo: true,
15
+ showFaceOverlay: true,
16
+ showFaceFeedbackBox: true,
17
+ showGazeDot: true,
18
+ camConstraints: { video: { width: { min: 320, ideal: 640, max: 1920 }, height: { min: 240, ideal: 480, max: 1080 }, facingMode: "user" } },
19
+ dataTimestep: 50,
20
+ showVideoPreview: true,
21
+ applyKalmanFilter: true,
22
+ saveDataAcrossSessions: true,
23
+ // Whether or not to store accuracy eigenValues, used by the calibration example file
24
+ storingPoints: false,
25
+ ////
26
+ videoIsOn: false,
27
+ };
28
+
29
+ export default params;
@@ -0,0 +1,109 @@
1
+ const pupil = {};
2
+
3
+ /**
4
+ * Returns intensity value at x,y position of a pixels image
5
+ * @param {Array} pixels - array of size width*height
6
+ * @param {Number} x - input x value
7
+ * @param {Number} y - input y value
8
+ * @param {Number} width - width of pixels image
9
+ * @returns {Number} - intensity value in [0,255]
10
+ */
11
+ const getValue = function (pixels, x, y, width){
12
+ return pixels[y * width + x];
13
+ };
14
+
15
+ /**
16
+ * Computes summation area table/integral image of a pixel matrix
17
+ * @param {Array} pixels value of eye area
18
+ * @param {Number} width - of image in 'pixels'
19
+ * @param {Number} height - of image in 'pixels'
20
+ * @returns {Array} - integral image
21
+ */
22
+ const getSumTable = function (pixels, width, height){
23
+ var integralImage = new Array(width);
24
+ var sumx = 0;
25
+ var sumy = 0;
26
+
27
+ for (var i = 0; i < width; i++){
28
+ integralImage[i] = new Array(height);
29
+ sumx += getValue(pixels, i, 0, width);
30
+ integralImage[i][0] = sumx;
31
+ }
32
+
33
+ for (var i = 0; i < height; i++){
34
+ sumy += getValue(pixels, 0, i, width);
35
+ integralImage[0][i] = sumy;
36
+ }
37
+
38
+ for (var x = 1; x < width; x++){
39
+ for (var y = 1; y < height; y++){
40
+ integralImage[x][y] = getValue(pixels, x, y, width) + integralImage[x - 1][y] + integralImage[x][y - 1] - integralImage[x - 1][y - 1];
41
+ }
42
+ }
43
+ return integralImage;
44
+ };
45
+
46
+ /**
47
+ * Detects a pupil in a set of pixels
48
+ * @param {Array} pixels - patch of pixels to look for pupil into
49
+ * @param {Number} width - of pixel patch
50
+ * @param {Number} height - of pixel patch
51
+ * @return {Array} coordinate of the bottom right corner and width of the best fitted pupil
52
+ */
53
+ const getSinglePupil = function (pixels, width, height){
54
+ var summedAreaTable = getSumTable(pixels, width, height);
55
+ var bestAvgScore = 999999; //want to minimize this score
56
+ var bestPoint = [0, 0]; //bottom right corner of best fitted pupil
57
+ var bestHalfWidth = 0; //corresponding half width of the best fitted pupil
58
+ var offset = Math.floor(width / 10.0); //padding
59
+ //halfWidth could also start at 1, but this makes it faster
60
+ for (var halfWidth = Math.floor(height / 10.0); halfWidth < width / 2; halfWidth++){
61
+ //think of a sliding rectangular window of width halfWidth*2 that goes through the whole eye pixel matrix and does the following:
62
+ //1) computes the irisArea, which is the total intensity of the iris
63
+ //2) computes the scleraIrisArea, which is multiple rows of pixels including the sclera and iris.
64
+ //3) computes avg, which is the intensity of the area divided by the number of pixels.
65
+ //start at the bottom right of the rectangle!not top left
66
+ for (var x = halfWidth; x < width - offset; x++){
67
+ for (var y = halfWidth; y < height - offset; y++){
68
+ //evaluate area by the formula found on wikipedia about the summed area table: I(D)+I(A)-I(B)-I(C)
69
+ var irisArea = summedAreaTable[x + offset][y + offset] + summedAreaTable[x + offset - halfWidth][y + offset - halfWidth] - summedAreaTable[x + offset][y + offset - halfWidth] - summedAreaTable[x + offset - halfWidth][y + offset];
70
+ var avgScore = 1.0 * irisArea / ((halfWidth + 1) * (halfWidth + 1)) + 1;
71
+ //summation area table again
72
+ var scleraIrisArea = ((1.0 * summedAreaTable[width - 1 - offset][y + offset] + summedAreaTable[0 + offset][y + offset - halfWidth] - summedAreaTable[0 + offset][y + offset] - summedAreaTable[width - 1 - offset][y + offset - halfWidth]) - irisArea);
73
+ //minimize avgScore/scleraIrisArea. 150 is too high, might have to change since it's closer to white
74
+ if ((avgScore) / scleraIrisArea < bestAvgScore && avgScore < 150){
75
+ bestAvgScore = (avgScore) / scleraIrisArea;
76
+ bestPoint = [x + offset, y + offset];
77
+ bestHalfWidth = halfWidth;
78
+ }
79
+ }
80
+ }
81
+ }
82
+ return [bestPoint, bestHalfWidth];
83
+ };
84
+
85
+ /**
86
+ * Given an object with two eye patches it finds the location of the detected pupils
87
+ * @param {Object} eyesObj - left and right detected eye patches
88
+ * @return {Object} eyesObj - updated eye patches with information about pupils' locations
89
+ */
90
+ const getPupils = function(eyesObj) {
91
+ if (!eyesObj) {
92
+ return eyesObj;
93
+ }
94
+ if (!eyesObj.left.blink) {
95
+ eyesObj.left.pupil = getSinglePupil(Array.prototype.slice.call(webgazer.util.grayscale(eyesObj.left.patch.data, eyesObj.left.width, eyesObj.left.height)), eyesObj.left.width, eyesObj.left.height);
96
+ eyesObj.left.pupil[0][0] -= eyesObj.left.pupil[1];
97
+ eyesObj.left.pupil[0][1] -= eyesObj.left.pupil[1];
98
+ }
99
+ if (!eyesObj.right.blink) {
100
+ eyesObj.right.pupil = getSinglePupil(Array.prototype.slice.call(webgazer.util.grayscale(eyesObj.right.patch.data, eyesObj.right.width, eyesObj.right.height)), eyesObj.right.width, eyesObj.right.height);
101
+ eyesObj.right.pupil[0][0] -= eyesObj.right.pupil[1];
102
+ eyesObj.right.pupil[0][1] -= eyesObj.right.pupil[1];
103
+ }
104
+ return eyesObj;
105
+ }
106
+
107
+ pupil.getPupils = getPupils;
108
+
109
+ export default pupil;
@@ -0,0 +1,104 @@
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 RidgeReg object,
9
+ * this object allow to perform ridge regression
10
+ * @constructor
11
+ */
12
+ reg.RidgeReg = function() {
13
+ this.init();
14
+ };
15
+
16
+ /**
17
+ * Initialize new arrays and initialize Kalman filter.
18
+ */
19
+ reg.RidgeReg.prototype.init = util_regression.InitRegression
20
+
21
+ /**
22
+ * Add given data from eyes
23
+ * @param {Object} eyes - eyes where extract data to add
24
+ * @param {Object} screenPos - The current screen point
25
+ * @param {Object} type - The type of performed action
26
+ */
27
+ reg.RidgeReg.prototype.addData = util_regression.addData
28
+
29
+ /**
30
+ * Try to predict coordinates from pupil data
31
+ * after apply linear regression on data set
32
+ * @param {Object} eyesObj - The current user eyes object
33
+ * @returns {Object}
34
+ */
35
+ reg.RidgeReg.prototype.predict = function(eyesObj) {
36
+ if (!eyesObj || this.eyeFeaturesClicks.length === 0) {
37
+ return null;
38
+ }
39
+ var acceptTime = performance.now() - this.trailTime;
40
+ var trailX = [];
41
+ var trailY = [];
42
+ var trailFeat = [];
43
+ for (var i = 0; i < this.trailDataWindow; i++) {
44
+ if (this.trailTimes.get(i) > acceptTime) {
45
+ trailX.push(this.screenXTrailArray.get(i));
46
+ trailY.push(this.screenYTrailArray.get(i));
47
+ trailFeat.push(this.eyeFeaturesTrail.get(i));
48
+ }
49
+ }
50
+
51
+ var screenXArray = this.screenXClicksArray.data.concat(trailX);
52
+ var screenYArray = this.screenYClicksArray.data.concat(trailY);
53
+ var eyeFeatures = this.eyeFeaturesClicks.data.concat(trailFeat);
54
+
55
+ var coefficientsX = util_regression.ridge(screenXArray, eyeFeatures, this.ridgeParameter);
56
+ var coefficientsY = util_regression.ridge(screenYArray, eyeFeatures, this.ridgeParameter);
57
+
58
+ var eyeFeats = util.getEyeFeats(eyesObj);
59
+ var predictedX = 0;
60
+ for(var i=0; i< eyeFeats.length; i++){
61
+ predictedX += eyeFeats[i] * coefficientsX[i];
62
+ }
63
+ var predictedY = 0;
64
+ for(var i=0; i< eyeFeats.length; i++){
65
+ predictedY += eyeFeats[i] * coefficientsY[i];
66
+ }
67
+
68
+ predictedX = Math.floor(predictedX);
69
+ predictedY = Math.floor(predictedY);
70
+
71
+ if (params.applyKalmanFilter) {
72
+ // Update Kalman model, and get prediction
73
+ var newGaze = [predictedX, predictedY]; // [20200607 xk] Should we use a 1x4 vector?
74
+ newGaze = this.kalman.update(newGaze);
75
+
76
+ return {
77
+ x: newGaze[0],
78
+ y: newGaze[1]
79
+ };
80
+ } else {
81
+ return {
82
+ x: predictedX,
83
+ y: predictedY
84
+ };
85
+ }
86
+ };
87
+
88
+ reg.RidgeReg.prototype.setData = util_regression.setData;
89
+
90
+ /**
91
+ * Return the data
92
+ * @returns {Array.<Object>|*}
93
+ */
94
+ reg.RidgeReg.prototype.getData = function() {
95
+ return this.dataClicks.data;
96
+ }
97
+
98
+ /**
99
+ * The RidgeReg object name
100
+ * @type {string}
101
+ */
102
+ reg.RidgeReg.prototype.name = 'ridge';
103
+
104
+ export default reg;
@@ -0,0 +1,161 @@
1
+ import util from './util.mjs';
2
+ import numeric from 'numeric';
3
+ import util_regression from './util_regression.mjs';
4
+ import params from './params.mjs';
5
+
6
+ const reg = {};
7
+
8
+ var ridgeParameter = Math.pow(10,-5);
9
+ var dataWindow = 700;
10
+ var weights = {'X':[0],'Y':[0]};
11
+ var trailDataWindow = 10;
12
+
13
+
14
+ /**
15
+ * Constructor of RidgeRegThreaded object,
16
+ * it retrieve data window, and prepare a worker,
17
+ * this object allow to perform threaded ridge regression
18
+ * @constructor
19
+ */
20
+ reg.RidgeRegThreaded = function() {
21
+ this.init();
22
+ };
23
+
24
+ /**
25
+ * Initialize new arrays and initialize Kalman filter.
26
+ */
27
+ reg.RidgeRegThreaded.prototype.init = function() {
28
+ this.screenXClicksArray = new util.DataWindow(dataWindow);
29
+ this.screenYClicksArray = new util.DataWindow(dataWindow);
30
+ this.eyeFeaturesClicks = new util.DataWindow(dataWindow);
31
+
32
+ this.screenXTrailArray = new util.DataWindow(trailDataWindow);
33
+ this.screenYTrailArray = new util.DataWindow(trailDataWindow);
34
+ this.eyeFeaturesTrail = new util.DataWindow(trailDataWindow);
35
+
36
+ this.dataClicks = new util.DataWindow(dataWindow);
37
+ this.dataTrail = new util.DataWindow(dataWindow);
38
+
39
+ // Place the src/ridgeworker.js file into the same directory as your html file.
40
+ if (!this.worker) {
41
+ this.worker = new Worker('ridgeWorker.mjs'); // [20200708] TODO: Figure out how to make this inline
42
+ this.worker.onerror = function(err) { console.log(err.message); };
43
+ this.worker.onmessage = function(evt) {
44
+ weights.X = evt.data.X;
45
+ weights.Y = evt.data.Y;
46
+ };
47
+ console.log('initialized worker');
48
+ }
49
+
50
+ // Initialize Kalman filter [20200608 xk] what do we do about parameters?
51
+ // [20200611 xk] unsure what to do w.r.t. dimensionality of these matrices. So far at least
52
+ // by my own anecdotal observation a 4x1 x vector seems to work alright
53
+ var F = [ [1, 0, 1, 0],
54
+ [0, 1, 0, 1],
55
+ [0, 0, 1, 0],
56
+ [0, 0, 0, 1]];
57
+
58
+ //Parameters Q and R may require some fine tuning
59
+ var Q = [ [1/4, 0, 1/2, 0],
60
+ [0, 1/4, 0, 1/2],
61
+ [1/2, 0, 1, 0],
62
+ [0, 1/2, 0, 1]];// * delta_t
63
+ var delta_t = 1/10; // The amount of time between frames
64
+ Q = numeric.mul(Q, delta_t);
65
+
66
+ var H = [ [1, 0, 0, 0, 0, 0],
67
+ [0, 1, 0, 0, 0, 0],
68
+ [0, 0, 1, 0, 0, 0],
69
+ [0, 0, 0, 1, 0, 0]];
70
+ var H = [ [1, 0, 0, 0],
71
+ [0, 1, 0, 0]];
72
+ var pixel_error = 47; //We will need to fine tune this value [20200611 xk] I just put a random value here
73
+
74
+ //This matrix represents the expected measurement error
75
+ var R = numeric.mul(numeric.identity(2), pixel_error);
76
+
77
+ var P_initial = numeric.mul(numeric.identity(4), 0.0001); //Initial covariance matrix
78
+ var x_initial = [[500], [500], [0], [0]]; // Initial measurement matrix
79
+
80
+ this.kalman = new util_regression.KalmanFilter(F, H, Q, R, P_initial, x_initial);
81
+ }
82
+ /**
83
+ * Add given data from eyes
84
+ * @param {Object} eyes - eyes where extract data to add
85
+ * @param {Object} screenPos - The current screen point
86
+ * @param {Object} type - The type of performed action
87
+ */
88
+ reg.RidgeRegThreaded.prototype.addData = function(eyes, screenPos, type) {
89
+ if (!eyes) {
90
+ return;
91
+ }
92
+ //not doing anything with blink at present
93
+ // if (eyes.left.blink || eyes.right.blink) {
94
+ // return;
95
+ // }
96
+ this.worker.postMessage({'eyes':util.getEyeFeats(eyes), 'screenPos':screenPos, 'type':type});
97
+ };
98
+
99
+ /**
100
+ * Try to predict coordinates from pupil data
101
+ * after apply linear regression on data set
102
+ * @param {Object} eyesObj - The current user eyes object
103
+ * @returns {Object}
104
+ */
105
+ reg.RidgeRegThreaded.prototype.predict = function(eyesObj) {
106
+ // console.log('LOGGING..');
107
+ if (!eyesObj) {
108
+ return null;
109
+ }
110
+ var coefficientsX = weights.X;
111
+ var coefficientsY = weights.Y;
112
+
113
+ var eyeFeats = util.getEyeFeats(eyesObj);
114
+ var predictedX = 0, predictedY = 0;
115
+ for(var i=0; i< eyeFeats.length; i++){
116
+ predictedX += eyeFeats[i] * coefficientsX[i];
117
+ predictedY += eyeFeats[i] * coefficientsY[i];
118
+ }
119
+
120
+ predictedX = Math.floor(predictedX);
121
+ predictedY = Math.floor(predictedY);
122
+
123
+ if (params.applyKalmanFilter) {
124
+ // Update Kalman model, and get prediction
125
+ var newGaze = [predictedX, predictedY]; // [20200607 xk] Should we use a 1x4 vector?
126
+ newGaze = this.kalman.update(newGaze);
127
+
128
+ return {
129
+ x: newGaze[0],
130
+ y: newGaze[1]
131
+ };
132
+ } else {
133
+ return {
134
+ x: predictedX,
135
+ y: predictedY
136
+ };
137
+ }
138
+ };
139
+
140
+ /**
141
+ * Add given data to current data set then,
142
+ * replace current data member with given data
143
+ * @param {Array.<Object>} data - The data to set
144
+ */
145
+ reg.RidgeRegThreaded.prototype.setData = util_regression.setData
146
+
147
+ /**
148
+ * Return the data
149
+ * @returns {Array.<Object>|*}
150
+ */
151
+ reg.RidgeRegThreaded.prototype.getData = function() {
152
+ return this.dataClicks.data;
153
+ };
154
+
155
+ /**
156
+ * The RidgeRegThreaded object name
157
+ * @type {string}
158
+ */
159
+ reg.RidgeRegThreaded.prototype.name = 'ridge';
160
+
161
+ export default reg;