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,40 @@
1
+ const puppeteer = require('puppeteer');
2
+ const { assert } = require('chai');
3
+ const TFFaceMesh = require('@tensorflow-models/facemesh');
4
+
5
+ describe('webgazerExtract functions', async () => {
6
+ let browser,page;
7
+ before(async () => {
8
+ browser = await puppeteer.launch()
9
+ page = await browser.newPage();
10
+ await page.goto('http://localhost:8000/webgazerExtractClient.html');
11
+ })
12
+ after(async () => {
13
+ await browser.close();
14
+ })
15
+ it('should load elements', async() =>{
16
+ const elements = await page.evaluate(() => {
17
+ return {myMouse:document.getElementById('myMouse'),
18
+ tobiiGP:document.getElementById('tobiiGP'),
19
+ wsCanvas: document.getElementById('wsCanvas'),
20
+ screencap: document.getElementById('screencap'),
21
+ showScreenCap: document.getElementById('showScreenCap'),
22
+ scTimeOffsetDiv: document.getElementById('scTimeOffsetDiv'),
23
+ diagDiv: document.getElementById('diagDiv')}
24
+ })
25
+ for(const [k,v] of Object.entries(elements)){
26
+ assert.equal((Object.keys(v).length === 0
27
+ && v.constructor === Object), true)
28
+ }
29
+ });
30
+
31
+ it('webgazer properties should be set correctly', async() =>{
32
+ await page.waitForSelector('#overlay');
33
+ let model = await page.evaluate(async() => {
34
+ let tracker = webgazer.getTracker();
35
+ return tracker.name
36
+ })
37
+ assert.equal(model,'TFFaceMesh')
38
+ })
39
+ });
40
+
@@ -0,0 +1,160 @@
1
+ const puppeteer = require('puppeteer');
2
+ const { assert } = require('chai');
3
+ const TFFaceMesh = require('@tensorflow-models/facemesh');
4
+
5
+ before(async () => {
6
+ const parent_dir = __dirname.substring(0,__dirname.length-4)
7
+ let my_y4m_video = parent_dir + 'www/data/src/P_01/dot.y4m'
8
+ browser = await puppeteer.launch({args:['--use-file-for-fake-video-capture='+my_y4m_video,
9
+ '--allow-file-access', '--use-fake-device-for-media-stream','--use-fake-ui-for-media-stream',
10
+ '--no-sandbox','--disable-setuid-sandbox',
11
+ ]
12
+ //,devtools:true //enable for debugging
13
+ });
14
+ page = await browser.newPage();
15
+ await page.goto('http://localhost:3000/calibration.html?');
16
+ page.coverage.startJSCoverage();
17
+ await page.goto('http://localhost:3000/calibration.html?');
18
+ await page.waitFor(1500)
19
+ await page.waitForSelector('#start_calibration')
20
+ //calibration button is not immediately clickable due to css transition
21
+ await page.waitFor(2500)
22
+
23
+ await page.evaluate(async() => {
24
+ document.querySelector("#start_calibration").click()
25
+ })
26
+ await page.waitFor(1500)
27
+ await page.evaluate(async() =>{
28
+ document.querySelector("body > div.swal-overlay.swal-overlay--show-modal > div > div.swal-footer > div > button").click()
29
+ })
30
+ })
31
+ describe('webgazer function', async() => {
32
+ after(async () => {
33
+ const jsCoverage = await page.coverage.stopJSCoverage();
34
+ let usedBytes = 0;
35
+ let webgazer_coverage;
36
+ jsCoverage.forEach(item => {if (item.url == "http://localhost:3000/webgazer.js")
37
+ {webgazer_coverage = item}
38
+ })
39
+ webgazer_coverage.ranges.forEach(range => (usedBytes += range.end - range.start - 1));
40
+ console.log((100*usedBytes/webgazer_coverage.text.length).toFixed(4), "% Code Coverage on webgazer.js")
41
+ await browser.close();
42
+ })
43
+ describe('top level functions', async() =>{
44
+ it('should be able to recognize video input', async() =>{
45
+ const videoAvailable = await page.evaluate(async() => {
46
+ return await webgazer.params.showFaceFeedbackBox;
47
+ });
48
+ const isReady = await page.evaluate(async() => {
49
+ return await webgazer.isReady()
50
+ });
51
+ assert.equal(videoAvailable,true);
52
+ assert.equal(isReady,true);
53
+ });
54
+ //modifying visibility params
55
+ it('webgazerVideoFeed should display', async() => {
56
+ let video_display = await page.evaluate(async() => {
57
+ return document.getElementById('webgazerVideoFeed').style.display
58
+ })
59
+ assert.notEqual(video_display,"none");
60
+ })
61
+ it('webgazerFaceFeedbackBox should display', async() => {
62
+ await page.waitForSelector('#webgazerFaceFeedbackBox')
63
+ let face_overlay = await page.evaluate(async() => {
64
+ return document.getElementById('webgazerFaceFeedbackBox').style.display
65
+ })
66
+ assert.notEqual(face_overlay,"none");
67
+ })
68
+ it('webgazerGazeDot should display', async() => {
69
+ let webgazer_gazedot = await page.evaluate(async() => {
70
+ return document.getElementById('webgazerGazeDot').style.display
71
+ })
72
+ assert.notEqual(webgazer_gazedot,"none");
73
+ })
74
+ it('faceoverlay should hide when showFaceOverlay is false', async() => {
75
+ face_overlay = await page.evaluate(async() => {
76
+ await webgazer.showFaceFeedbackBox(false)
77
+ return document.getElementById('webgazerFaceFeedbackBox').style.display
78
+ })
79
+ assert.equal(face_overlay,"none");
80
+ })
81
+ it('webgazerGazeDot should hide when showPredictionPoints is false', async() =>{
82
+ let webgazer_gazedot = await page.evaluate(async() => {
83
+ await webgazer.showPredictionPoints(false)
84
+ return document.getElementById('webgazerGazeDot').style.display
85
+ })
86
+ assert.equal(webgazer_gazedot,"none");
87
+ })
88
+ it('webgazerVideoFeed should hide when showVideo is false', async() => {
89
+ video_display = await page.evaluate(async() => {
90
+ await webgazer.showVideo(false)
91
+ return document.getElementById('webgazerVideoFeed').style.display
92
+ });
93
+ assert.equal(video_display,"none");
94
+ })
95
+ it('getVideoElementCanvas should exist and be a canvas element', async() => {
96
+ let video_element_canvas_type = await page.evaluate(async() => {
97
+ return await webgazer.getVideoElementCanvas().nodeName
98
+ })
99
+ assert.equal(video_element_canvas_type,'CANVAS')
100
+ })
101
+ it('preview to camera resolution ratio should be [0.5,0.5]', async() =>{
102
+ let preview_to_camera_resolution_ratio = await page.evaluate(async() => {
103
+ return await webgazer.getVideoPreviewToCameraResolutionRatio()
104
+ })
105
+ assert.equal(preview_to_camera_resolution_ratio[0],0.5)
106
+ assert.equal(preview_to_camera_resolution_ratio[1],0.5)
107
+ })
108
+ it('should be able to change video viewer size', async()=>{
109
+ const video_dimensions = await page.evaluate(async()=>{
110
+ return [webgazer.params.videoViewerWidth,webgazer.params.videoViewerHeight]
111
+ })
112
+ const new_dimensions = [video_dimensions[0],video_dimensions[1]]
113
+ const new_video_dimensions = await page.evaluate(async(new_dimensions)=>{
114
+ await webgazer.setVideoViewerSize(new_dimensions[0],new_dimensions[1])
115
+ return [webgazer.params.videoViewerWidth,webgazer.params.videoViewerHeight]
116
+ },new_dimensions)
117
+ assert.equal(new_video_dimensions[0],new_dimensions[0])
118
+ assert.equal(new_video_dimensions[1],new_dimensions[1])
119
+ })
120
+ it('top level, non-video no arguments webgazer functions should work', async() =>{
121
+ let basic_functions = await page.evaluate(async() => {
122
+ return {getCurrentPrediction: JSON.stringify(await webgazer.getCurrentPrediction()),
123
+ addMouseEventListeners: JSON.stringify(await webgazer.addMouseEventListeners()),
124
+ getStoredPoints:JSON.stringify(await webgazer.getStoredPoints()),
125
+ removeMouseEventListeners:JSON.stringify(await webgazer.removeMouseEventListeners()),
126
+ isReady:JSON.stringify(await webgazer.isReady()),
127
+ detectCompatibility:JSON.stringify(await webgazer.detectCompatibility()),
128
+ clearGazeListener:JSON.stringify(await webgazer.clearGazeListener()),
129
+ getRegression:JSON.stringify(await webgazer.getRegression()),
130
+ getStoredPoints:JSON.stringify(await webgazer.getStoredPoints()),
131
+ pause:JSON.stringify(await webgazer.pause())
132
+ }
133
+ })
134
+
135
+
136
+ for(const [k,v] of Object.entries(basic_functions)){
137
+ assert.notEqual(Object.keys(v),null)
138
+ assert.notEqual(Object.keys(v),{})
139
+ }
140
+
141
+ assert.equal(basic_functions.isReady,"true")
142
+ assert.equal(basic_functions.detectCompatibility,"true")
143
+ })
144
+ it('can record screen position, set tracker and regression and set static video', async() =>{
145
+ const screen_functions = page.evaluate(async() => {
146
+ return {setStaticVideo: await webgazer.setStaticVideo('../www/data/src/P_02/1491487691210_2_-study-dot_test_instructions.webm'),
147
+ setTracker: await webgazer.setTracker('TFFacemesh'),
148
+ setRegression: await webgazer.setRegression('ridge')}
149
+ })
150
+ for(const [k,v] of Object.entries(screen_functions)){
151
+ assert.notEqual(Object.keys(v),null)
152
+ assert.notEqual(Object.keys(v),{})
153
+ }
154
+ })
155
+ //checkEyesInValidationBox exists in code but the comment above says it's wrong and it returns nothing
156
+ })
157
+ require('./regression_test')
158
+ require('./util_test')
159
+
160
+ })
@@ -0,0 +1,41 @@
1
+ const puppeteer = require('puppeteer');
2
+ const { assert } = require('chai');
3
+
4
+ describe('Main Page Basics', async () => {
5
+ let browser,page,response;
6
+ before(async () => {
7
+ browser = await puppeteer.launch();
8
+ page = await browser.newPage();
9
+ response = await page.goto('http://localhost:3000');
10
+ })
11
+
12
+ after(async () => {
13
+ await browser.close();
14
+ })
15
+
16
+ it('Page response should be 200', async() =>{
17
+ assert.equal(response.status(),200)
18
+ })
19
+ it('clicking the collision button should send you to new page' , async() =>{
20
+ const collision_button = "#collision_button";
21
+ const [response] = await Promise.all([
22
+ page.waitForNavigation(),
23
+ page.click(collision_button),
24
+ ]);
25
+ assert.equal(page.url(),'http://localhost:3000/collision.html?')
26
+ assert.equal(response.status(),200)
27
+ });
28
+ it('clicking the calibration button should send you to new page' , async() =>{
29
+ await page.goto('http://localhost:3000');
30
+ const calibration_button = "#calibration_button";
31
+ const [response] = await Promise.all([
32
+ page.waitForNavigation(),
33
+ page.click(calibration_button),
34
+ ]);
35
+ assert.equal(page.url(),'http://localhost:3000/calibration.html?')
36
+ assert.equal(response.status(),200)
37
+ });
38
+ });
39
+
40
+
41
+
@@ -5,12 +5,11 @@ import { colorDarkRed } from '../constants'
5
5
 
