remote-calibrator 0.3.0 → 0.5.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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;