"use strict"; console.log("thread starting"); // Add src/util.mjs and src/mat.mjs to the same directory as your html file importScripts("./worker_scripts/util.js", "./worker_scripts/mat.js"); // [20200708] Figure out how to make all of this wrap up neatly var ridgeParameter = Math.pow(10, -5); var resizeWidth = 10; var resizeHeight = 6; var dataWindow = 700; var trailDataWindow = 10; var trainInterval = 500; var screenXClicksArray = new self.webgazer.util.DataWindow(dataWindow); var screenYClicksArray = new self.webgazer.util.DataWindow(dataWindow); var eyeFeaturesClicks = new self.webgazer.util.DataWindow(dataWindow); var dataClicks = new self.webgazer.util.DataWindow(dataWindow); var screenXTrailArray = new self.webgazer.util.DataWindow(trailDataWindow); var screenYTrailArray = new self.webgazer.util.DataWindow(trailDataWindow); var eyeFeaturesTrail = new self.webgazer.util.DataWindow(trailDataWindow); var dataTrail = new self.webgazer.util.DataWindow(trailDataWindow); /** * Performs ridge regression, according to the Weka code. * @param {Array} y - corresponds to screen coordinates (either x or y) for each of n click events * @param {Array.>} X - corresponds to gray pixel features (120 pixels for both eyes) for each of n clicks * @param {Array} k - ridge parameter * @return{Array} regression coefficients */ function ridge(y, X, k) { var nc = X[0].length; var m_Coefficients = new Array(nc); var xt = self.webgazer.mat.transpose(X); var solution = new Array(); var success = true; do { var ss = self.webgazer.mat.mult(xt, X); // Set ridge regression adjustment for (var i = 0; i < nc; i++) { ss[i][i] = ss[i][i] + k; } // Carry out the regression var bb = self.webgazer.mat.mult(xt, y); for (var i = 0; i < nc; i++) { m_Coefficients[i] = bb[i][0]; } try { var n = m_Coefficients.length !== 0 ? m_Coefficients.length / m_Coefficients.length : 0; if (m_Coefficients.length * n !== m_Coefficients.length) { console.log("Array length must be a multiple of m"); } solution = ss.length === ss[0].length ? self.webgazer.mat.LUDecomposition(ss, bb) : self.webgazer.mat.QRDecomposition(ss, bb); for (var i = 0; i < nc; i++) { m_Coefficients[i] = solution[i][0]; } success = true; } catch (ex) { k *= 10; console.log(ex); success = false; } } while (!success); return m_Coefficients; } //TODO: still usefull ??? /** * * @returns {Number} */ function getCurrentFixationIndex() { var index = 0; var recentX = this.screenXTrailArray.get(0); var recentY = this.screenYTrailArray.get(0); for (var i = this.screenXTrailArray.length - 1; i >= 0; i--) { var currX = this.screenXTrailArray.get(i); var currY = this.screenYTrailArray.get(i); var euclideanDistance = Math.sqrt(Math.pow(currX - recentX, 2) + Math.pow(currY - recentY, 2)); if (euclideanDistance > 72) { return i + 1; } } return i; } /** * Event handler, it store screen position to allow training * @param {Event} event - the receive event */ self.onmessage = function (event) { var data = event.data; var screenPos = data["screenPos"]; var eyes = data["eyes"]; var type = data["type"]; if (type === "click") { self.screenXClicksArray.push([screenPos[0]]); self.screenYClicksArray.push([screenPos[1]]); self.eyeFeaturesClicks.push(eyes); } else if (type === "move") { self.screenXTrailArray.push([screenPos[0]]); self.screenYTrailArray.push([screenPos[1]]); self.eyeFeaturesTrail.push(eyes); self.dataTrail.push({ eyes: eyes, screenPos: screenPos, type: type }); } self.needsTraining = true; }; /** * Compute coefficient from training data */ function retrain() { if (self.screenXClicksArray.length === 0) { return; } if (!self.needsTraining) { return; } var screenXArray = self.screenXClicksArray.data.concat(self.screenXTrailArray.data); var screenYArray = self.screenYClicksArray.data.concat(self.screenYTrailArray.data); var eyeFeatures = self.eyeFeaturesClicks.data.concat(self.eyeFeaturesTrail.data); var coefficientsX = ridge(screenXArray, eyeFeatures, ridgeParameter); var coefficientsY = ridge(screenYArray, eyeFeatures, ridgeParameter); self.postMessage({ X: coefficientsX, Y: coefficientsY }); self.needsTraining = false; } setInterval(retrain, trainInterval);