6
6
  // CROSS
7
7
  const crossLW = 32 // Width of a line of the middle cross
8
- const crossLH = 4
8
+ const crossLH = 3
9
9
  export const _getCrossX = (eyeSide, tX) => {
10
10
  return eyeSide === 'left' ? tX * 0.1 : tX * 0.9
11
11
  }
12
12
  export function _cross(ctx, cX, mY) {
13
- // Draw a cross at the middle of the canvas
14
13
  ctx.fillStyle = '#000'
15
14
  ctx.fillRect(cX - (crossLW >> 1), mY - (crossLH >> 1), crossLW, crossLH)
16
15
  ctx.fillRect(cX - (crossLH >> 1), mY - (crossLW >> 1), crossLH, crossLW)
package/src/core.js CHANGED
@@ -533,6 +533,24 @@ RemoteCalibrator.prototype._constructFloatInstructionElement = function (
533
533
  return (this._background.instructionElement = instP)
534
534
  }
535
535
 
536
+ RemoteCalibrator.prototype._setFloatInstructionElementPos = function (side) {
537
+ const r = this.instructionElement.getBoundingClientRect()
538
+ if (side === 'left') {
539
+ this.instructionElement.style.left = '10%'
540
+ this.instructionElement.style.right = 'unset'
541
+ this.instructionElement.style.transform = `translate(${-r.width / 2}px, 0)`
542
+ } else if (side === 'right') {
543
+ this.instructionElement.style.right = '10%'
544
+ this.instructionElement.style.left = 'unset'
545
+ this.instructionElement.style.transform = `translate(${r.width / 2}px, 0)`
546
+ } else {
547
+ // Reset to center
548
+ this.instructionElement.style.left = '50%'
549
+ this.instructionElement.style.right = 'unset'
550
+ this.instructionElement.style.transform = 'translate(-50%, 0)'
551
+ }
552
+ }
553
+
536
554
  RemoteCalibrator.prototype._removeFloatInstructionElement = function () {
537
555
  if (this.instructionElement) {
538
556
  this.background.removeChild(this.instructionElement)
@@ -11,6 +11,7 @@
11
11
  }
12
12
 
13
13
  #blind-spot-instruction {
14
+ display: block !important;
14
15
  top: 65%;
15
16
  }
16
17
 
@@ -39,7 +39,7 @@ export function blindSpotTest(RC, options, toTrackDistance = false, callback) {
39
39
  RC.background.appendChild(blindSpotDiv)
40
40
  RC._constructFloatInstructionElement(
41
41
  'blind-spot-instruction',
42
- `Keep your <span id="eye-side"></span> eye closed and focus on the cross.`
42
+ `Close <span id="eye-side"></span> eye.`
43
43
  )
44
44
 
45
45
  // Get HTML elements
@@ -48,6 +48,7 @@ export function blindSpotTest(RC, options, toTrackDistance = false, callback) {
48
48
 
49
49
  const eyeSideEle = document.getElementById('eye-side')
50
50
  let eyeSide = (eyeSideEle.innerText = 'LEFT').toLocaleLowerCase()
51
+ RC._setFloatInstructionElementPos(eyeSide)
51
52
  let crossX = _getCrossX(eyeSide, c.width)
52
53
 
53
54
  let circleBounds
@@ -113,6 +114,8 @@ export function blindSpotTest(RC, options, toTrackDistance = false, callback) {
113
114
  if (eyeSide === 'left')
114
115
  eyeSide = (eyeSideEle.innerText = 'RIGHT').toLocaleLowerCase()
115
116
  else eyeSide = (eyeSideEle.innerText = 'LEFT').toLocaleLowerCase()
117
+ RC._setFloatInstructionElementPos(eyeSide)
118
+
116
119
  circleBounds = _getCircleBounds(eyeSide, crossX, c.width)
117
120
  circleX = circleBounds[eyeSide === 'left' ? 0 : 1]
118
121
  v = eyeSide === 'left' ? 1 : -1
@@ -138,8 +138,8 @@ RemoteCalibrator.prototype.trackDistance = function (
138
138
 
139
139
  const startTrackingPupils = async (RC, beforeCallbackTrack, callbackTrack) => {
140
140
  RC.gazeTracker.beginVideo({ pipWidthPx: trackingOptions.pipWidthPx }, () => {
141
- beforeCallbackTrack()
142
141
  RC._removeFloatInstructionElement()
142
+ beforeCallbackTrack()
143
143
  _tracking(RC, trackingOptions, callbackTrack)
144
144
  })
145
145
  }
@@ -269,7 +269,7 @@ const _tracking = async (RC, trackingOptions, callbackTrack) => {
269
269
  callbackTrack({
270
270
  value: {
271
271
  viewingDistanceCm: data.value,
272
- nearPointCm: nPData ? nPData.value : null,
272
+ nearPointCm: nPData ? nPData.value : [null, null],
273
273
  },
274
274
  timestamp: timestamp,
275
275
  method: 'Facemesh Predict',
@@ -88,9 +88,11 @@ RemoteCalibrator.prototype.panel = async function (
88
88
  {
89
89
  headline: text.panel.headline,
90
90
  description: text.panel.description,
91
+ nextHeadline: text.panel.nextHeadline,
92
+ nextDescription: text.panel.nextDescription,
91
93
  nextButton: text.panel.nextButton,
92
94
  color: '#3490de',
93
- _demoActivateAll: false, // ! Not open for users
95
+ _demoActivateAll: false, // ! Private
94
96
  },
95
97
  options
96
98
  )
@@ -116,10 +118,8 @@ RemoteCalibrator.prototype.panel = async function (
116
118
 
117
119
  const panel = document.createElement('div')
118
120
  panel.className = panel.id = 'rc-panel'
119
- panel.innerHTML = `<h1 class="rc-panel-title">${options.headline}</h1>`
120
- panel.innerHTML += options.description
121
- ? `<p class="rc-panel-description">${options.description}</p>`
122
- : ''
121
+ panel.innerHTML = `<h1 class="rc-panel-title" id="rc-panel-title">${options.headline}</h1>`
122
+ panel.innerHTML += `<p class="rc-panel-description" id="rc-panel-description">${options.description}</p>`
123
123
  panel.innerHTML += '<div class="rc-panel-steps" id="rc-panel-steps"></div>'
124
124
 
125
125
  if (!_reset) parentElement.appendChild(panel)
@@ -300,6 +300,15 @@ const _activateStepAt = (RC, current, tasks, options, finalCallback) => {
300
300
  _activateStepAt(RC, current, tasks, options, finalCallback)
301
301
  }
302
302
  } else {
303
+ // Change headline and description
304
+ const { headline, nextHeadline, description, nextDescription } =
305
+ options
306
+ if (headline !== nextHeadline)
307
+ document.querySelector('#rc-panel-title').innerHTML = nextHeadline
308
+ if (description !== nextDescription)
309
+ document.querySelector('#rc-panel-description').innerHTML =
310
+ nextDescription
311
+
303
312
  e.onclick = () => {
304
313
  RC._panelFinished = true
305
314
  if (finalCallback && typeof finalCallback === 'function')
package/src/text.json CHANGED
@@ -5,11 +5,11 @@
5
5
  },
6
6
  "measureDistance": {
7
7
  "headline": "📏 Measure Viewing Distance",
8
- "description": "We'll measure your viewing distance. To do this, we will perform a blind spot test. Close/Cover one of your eyes (as instructed below) and focus on the black cross. Press <b>SPACE</b> or click <b>OK</b> when the red circle disappears. If it doesn't disappear, try moving closer to the screen."
8
+ "description": "We'll measure your viewing distance. To do this, we will perform a blind spot test. Close/Cover one of your eyes (as instructed below) and focus on the black crosshair. Press <b>SPACE</b> or click <b>OK</b> when the red circle disappears. If it doesn't disappear, try moving closer to the screen."
9
9
  },
10
10
  "trackDistance": {
11
11
  "headline": "🙂 Set up for Head Tracking",
12
- "description": "Now we'll set up head tracking, to monitor viewing distance. When asked, please grant permission to access your camera. We'll perform a blind spot test first. Close/Cover one of your eyes (as instructed below) and focus on the black cross. Please press <b>SPACE</b> or click <b>OK</b> when the red dot disappears. If it doesn't disappear, try moving closer to the screen. You'll do this twice with each eye. Once you're done, head tracking will begin."
12
+ "description": "Now we'll set up head tracking, to monitor viewing distance. When asked, please grant permission to access your camera. We'll perform a blind spot test first. Close/Cover one of your eyes (as instructed below) and focus on the black crosshair. Please press <b>SPACE</b> or click <b>OK</b> when the red dot disappears. If it doesn't disappear, try moving closer to the screen. You'll do this twice with each eye. Once you're done, head tracking will begin."
13
13
  },
14
14
  "calibrateGaze": {
15
15
  "headline": "👀 Set up for Gaze Tracking",
@@ -22,6 +22,8 @@
22
22
  "panel": {
23
23
  "headline": "To calibrate, press a button!",
24
24
  "description": "",
25
+ "nextHeadline": "Thanks for calibrating. Hit the button to continue.",
26
+ "nextDescription": "",
25
27
  "nextButton": "Done"
26
28
  },
27
29
  "measurePD": {