remote-calibrator 0.2.1 → 0.2.2-beta.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +2 -0
  3. package/homepage/example.js +11 -13
  4. package/lib/RemoteCalibrator.min.js +1 -1
  5. package/lib/RemoteCalibrator.min.js.LICENSE.txt +1 -1
  6. package/lib/RemoteCalibrator.min.js.map +1 -1
  7. package/package.json +8 -8
  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/core.js +18 -0
  37. package/src/css/distance.scss +1 -0
  38. package/src/distance/distance.js +4 -1
  39. package/src/distance/distanceTrack.js +2 -2
  40. package/src/panel/panel.js +14 -5
  41. package/src/text.json +4 -2
@@ -0,0 +1,240 @@
1
+ import util from './util.mjs';
2
+ import numeric from 'numeric';
3
+ import mat from './mat.mjs';
4
+ import params from './params.mjs';
5
+
6
+ const util_regression = {};
7
+
8
+
9
+ /**
10
+ * Initialize new arrays and initialize Kalman filter for regressions.
11
+ */
12
+ util_regression.InitRegression = function() {
13
+ var dataWindow = 700;
14
+ var trailDataWindow = 10;
15
+ this.ridgeParameter = Math.pow(10,-5);
16
+ this.errorXArray = new util.DataWindow(dataWindow);
17
+ this.errorYArray = new util.DataWindow(dataWindow);
18
+
19
+
20
+ this.screenXClicksArray = new util.DataWindow(dataWindow);
21
+ this.screenYClicksArray = new util.DataWindow(dataWindow);
22
+ this.eyeFeaturesClicks = new util.DataWindow(dataWindow);
23
+
24
+ //sets to one second worth of cursor trail
25
+ this.trailTime = 1000;
26
+ this.trailDataWindow = this.trailTime / params.moveTickSize;
27
+ this.screenXTrailArray = new util.DataWindow(trailDataWindow);
28
+ this.screenYTrailArray = new util.DataWindow(trailDataWindow);
29
+ this.eyeFeaturesTrail = new util.DataWindow(trailDataWindow);
30
+ this.trailTimes = new util.DataWindow(trailDataWindow);
31
+
32
+ this.dataClicks = new util.DataWindow(dataWindow);
33
+ this.dataTrail = new util.DataWindow(trailDataWindow);
34
+
35
+ // Initialize Kalman filter [20200608 xk] what do we do about parameters?
36
+ // [20200611 xk] unsure what to do w.r.t. dimensionality of these matrices. So far at least
37
+ // by my own anecdotal observation a 4x1 x vector seems to work alright
38
+ var F = [ [1, 0, 1, 0],
39
+ [0, 1, 0, 1],
40
+ [0, 0, 1, 0],
41
+ [0, 0, 0, 1]];
42
+
43
+ //Parameters Q and R may require some fine tuning
44
+ var Q = [ [1/4, 0, 1/2, 0],
45
+ [0, 1/4, 0, 1/2],
46
+ [1/2, 0, 1, 0],
47
+ [0, 1/2, 0, 1]];// * delta_t
48
+ var delta_t = 1/10; // The amount of time between frames
49
+ Q = numeric.mul(Q, delta_t);
50
+
51
+ var H = [ [1, 0, 0, 0, 0, 0],
52
+ [0, 1, 0, 0, 0, 0],
53
+ [0, 0, 1, 0, 0, 0],
54
+ [0, 0, 0, 1, 0, 0]];
55
+ var H = [ [1, 0, 0, 0],
56
+ [0, 1, 0, 0]];
57
+ var pixel_error = 47; //We will need to fine tune this value [20200611 xk] I just put a random value here
58
+
59
+ //This matrix represents the expected measurement error
60
+ var R = numeric.mul(numeric.identity(2), pixel_error);
61
+
62
+ var P_initial = numeric.mul(numeric.identity(4), 0.0001); //Initial covariance matrix
63
+ var x_initial = [[500], [500], [0], [0]]; // Initial measurement matrix
64
+
65
+ this.kalman = new util_regression.KalmanFilter(F, H, Q, R, P_initial, x_initial);
66
+ }
67
+
68
+ /**
69
+ * Kalman Filter constructor
70
+ * Kalman filters work by reducing the amount of noise in a models.
71
+ * https://blog.cordiner.net/2011/05/03/object-tracking-using-a-kalman-filter-matlab/
72
+ *
73
+ * @param {Array.<Array.<Number>>} F - transition matrix
74
+ * @param {Array.<Array.<Number>>} Q - process noise matrix
75
+ * @param {Array.<Array.<Number>>} H - maps between measurement vector and noise matrix
76
+ * @param {Array.<Array.<Number>>} R - defines measurement error of the device
77
+ * @param {Array} P_initial - the initial state
78
+ * @param {Array} X_initial - the initial state of the device
79
+ */
80
+ util_regression.KalmanFilter = function(F, H, Q, R, P_initial, X_initial) {
81
+ this.F = F; // State transition matrix
82
+ this.Q = Q; // Process noise matrix
83
+ this.H = H; // Transformation matrix
84
+ this.R = R; // Measurement Noise
85
+ this.P = P_initial; //Initial covariance matrix
86
+ this.X = X_initial; //Initial guess of measurement
87
+ };
88
+
89
+ /**
90
+ * Get Kalman next filtered value and update the internal state
91
+ * @param {Array} z - the new measurement
92
+ * @return {Array}
93
+ */
94
+ util_regression.KalmanFilter.prototype.update = function(z) {
95
+ // Here, we define all the different matrix operations we will need
96
+ var add = numeric.add, sub = numeric.sub, inv = numeric.inv, identity = numeric.identity;
97
+ var mult = mat.mult, transpose = mat.transpose;
98
+ //TODO cache variables like the transpose of H
99
+
100
+ // prediction: X = F * X | P = F * P * F' + Q
101
+ var X_p = mult(this.F, this.X); //Update state vector
102
+ var P_p = add(mult(mult(this.F,this.P), transpose(this.F)), this.Q); //Predicted covaraince
103
+
104
+ //Calculate the update values
105
+ var y = sub(z, mult(this.H, X_p)); // This is the measurement error (between what we expect and the actual value)
106
+ var S = add(mult(mult(this.H, P_p), transpose(this.H)), this.R); //This is the residual covariance (the error in the covariance)
107
+
108
+ // kalman multiplier: K = P * H' * (H * P * H' + R)^-1
109
+ var K = mult(P_p, mult(transpose(this.H), inv(S))); //This is the Optimal Kalman Gain
110
+
111
+ //We need to change Y into it's column vector form
112
+ for(var i = 0; i < y.length; i++){
113
+ y[i] = [y[i]];
114
+ }
115
+
116
+ //Now we correct the internal values of the model
117
+ // correction: X = X + K * (m - H * X) | P = (I - K * H) * P
118
+ this.X = add(X_p, mult(K, y));
119
+ this.P = mult(sub(identity(K.length), mult(K,this.H)), P_p);
120
+ return transpose(mult(this.H, this.X))[0]; //Transforms the predicted state back into it's measurement form
121
+ };
122
+
123
+ /**
124
+ * Performs ridge regression, according to the Weka code.
125
+ * @param {Array} y - corresponds to screen coordinates (either x or y) for each of n click events
126
+ * @param {Array.<Array.<Number>>} X - corresponds to gray pixel features (120 pixels for both eyes) for each of n clicks
127
+ * @param {Array} k - ridge parameter
128
+ * @return{Array} regression coefficients
129
+ */
130
+ util_regression.ridge = function(y, X, k){
131
+ var nc = X[0].length;
132
+ var m_Coefficients = new Array(nc);
133
+ var xt = mat.transpose(X);
134
+ var solution = new Array();
135
+ var success = true;
136
+ do{
137
+ var ss = mat.mult(xt,X);
138
+ // Set ridge regression adjustment
139
+ for (var i = 0; i < nc; i++) {
140
+ ss[i][i] = ss[i][i] + k;
141
+ }
142
+
143
+ // Carry out the regression
144
+ var bb = mat.mult(xt,y);
145
+ for(var i = 0; i < nc; i++) {
146
+ m_Coefficients[i] = bb[i][0];
147
+ }
148
+ try{
149
+ var n = (m_Coefficients.length !== 0 ? m_Coefficients.length/m_Coefficients.length: 0);
150
+ if (m_Coefficients.length*n !== m_Coefficients.length){
151
+ console.log('Array length must be a multiple of m')
152
+ }
153
+ solution = (ss.length === ss[0].length ? (numeric.LUsolve(numeric.LU(ss,true),bb)) : (webgazer.mat.QRDecomposition(ss,bb)));
154
+
155
+ for (var i = 0; i < nc; i++){
156
+ m_Coefficients[i] = solution[i];
157
+ }
158
+ success = true;
159
+ }
160
+ catch (ex){
161
+ k *= 10;
162
+ console.log(ex);
163
+ success = false;
164
+ }
165
+ } while (!success);
166
+ return m_Coefficients;
167
+ }
168
+
169
+ /**
170
+ * Add given data to current data set then,
171
+ * replace current data member with given data
172
+ * @param {Array.<Object>} data - The data to set
173
+ */
174
+ util_regression.setData = function(data) {
175
+ for (var i = 0; i < data.length; i++) {
176
+ // Clone data array
177
+ var leftData = new Uint8ClampedArray(data[i].eyes.left.patch.data);
178
+ var rightData = new Uint8ClampedArray(data[i].eyes.right.patch.data);
179
+ // Duplicate ImageData object
180
+ data[i].eyes.left.patch = new ImageData(leftData, data[i].eyes.left.width, data[i].eyes.left.height);
181
+ data[i].eyes.right.patch = new ImageData(rightData, data[i].eyes.right.width, data[i].eyes.right.height);
182
+
183
+ // Add those data objects to model
184
+ this.addData(data[i].eyes, data[i].screenPos, data[i].type);
185
+ }
186
+ };
187
+
188
+
189
+ //not used ?!
190
+ //TODO: still usefull ???
191
+ /**
192
+ *
193
+ * @returns {Number}
194
+ */
195
+ util_regression.getCurrentFixationIndex = function() {
196
+ var index = 0;
197
+ var recentX = this.screenXTrailArray.get(0);
198
+ var recentY = this.screenYTrailArray.get(0);
199
+ for (var i = this.screenXTrailArray.length - 1; i >= 0; i--) {
200
+ var currX = this.screenXTrailArray.get(i);
201
+ var currY = this.screenYTrailArray.get(i);
202
+ var euclideanDistance = Math.sqrt(Math.pow((currX-recentX),2)+Math.pow((currY-recentY),2));
203
+ if (euclideanDistance > 72){
204
+ return i+1;
205
+ }
206
+ }
207
+ return i;
208
+ }
209
+
210
+ util_regression.addData = function(eyes, screenPos, type) {
211
+ if (!eyes) {
212
+ return;
213
+ }
214
+ //not doing anything with blink at present
215
+ // if (eyes.left.blink || eyes.right.blink) {
216
+ // return;
217
+ // }
218
+ if (type === 'click') {
219
+ this.screenXClicksArray.push([screenPos[0]]);
220
+ this.screenYClicksArray.push([screenPos[1]]);
221
+ this.eyeFeaturesClicks.push(util.getEyeFeats(eyes));
222
+ this.dataClicks.push({'eyes':eyes, 'screenPos':screenPos, 'type':type});
223
+ } else if (type === 'move') {
224
+ this.screenXTrailArray.push([screenPos[0]]);
225
+ this.screenYTrailArray.push([screenPos[1]]);
226
+
227
+ this.eyeFeaturesTrail.push(util.getEyeFeats(eyes));
228
+ this.trailTimes.push(performance.now());
229
+ this.dataTrail.push({'eyes':eyes, 'screenPos':screenPos, 'type':type});
230
+ }
231
+
232
+ // [20180730 JT] Why do we do this? It doesn't return anything...
233
+ // But as JS is pass by reference, it still affects it.
234
+ //
235
+ // Causes problems for when we want to call 'addData' twice in a row on the same object, but perhaps with different screenPos or types (think multiple interactions within one video frame)
236
+ //eyes.left.patch = Array.from(eyes.left.patch.data);
237
+ //eyes.right.patch = Array.from(eyes.right.patch.data);
238
+ };
239
+
240
+ export default util_regression;
@@ -0,0 +1,306 @@
1
+ (function() {
2
+ 'use strict';
3
+
4
+ self.webgazer = self.webgazer || {};
5
+ self.webgazer.mat = self.webgazer.mat || {};
6
+
7
+ /**
8
+ * Transposes an mxn array
9
+ * @param {Array.<Array.<Number>>} matrix - of 'M x N' dimensionality
10
+ * @return {Array.<Array.<Number>>} transposed matrix
11
+ */
12
+ self.webgazer.mat.transpose = function(matrix){
13
+ var m = matrix.length;
14
+ var n = matrix[0].length;
15
+ var transposedMatrix = new Array(n);
16
+
17
+ for (var i = 0; i < m; i++){
18
+ for (var j = 0; j < n; j++){
19
+ if (i === 0) transposedMatrix[j] = new Array(m);
20
+ transposedMatrix[j][i] = matrix[i][j];
21
+ }
22
+ }
23
+
24
+ return transposedMatrix;
25
+ };
26
+
27
+ /**
28
+ * Get a sub-matrix of matrix
29
+ * @param {Array.<Array.<Number>>} matrix - original matrix
30
+ * @param {Array.<Number>} r - Array of row indices
31
+ * @param {Number} j0 - Initial column index
32
+ * @param {Number} j1 - Final column index
33
+ * @returns {Array} The sub-matrix matrix(r(:),j0:j1)
34
+ */
35
+ self.webgazer.mat.getMatrix = function(matrix, r, j0, j1){
36
+ var X = new Array(r.length),
37
+ m = j1-j0+1;
38
+
39
+ for (var i = 0; i < r.length; i++){
40
+ X[i] = new Array(m);
41
+ for (var j = j0; j <= j1; j++){
42
+ X[i][j-j0] = matrix[r[i]][j];
43
+ }
44
+ }
45
+ return X;
46
+ };
47
+
48
+ /**
49
+ * Get a submatrix of matrix
50
+ * @param {Array.<Array.<Number>>} matrix - original matrix
51
+ * @param {Number} i0 - Initial row index
52
+ * @param {Number} i1 - Final row index
53
+ * @param {Number} j0 - Initial column index
54
+ * @param {Number} j1 - Final column index
55
+ * @return {Array} The sub-matrix matrix(i0:i1,j0:j1)
56
+ */
57
+ self.webgazer.mat.getSubMatrix = function(matrix, i0, i1, j0, j1){
58
+ var size = j1 - j0 + 1,
59
+ X = new Array(i1-i0+1);
60
+
61
+ for (var i = i0; i <= i1; i++){
62
+ var subI = i-i0;
63
+
64
+ X[subI] = new Array(size);
65
+
66
+ for (var j = j0; j <= j1; j++){
67
+ X[subI][j-j0] = matrix[i][j];
68
+ }
69
+ }
70
+ return X;
71
+ };
72
+
73
+ /**
74
+ * Linear algebraic matrix multiplication, matrix1 * matrix2
75
+ * @param {Array.<Array.<Number>>} matrix1
76
+ * @param {Array.<Array.<Number>>} matrix2
77
+ * @return {Array.<Array.<Number>>} Matrix product, matrix1 * matrix2
78
+ */
79
+ self.webgazer.mat.mult = function(matrix1, matrix2){
80
+
81
+ if (matrix2.length != matrix1[0].length){
82
+ console.log('Matrix inner dimensions must agree:');
83
+
84
+ }
85
+
86
+ var X = new Array(matrix1.length),
87
+ Bcolj = new Array(matrix1[0].length);
88
+
89
+ for (var j = 0; j < matrix2[0].length; j++){
90
+ for (var k = 0; k < matrix1[0].length; k++){
91
+ Bcolj[k] = matrix2[k][j];
92
+ }
93
+ for (var i = 0; i < matrix1.length; i++){
94
+
95
+ if (j === 0)
96
+ X[i] = new Array(matrix2[0].length);
97
+
98
+ var Arowi = matrix1[i];
99
+ var s = 0;
100
+ for (var k = 0; k < matrix1[0].length; k++){
101
+ s += Arowi[k]*Bcolj[k];
102
+ }
103
+ X[i][j] = s;
104
+ }
105
+ }
106
+ return X;
107
+ };
108
+
109
+
110
+ /**
111
+ * LUDecomposition to solve A*X = B, based on WEKA code
112
+ * @param {Array.<Array.<Number>>} A - left matrix of equation to be solved
113
+ * @param {Array.<Array.<Number>>} B - right matrix of equation to be solved
114
+ * @return {Array.<Array.<Number>>} X so that L*U*X = B(piv,:)
115
+ */
116
+ self.webgazer.mat.LUDecomposition = function(A,B){
117
+ var LU = new Array(A.length);
118
+
119
+ for (var i = 0; i < A.length; i++){
120
+ LU[i] = new Array(A[0].length);
121
+ for (var j = 0; j < A[0].length; j++){
122
+ LU[i][j] = A[i][j];
123
+ }
124
+ }
125
+
126
+ var m = A.length;
127
+ var n = A[0].length;
128
+ var piv = new Array(m);
129
+ for (var i = 0; i < m; i++){
130
+ piv[i] = i;
131
+ }
132
+ var pivsign = 1;
133
+ var LUrowi = new Array();
134
+ var LUcolj = new Array(m);
135
+ // Outer loop.
136
+ for (var j = 0; j < n; j++){
137
+ // Make a copy of the j-th column to localize references.
138
+ for (var i = 0; i < m; i++){
139
+ LUcolj[i] = LU[i][j];
140
+ }
141
+ // Apply previous transformations.
142
+ for (var i = 0; i < m; i++){
143
+ LUrowi = LU[i];
144
+ // Most of the time is spent in the following dot product.
145
+ var kmax = Math.min(i,j);
146
+ var s = 0;
147
+ for (var k = 0; k < kmax; k++){
148
+ s += LUrowi[k]*LUcolj[k];
149
+ }
150
+ LUrowi[j] = LUcolj[i] -= s;
151
+ }
152
+ // Find pivot and exchange if necessary.
153
+ var p = j;
154
+ for (var i = j+1; i < m; i++){
155
+ if (Math.abs(LUcolj[i]) > Math.abs(LUcolj[p])){
156
+ p = i;
157
+ }
158
+ }
159
+ if (p != j){
160
+ for (var k = 0; k < n; k++){
161
+ var t = LU[p][k];
162
+ LU[p][k] = LU[j][k];
163
+ LU[j][k] = t;
164
+ }
165
+ var k = piv[p];
166
+ piv[p] = piv[j];
167
+ piv[j] = k;
168
+ pivsign = -pivsign;
169
+ }
170
+ // Compute multipliers.
171
+ if (j < m & LU[j][j] != 0){
172
+ for (var i = j+1; i < m; i++){
173
+ LU[i][j] /= LU[j][j];
174
+ }
175
+ }
176
+ }
177
+ if (B.length != m){
178
+ console.log('Matrix row dimensions must agree.');
179
+ }
180
+ for (var j = 0; j < n; j++){
181
+ if (LU[j][j] === 0){
182
+ console.log('Matrix is singular.')
183
+ }
184
+ }
185
+ var nx = B[0].length;
186
+ var X = self.webgazer.mat.getMatrix(B,piv,0,nx-1);
187
+ // Solve L*Y = B(piv,:)
188
+ for (var k = 0; k < n; k++){
189
+ for (var i = k+1; i < n; i++){
190
+ for (var j = 0; j < nx; j++){
191
+ X[i][j] -= X[k][j]*LU[i][k];
192
+ }
193
+ }
194
+ }
195
+ // Solve U*X = Y;
196
+ for (var k = n-1; k >= 0; k--){
197
+ for (var j = 0; j < nx; j++){
198
+ X[k][j] /= LU[k][k];
199
+ }
200
+ for (var i = 0; i < k; i++){
201
+ for (var j = 0; j < nx; j++){
202
+ X[i][j] -= X[k][j]*LU[i][k];
203
+ }
204
+ }
205
+ }
206
+ return X;
207
+ };
208
+
209
+ /**
210
+ * Least squares solution of A*X = B, based on WEKA code
211
+ * @param {Array.<Array.<Number>>} A - left side matrix to be solved
212
+ * @param {Array.<Array.<Number>>} B - a matrix with as many rows as A and any number of columns.
213
+ * @return {Array.<Array.<Number>>} X - that minimizes the two norms of QR*X-B.
214
+ */
215
+ self.webgazer.mat.QRDecomposition = function(A, B){
216
+ // Initialize.
217
+ var QR = new Array(A.length);
218
+
219
+ for (var i = 0; i < A.length; i++){
220
+ QR[i] = new Array(A[0].length);
221
+ for (var j = 0; j < A[0].length; j++){
222
+ QR[i][j] = A[i][j];
223
+ }
224
+ }
225
+ var m = A.length;
226
+ var n = A[0].length;
227
+ var Rdiag = new Array(n);
228
+ var nrm;
229
+
230
+ // Main loop.
231
+ for (var k = 0; k < n; k++){
232
+ // Compute 2-norm of k-th column without under/overflow.
233
+ nrm = 0;
234
+ for (var i = k; i < m; i++){
235
+ nrm = Math.hypot(nrm,QR[i][k]);
236
+ }
237
+ if (nrm != 0){
238
+ // Form k-th Householder vector.
239
+ if (QR[k][k] < 0){
240
+ nrm = -nrm;
241
+ }
242
+ for (var i = k; i < m; i++){
243
+ QR[i][k] /= nrm;
244
+ }
245
+ QR[k][k] += 1;
246
+
247
+ // Apply transformation to remaining columns.
248
+ for (var j = k+1; j < n; j++){
249
+ var s = 0;
250
+ for (var i = k; i < m; i++){
251
+ s += QR[i][k]*QR[i][j];
252
+ }
253
+ s = -s/QR[k][k];
254
+ for (var i = k; i < m; i++){
255
+ QR[i][j] += s*QR[i][k];
256
+ }
257
+ }
258
+ }
259
+ Rdiag[k] = -nrm;
260
+ }
261
+ if (B.length != m){
262
+ console.log('Matrix row dimensions must agree.');
263
+ }
264
+ for (var j = 0; j < n; j++){
265
+ if (Rdiag[j] === 0)
266
+ console.log('Matrix is rank deficient');
267
+ }
268
+ // Copy right hand side
269
+ var nx = B[0].length;
270
+ var X = new Array(B.length);
271
+ for(var i=0; i<B.length; i++){
272
+ X[i] = new Array(B[0].length);
273
+ }
274
+ for (var i = 0; i < B.length; i++){
275
+ for (var j = 0; j < B[0].length; j++){
276
+ X[i][j] = B[i][j];
277
+ }
278
+ }
279
+ // Compute Y = transpose(Q)*B
280
+ for (var k = 0; k < n; k++){
281
+ for (var j = 0; j < nx; j++){
282
+ var s = 0.0;
283
+ for (var i = k; i < m; i++){
284
+ s += QR[i][k]*X[i][j];
285
+ }
286
+ s = -s/QR[k][k];
287
+ for (var i = k; i < m; i++){
288
+ X[i][j] += s*QR[i][k];
289
+ }
290
+ }
291
+ }
292
+ // Solve R*X = Y;
293
+ for (var k = n-1; k >= 0; k--){
294
+ for (var j = 0; j < nx; j++){
295
+ X[k][j] /= Rdiag[k];
296
+ }
297
+ for (var i = 0; i < k; i++){
298
+ for (var j = 0; j < nx; j++){
299
+ X[i][j] -= X[k][j]*QR[i][k];
300
+ }
301
+ }
302
+ }
303
+ return self.webgazer.mat.getSubMatrix(X,0,n-1,0,nx-1);
304
+ }
305
+
306
+ }());