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.
- package/CHANGELOG.md +12 -0
- package/README.md +29 -19
- package/homepage/example.js +9 -3
- package/i18n/fetch-languages-sheets.js +5 -4
- 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 +15 -15
- 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 +1235 -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/const.js +3 -0
- package/src/core.js +8 -0
- package/src/css/distance.scss +40 -0
- package/src/css/panel.scss +32 -1
- package/src/distance/distance.js +4 -4
- package/src/distance/distanceCheck.js +115 -0
- package/src/distance/distanceTrack.js +99 -41
- package/src/{interpupillaryDistance.js → distance/interPupillaryDistance.js} +14 -12
- package/src/gaze/gazeTracker.js +16 -1
- package/src/i18n.js +1 -1
- package/src/index.js +2 -1
- package/src/panel.js +32 -3
- package/webpack.config.js +4 -4
@@ -0,0 +1,125 @@
|
|
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 RidgeWeightedReg object
|
9
|
+
* @constructor
|
10
|
+
*/
|
11
|
+
reg.RidgeWeightedReg = function() {
|
12
|
+
this.init();
|
13
|
+
};
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Initialize new arrays and initialize Kalman filter.
|
17
|
+
*/
|
18
|
+
reg.RidgeWeightedReg.prototype.init = util_regression.InitRegression
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Add given data from eyes
|
22
|
+
* @param {Object} eyes - eyes where extract data to add
|
23
|
+
* @param {Object} screenPos - The current screen point
|
24
|
+
* @param {Object} type - The type of performed action
|
25
|
+
*/
|
26
|
+
reg.RidgeWeightedReg.prototype.addData = util_regression.addData
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Try to predict coordinates from pupil data
|
30
|
+
* after apply linear regression on data set
|
31
|
+
* @param {Object} eyesObj - The current user eyes object
|
32
|
+
* @returns {Object}
|
33
|
+
*/
|
34
|
+
reg.RidgeWeightedReg.prototype.predict = function(eyesObj) {
|
35
|
+
if (!eyesObj || this.eyeFeaturesClicks.length === 0) {
|
36
|
+
return null;
|
37
|
+
}
|
38
|
+
var acceptTime = performance.now() - this.trailTime;
|
39
|
+
var trailX = [];
|
40
|
+
var trailY = [];
|
41
|
+
var trailFeat = [];
|
42
|
+
for (var i = 0; i < this.trailDataWindow; i++) {
|
43
|
+
if (this.trailTimes.get(i) > acceptTime) {
|
44
|
+
trailX.push(this.screenXTrailArray.get(i));
|
45
|
+
trailY.push(this.screenYTrailArray.get(i));
|
46
|
+
trailFeat.push(this.eyeFeaturesTrail.get(i));
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
var len = this.eyeFeaturesClicks.data.length;
|
51
|
+
var weightedEyeFeats = Array(len);
|
52
|
+
var weightedXArray = Array(len);
|
53
|
+
var weightedYArray = Array(len);
|
54
|
+
for (var i = 0; i < len; i++) {
|
55
|
+
var weight = Math.sqrt( 1 / (len - i) ); // access from oldest to newest so should start with low weight and increase steadily
|
56
|
+
//abstraction is leaking...
|
57
|
+
var trueIndex = this.eyeFeaturesClicks.getTrueIndex(i);
|
58
|
+
for (var j = 0; j < this.eyeFeaturesClicks.data[trueIndex].length; j++) {
|
59
|
+
var val = this.eyeFeaturesClicks.data[trueIndex][j] * weight;
|
60
|
+
if (weightedEyeFeats[trueIndex] !== undefined){
|
61
|
+
weightedEyeFeats[trueIndex].push(val);
|
62
|
+
} else {
|
63
|
+
weightedEyeFeats[trueIndex] = [val];
|
64
|
+
}
|
65
|
+
}
|
66
|
+
weightedXArray[trueIndex] = this.screenXClicksArray.get(i).slice(0, this.screenXClicksArray.get(i).length);
|
67
|
+
weightedYArray[trueIndex] = this.screenYClicksArray.get(i).slice(0, this.screenYClicksArray.get(i).length);
|
68
|
+
weightedXArray[i][0] = weightedXArray[i][0] * weight;
|
69
|
+
weightedYArray[i][0] = weightedYArray[i][0] * weight;
|
70
|
+
}
|
71
|
+
|
72
|
+
var screenXArray = weightedXArray.concat(trailX);
|
73
|
+
var screenYArray = weightedYArray.concat(trailY);
|
74
|
+
var eyeFeatures = weightedEyeFeats.concat(trailFeat);
|
75
|
+
|
76
|
+
var coefficientsX = util_regression.ridge(screenXArray, eyeFeatures, this.ridgeParameter);
|
77
|
+
var coefficientsY = util_regression.ridge(screenYArray, eyeFeatures, this.ridgeParameter);
|
78
|
+
|
79
|
+
var eyeFeats = util.getEyeFeats(eyesObj);
|
80
|
+
var predictedX = 0;
|
81
|
+
for(var i=0; i< eyeFeats.length; i++){
|
82
|
+
predictedX += eyeFeats[i] * coefficientsX[i];
|
83
|
+
}
|
84
|
+
var predictedY = 0;
|
85
|
+
for(var i=0; i< eyeFeats.length; i++){
|
86
|
+
predictedY += eyeFeats[i] * coefficientsY[i];
|
87
|
+
}
|
88
|
+
|
89
|
+
predictedX = Math.floor(predictedX);
|
90
|
+
predictedY = Math.floor(predictedY);
|
91
|
+
|
92
|
+
if (params.applyKalmanFilter) {
|
93
|
+
// Update Kalman model, and get prediction
|
94
|
+
var newGaze = [predictedX, predictedY]; // [20200607 xk] Should we use a 1x4 vector?
|
95
|
+
newGaze = this.kalman.update(newGaze);
|
96
|
+
|
97
|
+
return {
|
98
|
+
x: newGaze[0],
|
99
|
+
y: newGaze[1]
|
100
|
+
};
|
101
|
+
} else {
|
102
|
+
return {
|
103
|
+
x: predictedX,
|
104
|
+
y: predictedY
|
105
|
+
};
|
106
|
+
}
|
107
|
+
};
|
108
|
+
|
109
|
+
reg.RidgeWeightedReg.prototype.setData = util_regression.setData;
|
110
|
+
|
111
|
+
/**
|
112
|
+
* Return the data
|
113
|
+
* @returns {Array.<Object>|*}
|
114
|
+
*/
|
115
|
+
reg.RidgeWeightedReg.prototype.getData = function() {
|
116
|
+
return this.dataClicks.data;
|
117
|
+
};
|
118
|
+
|
119
|
+
/**
|
120
|
+
* The RidgeWeightedReg object name
|
121
|
+
* @type {string}
|
122
|
+
*/
|
123
|
+
reg.RidgeWeightedReg.prototype.name = 'ridgeWeighted';
|
124
|
+
|
125
|
+
export default reg;
|
@@ -0,0 +1,135 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
console.log('thread starting');
|
4
|
+
|
5
|
+
// Add src/util.mjs and src/mat.mjs to the same directory as your html file
|
6
|
+
importScripts('./worker_scripts/util.js', './worker_scripts/mat.js'); // [20200708] Figure out how to make all of this wrap up neatly
|
7
|
+
var ridgeParameter = Math.pow(10,-5);
|
8
|
+
var resizeWidth = 10;
|
9
|
+
var resizeHeight = 6;
|
10
|
+
var dataWindow = 700;
|
11
|
+
var trailDataWindow = 10;
|
12
|
+
var trainInterval = 500;
|
13
|
+
|
14
|
+
var screenXClicksArray = new self.webgazer.util.DataWindow(dataWindow);
|
15
|
+
var screenYClicksArray = new self.webgazer.util.DataWindow(dataWindow);
|
16
|
+
var eyeFeaturesClicks = new self.webgazer.util.DataWindow(dataWindow);
|
17
|
+
var dataClicks = new self.webgazer.util.DataWindow(dataWindow);
|
18
|
+
|
19
|
+
var screenXTrailArray = new self.webgazer.util.DataWindow(trailDataWindow);
|
20
|
+
var screenYTrailArray = new self.webgazer.util.DataWindow(trailDataWindow);
|
21
|
+
var eyeFeaturesTrail = new self.webgazer.util.DataWindow(trailDataWindow);
|
22
|
+
var dataTrail = new self.webgazer.util.DataWindow(trailDataWindow);
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Performs ridge regression, according to the Weka code.
|
26
|
+
* @param {Array} y - corresponds to screen coordinates (either x or y) for each of n click events
|
27
|
+
* @param {Array.<Array.<Number>>} X - corresponds to gray pixel features (120 pixels for both eyes) for each of n clicks
|
28
|
+
* @param {Array} k - ridge parameter
|
29
|
+
* @return{Array} regression coefficients
|
30
|
+
*/
|
31
|
+
function ridge(y, X, k){
|
32
|
+
var nc = X[0].length;
|
33
|
+
var m_Coefficients = new Array(nc);
|
34
|
+
var xt = self.webgazer.mat.transpose(X);
|
35
|
+
var solution = new Array();
|
36
|
+
var success = true;
|
37
|
+
do{
|
38
|
+
var ss = self.webgazer.mat.mult(xt,X);
|
39
|
+
// Set ridge regression adjustment
|
40
|
+
for (var i = 0; i < nc; i++) {
|
41
|
+
ss[i][i] = ss[i][i] + k;
|
42
|
+
}
|
43
|
+
|
44
|
+
// Carry out the regression
|
45
|
+
var bb = self.webgazer.mat.mult(xt,y);
|
46
|
+
for(var i = 0; i < nc; i++) {
|
47
|
+
m_Coefficients[i] = bb[i][0];
|
48
|
+
}
|
49
|
+
try{
|
50
|
+
var n = (m_Coefficients.length !== 0 ? m_Coefficients.length/m_Coefficients.length: 0);
|
51
|
+
if (m_Coefficients.length*n !== m_Coefficients.length){
|
52
|
+
console.log('Array length must be a multiple of m')
|
53
|
+
}
|
54
|
+
solution = (ss.length === ss[0].length ? (self.webgazer.mat.LUDecomposition(ss,bb)) : (self.webgazer.mat.QRDecomposition(ss,bb)));
|
55
|
+
|
56
|
+
for (var i = 0; i < nc; i++){
|
57
|
+
m_Coefficients[i] = solution[i][0];
|
58
|
+
}
|
59
|
+
success = true;
|
60
|
+
}
|
61
|
+
catch (ex){
|
62
|
+
k *= 10;
|
63
|
+
console.log(ex);
|
64
|
+
success = false;
|
65
|
+
}
|
66
|
+
} while (!success);
|
67
|
+
return m_Coefficients;
|
68
|
+
}
|
69
|
+
|
70
|
+
//TODO: still usefull ???
|
71
|
+
/**
|
72
|
+
*
|
73
|
+
* @returns {Number}
|
74
|
+
*/
|
75
|
+
function getCurrentFixationIndex() {
|
76
|
+
var index = 0;
|
77
|
+
var recentX = this.screenXTrailArray.get(0);
|
78
|
+
var recentY = this.screenYTrailArray.get(0);
|
79
|
+
for (var i = this.screenXTrailArray.length - 1; i >= 0; i--) {
|
80
|
+
var currX = this.screenXTrailArray.get(i);
|
81
|
+
var currY = this.screenYTrailArray.get(i);
|
82
|
+
var euclideanDistance = Math.sqrt(Math.pow((currX-recentX),2)+Math.pow((currY-recentY),2));
|
83
|
+
if (euclideanDistance > 72){
|
84
|
+
return i+1;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
return i;
|
88
|
+
}
|
89
|
+
|
90
|
+
/**
|
91
|
+
* Event handler, it store screen position to allow training
|
92
|
+
* @param {Event} event - the receive event
|
93
|
+
*/
|
94
|
+
self.onmessage = function(event) {
|
95
|
+
var data = event.data;
|
96
|
+
var screenPos = data['screenPos'];
|
97
|
+
var eyes = data['eyes'];
|
98
|
+
var type = data['type'];
|
99
|
+
if (type === 'click') {
|
100
|
+
self.screenXClicksArray.push([screenPos[0]]);
|
101
|
+
self.screenYClicksArray.push([screenPos[1]]);
|
102
|
+
|
103
|
+
self.eyeFeaturesClicks.push(eyes);
|
104
|
+
} else if (type === 'move') {
|
105
|
+
self.screenXTrailArray.push([screenPos[0]]);
|
106
|
+
self.screenYTrailArray.push([screenPos[1]]);
|
107
|
+
|
108
|
+
self.eyeFeaturesTrail.push(eyes);
|
109
|
+
self.dataTrail.push({'eyes':eyes, 'screenPos':screenPos, 'type':type});
|
110
|
+
}
|
111
|
+
self.needsTraining = true;
|
112
|
+
};
|
113
|
+
|
114
|
+
/**
|
115
|
+
* Compute coefficient from training data
|
116
|
+
*/
|
117
|
+
function retrain() {
|
118
|
+
if (self.screenXClicksArray.length === 0) {
|
119
|
+
return;
|
120
|
+
}
|
121
|
+
if (!self.needsTraining) {
|
122
|
+
return;
|
123
|
+
}
|
124
|
+
var screenXArray = self.screenXClicksArray.data.concat(self.screenXTrailArray.data);
|
125
|
+
var screenYArray = self.screenYClicksArray.data.concat(self.screenYTrailArray.data);
|
126
|
+
var eyeFeatures = self.eyeFeaturesClicks.data.concat(self.eyeFeaturesTrail.data);
|
127
|
+
|
128
|
+
var coefficientsX = ridge(screenXArray, eyeFeatures, ridgeParameter);
|
129
|
+
var coefficientsY = ridge(screenYArray, eyeFeatures, ridgeParameter);
|
130
|
+
self.postMessage({'X':coefficientsX, 'Y': coefficientsY});
|
131
|
+
self.needsTraining = false;
|
132
|
+
}
|
133
|
+
|
134
|
+
setInterval(retrain, trainInterval);
|
135
|
+
|
@@ -0,0 +1,348 @@
|
|
1
|
+
const util = {};
|
2
|
+
|
3
|
+
var resizeWidth = 10;
|
4
|
+
var resizeHeight = 6;
|
5
|
+
|
6
|
+
//not used !?
|
7
|
+
/**
|
8
|
+
* Eye class, represents an eye patch detected in the video stream
|
9
|
+
* @param {ImageData} patch - the image data corresponding to an eye
|
10
|
+
* @param {Number} imagex - x-axis offset from the top-left corner of the video canvas
|
11
|
+
* @param {Number} imagey - y-axis offset from the top-left corner of the video canvas
|
12
|
+
* @param {Number} width - width of the eye patch
|
13
|
+
* @param {Number} height - height of the eye patch
|
14
|
+
*/
|
15
|
+
util.Eye = function(patch, imagex, imagey, width, height) {
|
16
|
+
this.patch = patch;
|
17
|
+
this.imagex = imagex;
|
18
|
+
this.imagey = imagey;
|
19
|
+
this.width = width;
|
20
|
+
this.height = height;
|
21
|
+
};
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Compute eyes size as gray histogram
|
25
|
+
* @param {Object} eyes - The eyes where looking for gray histogram
|
26
|
+
* @returns {Array.<T>} The eyes gray level histogram
|
27
|
+
*/
|
28
|
+
util.getEyeFeats = function(eyes) {
|
29
|
+
var resizedLeft = this.resizeEye(eyes.left, resizeWidth, resizeHeight);
|
30
|
+
var resizedRight = this.resizeEye(eyes.right, resizeWidth, resizeHeight);
|
31
|
+
|
32
|
+
var leftGray = this.grayscale(resizedLeft.data, resizedLeft.width, resizedLeft.height);
|
33
|
+
var rightGray = this.grayscale(resizedRight.data, resizedRight.width, resizedRight.height);
|
34
|
+
|
35
|
+
var histLeft = [];
|
36
|
+
this.equalizeHistogram(leftGray, 5, histLeft);
|
37
|
+
var histRight = [];
|
38
|
+
this.equalizeHistogram(rightGray, 5, histRight);
|
39
|
+
|
40
|
+
// var leftGrayArray = Array.prototype.slice.call(histLeft);
|
41
|
+
// var rightGrayArray = Array.prototype.slice.call(histRight);
|
42
|
+
|
43
|
+
return histLeft.concat(histRight);
|
44
|
+
}
|
45
|
+
//Data Window class
|
46
|
+
//operates like an array but 'wraps' data around to keep the array at a fixed windowSize
|
47
|
+
/**
|
48
|
+
* DataWindow class - Operates like an array, but 'wraps' data around to keep the array at a fixed windowSize
|
49
|
+
* @param {Number} windowSize - defines the maximum size of the window
|
50
|
+
* @param {Array} data - optional data to seed the DataWindow with
|
51
|
+
**/
|
52
|
+
util.DataWindow = function(windowSize, data) {
|
53
|
+
this.data = [];
|
54
|
+
this.windowSize = windowSize;
|
55
|
+
this.index = 0;
|
56
|
+
this.length = 0;
|
57
|
+
if(data){
|
58
|
+
this.data = data.slice(data.length-windowSize,data.length);
|
59
|
+
this.length = this.data.length;
|
60
|
+
}
|
61
|
+
};
|
62
|
+
|
63
|
+
/**
|
64
|
+
* [push description]
|
65
|
+
* @param {*} entry - item to be inserted. It either grows the DataWindow or replaces the oldest item
|
66
|
+
* @return {DataWindow} this
|
67
|
+
*/
|
68
|
+
util.DataWindow.prototype.push = function(entry) {
|
69
|
+
if (this.data.length < this.windowSize) {
|
70
|
+
this.data.push(entry);
|
71
|
+
this.length = this.data.length;
|
72
|
+
return this;
|
73
|
+
}
|
74
|
+
|
75
|
+
//replace oldest entry by wrapping around the DataWindow
|
76
|
+
this.data[this.index] = entry;
|
77
|
+
this.index = (this.index + 1) % this.windowSize;
|
78
|
+
return this;
|
79
|
+
};
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Get the element at the ind position by wrapping around the DataWindow
|
83
|
+
* @param {Number} ind index of desired entry
|
84
|
+
* @return {*}
|
85
|
+
*/
|
86
|
+
util.DataWindow.prototype.get = function(ind) {
|
87
|
+
return this.data[this.getTrueIndex(ind)];
|
88
|
+
};
|
89
|
+
|
90
|
+
/**
|
91
|
+
* Gets the true this.data array index given an index for a desired element
|
92
|
+
* @param {Number} ind - index of desired entry
|
93
|
+
* @return {Number} index of desired entry in this.data
|
94
|
+
*/
|
95
|
+
util.DataWindow.prototype.getTrueIndex = function(ind) {
|
96
|
+
if (this.data.length < this.windowSize) {
|
97
|
+
return ind;
|
98
|
+
} else {
|
99
|
+
//wrap around ind so that we can traverse from oldest to newest
|
100
|
+
return (ind + this.index) % this.windowSize;
|
101
|
+
}
|
102
|
+
};
|
103
|
+
|
104
|
+
/**
|
105
|
+
* Append all the contents of data
|
106
|
+
* @param {Array} data - to be inserted
|
107
|
+
*/
|
108
|
+
util.DataWindow.prototype.addAll = function(data) {
|
109
|
+
for (var i = 0; i < data.length; i++) {
|
110
|
+
this.push(data[i]);
|
111
|
+
}
|
112
|
+
};
|
113
|
+
|
114
|
+
|
115
|
+
//Helper functions
|
116
|
+
/**
|
117
|
+
* Grayscales an image patch. Can be used for the whole canvas, detected face, detected eye, etc.
|
118
|
+
*
|
119
|
+
* Code from tracking.js by Eduardo Lundgren, et al.
|
120
|
+
* https://github.com/eduardolundgren/tracking.js/blob/master/src/tracking.js
|
121
|
+
*
|
122
|
+
* Software License Agreement (BSD License) Copyright (c) 2014, Eduardo A. Lundgren Melo. All rights reserved.
|
123
|
+
* Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
124
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
125
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
126
|
+
* The name of Eduardo A. Lundgren Melo may not be used to endorse or promote products derived from this software without specific prior written permission of Eduardo A. Lundgren Melo.
|
127
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
128
|
+
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
129
|
+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
130
|
+
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
131
|
+
*
|
132
|
+
* @param {Array} pixels - image data to be grayscaled
|
133
|
+
* @param {Number} width - width of image data to be grayscaled
|
134
|
+
* @param {Number} height - height of image data to be grayscaled
|
135
|
+
* @return {Array} grayscaledImage
|
136
|
+
*/
|
137
|
+
util.grayscale = function(pixels, width, height){
|
138
|
+
var gray = new Uint8ClampedArray(pixels.length >> 2);
|
139
|
+
var p = 0;
|
140
|
+
var w = 0;
|
141
|
+
for (var i = 0; i < height; i++) {
|
142
|
+
for (var j = 0; j < width; j++) {
|
143
|
+
var value = pixels[w] * 0.299 + pixels[w + 1] * 0.587 + pixels[w + 2] * 0.114;
|
144
|
+
gray[p++] = value;
|
145
|
+
|
146
|
+
w += 4;
|
147
|
+
}
|
148
|
+
}
|
149
|
+
return gray;
|
150
|
+
};
|
151
|
+
|
152
|
+
/**
|
153
|
+
* Increase contrast of an image.
|
154
|
+
*
|
155
|
+
* Code from Martin Tschirsich, Copyright (c) 2012.
|
156
|
+
* https://github.com/mtschirs/js-objectdetect/blob/gh-pages/js/objectdetect.js
|
157
|
+
*
|
158
|
+
* @param {Array} src - grayscale integer array
|
159
|
+
* @param {Number} step - sampling rate, control performance
|
160
|
+
* @param {Array} dst - array to hold the resulting image
|
161
|
+
*/
|
162
|
+
util.equalizeHistogram = function(src, step, dst) {
|
163
|
+
var srcLength = src.length;
|
164
|
+
if (!dst) dst = src;
|
165
|
+
if (!step) step = 5;
|
166
|
+
|
167
|
+
// Compute histogram and histogram sum:
|
168
|
+
var hist = Array(256).fill(0);
|
169
|
+
|
170
|
+
for (var i = 0; i < srcLength; i += step) {
|
171
|
+
++hist[src[i]];
|
172
|
+
}
|
173
|
+
|
174
|
+
// Compute integral histogram:
|
175
|
+
var norm = 255 * step / srcLength,
|
176
|
+
prev = 0;
|
177
|
+
for (var j = 0; j < 256; ++j) {
|
178
|
+
var h = hist[j];
|
179
|
+
prev = h += prev;
|
180
|
+
hist[j] = h * norm; // For non-integer src: ~~(h * norm + 0.5);
|
181
|
+
}
|
182
|
+
|
183
|
+
// Equalize image:
|
184
|
+
for (var k = 0; k < srcLength; ++k) {
|
185
|
+
dst[k] = hist[src[k]];
|
186
|
+
}
|
187
|
+
return dst;
|
188
|
+
};
|
189
|
+
|
190
|
+
//not used !?
|
191
|
+
util.threshold = function(data, threshold) {
|
192
|
+
for (let i = 0; i < data.length; i++) {
|
193
|
+
data[i] = (data[i] > threshold) ? 255 : 0;
|
194
|
+
}
|
195
|
+
return data;
|
196
|
+
};
|
197
|
+
|
198
|
+
//not used !?
|
199
|
+
util.correlation = function(data1, data2) {
|
200
|
+
const length = Math.min(data1.length, data2.length);
|
201
|
+
let count = 0;
|
202
|
+
for (let i = 0; i < length; i++) {
|
203
|
+
if (data1[i] === data2[i]) {
|
204
|
+
count++;
|
205
|
+
}
|
206
|
+
}
|
207
|
+
return count / Math.max(data1.length, data2.length);
|
208
|
+
};
|
209
|
+
|
210
|
+
/**
|
211
|
+
* Gets an Eye object and resizes it to the desired resolution
|
212
|
+
* @param {webgazer.util.Eye} eye - patch to be resized
|
213
|
+
* @param {Number} resizeWidth - desired width
|
214
|
+
* @param {Number} resizeHeight - desired height
|
215
|
+
* @return {webgazer.util.Eye} resized eye patch
|
216
|
+
*/
|
217
|
+
util.resizeEye = function(eye, resizeWidth, resizeHeight) {
|
218
|
+
|
219
|
+
var canvas = document.createElement('canvas');
|
220
|
+
canvas.width = eye.width;
|
221
|
+
canvas.height = eye.height;
|
222
|
+
|
223
|
+
canvas.getContext('2d').putImageData(eye.patch,0,0);
|
224
|
+
|
225
|
+
var tempCanvas = document.createElement('canvas');
|
226
|
+
|
227
|
+
tempCanvas.width = resizeWidth;
|
228
|
+
tempCanvas.height = resizeHeight;
|
229
|
+
|
230
|
+
// save the canvas into temp canvas
|
231
|
+
tempCanvas.getContext('2d').drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, resizeWidth, resizeHeight);
|
232
|
+
|
233
|
+
return tempCanvas.getContext('2d').getImageData(0, 0, resizeWidth, resizeHeight);
|
234
|
+
};
|
235
|
+
|
236
|
+
/**
|
237
|
+
* Checks if the prediction is within the boundaries of the viewport and constrains it
|
238
|
+
* @param {Array} prediction [x,y] - predicted gaze coordinates
|
239
|
+
* @return {Array} constrained coordinates
|
240
|
+
*/
|
241
|
+
util.bound = function(prediction){
|
242
|
+
prediction.x = _constrain(prediction.x, 0, window.innerWidth);
|
243
|
+
prediction.y = _constrain(prediction.y, 0, window.innerHeight);
|
244
|
+
return prediction
|
245
|
+
};
|
246
|
+
|
247
|
+
function _constrain(a, b0, b1) {
|
248
|
+
return (a < b0) ? b0 : (a > b1) ? b1 : a
|
249
|
+
}
|
250
|
+
|
251
|
+
//not used !?
|
252
|
+
/**
|
253
|
+
* Write statistics in debug paragraph panel
|
254
|
+
* @param {HTMLElement} para - The <p> tag where write data
|
255
|
+
* @param {Object} stats - The stats data to output
|
256
|
+
*/
|
257
|
+
function debugBoxWrite(para, stats) {
|
258
|
+
var str = '';
|
259
|
+
for (var key in stats) {
|
260
|
+
str += key + ': ' + stats[key] + '\n';
|
261
|
+
}
|
262
|
+
para.innerText = str;
|
263
|
+
}
|
264
|
+
|
265
|
+
//not used !?
|
266
|
+
/**
|
267
|
+
* Constructor of DebugBox object,
|
268
|
+
* it insert an paragraph inside a div to the body, in view to display debug data
|
269
|
+
* @param {Number} interval - The log interval
|
270
|
+
* @constructor
|
271
|
+
*/
|
272
|
+
util.DebugBox = function(interval) {
|
273
|
+
this.para = document.createElement('p');
|
274
|
+
this.div = document.createElement('div');
|
275
|
+
this.div.appendChild(this.para);
|
276
|
+
document.body.appendChild(this.div);
|
277
|
+
|
278
|
+
this.buttons = {};
|
279
|
+
this.canvas = {};
|
280
|
+
this.stats = {};
|
281
|
+
var updateInterval = interval || 300;
|
282
|
+
(function(localThis) {
|
283
|
+
setInterval(function() {
|
284
|
+
debugBoxWrite(localThis.para, localThis.stats);
|
285
|
+
}, updateInterval);
|
286
|
+
}(this));
|
287
|
+
};
|
288
|
+
|
289
|
+
//not used !?
|
290
|
+
/**
|
291
|
+
* Add stat data for log
|
292
|
+
* @param {String} key - The data key
|
293
|
+
* @param {*} value - The value
|
294
|
+
*/
|
295
|
+
util.DebugBox.prototype.set = function(key, value) {
|
296
|
+
this.stats[key] = value;
|
297
|
+
};
|
298
|
+
|
299
|
+
//not used !?
|
300
|
+
/**
|
301
|
+
* Initialize stats in case where key does not exist, else
|
302
|
+
* increment value for key
|
303
|
+
* @param {String} key - The key to process
|
304
|
+
* @param {Number} incBy - Value to increment for given key (default: 1)
|
305
|
+
* @param {Number} init - Initial value in case where key does not exist (default: 0)
|
306
|
+
*/
|
307
|
+
util.DebugBox.prototype.inc = function(key, incBy, init) {
|
308
|
+
if (!this.stats[key]) {
|
309
|
+
this.stats[key] = init || 0;
|
310
|
+
}
|
311
|
+
this.stats[key] += incBy || 1;
|
312
|
+
};
|
313
|
+
|
314
|
+
//not used !?
|
315
|
+
/**
|
316
|
+
* Create a button and register the given function to the button click event
|
317
|
+
* @param {String} name - The button name to link
|
318
|
+
* @param {Function} func - The onClick callback
|
319
|
+
*/
|
320
|
+
util.DebugBox.prototype.addButton = function(name, func) {
|
321
|
+
if (!this.buttons[name]) {
|
322
|
+
this.buttons[name] = document.createElement('button');
|
323
|
+
this.div.appendChild(this.buttons[name]);
|
324
|
+
}
|
325
|
+
var button = this.buttons[name];
|
326
|
+
this.buttons[name] = button;
|
327
|
+
button.addEventListener('click', func);
|
328
|
+
button.innerText = name;
|
329
|
+
};
|
330
|
+
|
331
|
+
//not used !?
|
332
|
+
/**
|
333
|
+
* Search for a canvas elemenet with name, or create on if not exist.
|
334
|
+
* Then send the canvas element as callback parameter.
|
335
|
+
* @param {String} name - The canvas name to send/create
|
336
|
+
* @param {Function} func - The callback function where send canvas
|
337
|
+
*/
|
338
|
+
util.DebugBox.prototype.show = function(name, func) {
|
339
|
+
if (!this.canvas[name]) {
|
340
|
+
this.canvas[name] = document.createElement('canvas');
|
341
|
+
this.div.appendChild(this.canvas[name]);
|
342
|
+
}
|
343
|
+
var canvas = this.canvas[name];
|
344
|
+
canvas.getContext('2d').clearRect(0,0, canvas.width, canvas.height);
|
345
|
+
func(canvas);
|
346
|
+
};
|
347
|
+
|
348
|
+
export default util;
|