remote-calibrator 0.2.2-beta.2 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +4 -1
- package/lib/RemoteCalibrator.min.js +1 -1
- package/lib/RemoteCalibrator.min.js.LICENSE.txt +1 -1
- package/lib/RemoteCalibrator.min.js.map +1 -1
- package/media/measureDistance.png +0 -0
- package/media/panel.png +0 -0
- package/media/screenSize.png +0 -0
- package/media/trackGaze.png +0 -0
- package/package.json +4 -4
- package/src/components/sound.js +2 -2
- package/src/css/main.css +7 -6
- package/src/screenSize.js +1 -1
- package/src/WebGazer4RC/.gitattributes +0 -10
- package/src/WebGazer4RC/LICENSE.md +0 -15
- package/src/WebGazer4RC/README.md +0 -142
- package/src/WebGazer4RC/gnu-lgpl-v3.0.md +0 -163
- package/src/WebGazer4RC/gplv3.md +0 -636
- package/src/WebGazer4RC/package-lock.json +0 -1133
- package/src/WebGazer4RC/package.json +0 -28
- package/src/WebGazer4RC/src/dom_util.mjs +0 -27
- package/src/WebGazer4RC/src/facemesh.mjs +0 -150
- package/src/WebGazer4RC/src/index.mjs +0 -1213
- package/src/WebGazer4RC/src/mat.mjs +0 -301
- package/src/WebGazer4RC/src/params.mjs +0 -29
- package/src/WebGazer4RC/src/pupil.mjs +0 -109
- package/src/WebGazer4RC/src/ridgeReg.mjs +0 -104
- package/src/WebGazer4RC/src/ridgeRegThreaded.mjs +0 -161
- package/src/WebGazer4RC/src/ridgeWeightedReg.mjs +0 -125
- package/src/WebGazer4RC/src/ridgeWorker.mjs +0 -135
- package/src/WebGazer4RC/src/util.mjs +0 -348
- package/src/WebGazer4RC/src/util_regression.mjs +0 -240
- package/src/WebGazer4RC/src/worker_scripts/mat.js +0 -306
- package/src/WebGazer4RC/src/worker_scripts/util.js +0 -398
- package/src/WebGazer4RC/test/regression_test.js +0 -182
- package/src/WebGazer4RC/test/run_tests_and_server.sh +0 -24
- package/src/WebGazer4RC/test/util_test.js +0 -60
- package/src/WebGazer4RC/test/webgazerExtract_test.js +0 -40
- package/src/WebGazer4RC/test/webgazer_test.js +0 -160
- package/src/WebGazer4RC/test/www_page_test.js +0 -41
@@ -1,240 +0,0 @@
|
|
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;
|
@@ -1,306 +0,0 @@
|
|
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
|
-
}());
|