remote-calibrator 0.2.1 → 0.2.2-beta.0
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.
- package/CHANGELOG.md +16 -0
- package/README.md +2 -0
- package/homepage/example.js +11 -13
- 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/package.json +8 -8
- package/src/WebGazer4RC/.gitattributes +10 -0
- package/src/WebGazer4RC/LICENSE.md +15 -0
- package/src/WebGazer4RC/README.md +142 -0
- package/src/WebGazer4RC/gnu-lgpl-v3.0.md +163 -0
- package/src/WebGazer4RC/gplv3.md +636 -0
- package/src/WebGazer4RC/package-lock.json +1133 -0
- package/src/WebGazer4RC/package.json +28 -0
- package/src/WebGazer4RC/src/dom_util.mjs +27 -0
- package/src/WebGazer4RC/src/facemesh.mjs +150 -0
- package/src/WebGazer4RC/src/index.mjs +1213 -0
- package/src/WebGazer4RC/src/mat.mjs +301 -0
- package/src/WebGazer4RC/src/params.mjs +29 -0
- package/src/WebGazer4RC/src/pupil.mjs +109 -0
- package/src/WebGazer4RC/src/ridgeReg.mjs +104 -0
- package/src/WebGazer4RC/src/ridgeRegThreaded.mjs +161 -0
- package/src/WebGazer4RC/src/ridgeWeightedReg.mjs +125 -0
- package/src/WebGazer4RC/src/ridgeWorker.mjs +135 -0
- package/src/WebGazer4RC/src/util.mjs +348 -0
- package/src/WebGazer4RC/src/util_regression.mjs +240 -0
- package/src/WebGazer4RC/src/worker_scripts/mat.js +306 -0
- package/src/WebGazer4RC/src/worker_scripts/util.js +398 -0
- package/src/WebGazer4RC/test/regression_test.js +182 -0
- package/src/WebGazer4RC/test/run_tests_and_server.sh +24 -0
- package/src/WebGazer4RC/test/util_test.js +60 -0
- package/src/WebGazer4RC/test/webgazerExtract_test.js +40 -0
- package/src/WebGazer4RC/test/webgazer_test.js +160 -0
- package/src/WebGazer4RC/test/www_page_test.js +41 -0
- package/src/components/onCanvas.js +1 -2
- package/src/core.js +18 -0
- package/src/css/distance.scss +1 -0
- package/src/distance/distance.js +4 -1
- package/src/distance/distanceTrack.js +2 -2
- package/src/panel/panel.js +14 -5
- 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
|
+
}());
|