remote-calibrator 0.2.1 → 0.2.2-beta.3
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +19 -0
- package/README.md +5 -1
- package/homepage/example.js +13 -12
- package/lib/RemoteCalibrator.min.js +1 -1
- package/lib/RemoteCalibrator.min.js.LICENSE.txt +8 -1
- package/lib/RemoteCalibrator.min.js.map +1 -1
- package/package.json +10 -9
- 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/components/sound.js +12 -0
- package/src/const.js +8 -0
- package/src/core.js +36 -0
- package/src/css/distance.scss +1 -0
- package/src/css/main.css +11 -0
- package/src/css/panel.scss +1 -0
- package/src/distance/distance.js +8 -1
- package/src/distance/distanceTrack.js +2 -2
- package/src/index.js +2 -0
- package/src/panel/panel.js +53 -21
- package/src/screenSize.js +2 -1
- package/src/text.json +5 -3
@@ -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 =
|
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)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { Synth } from 'tone'
|
2
|
+
|
3
|
+
const feedbackSynth = new Synth({
|
4
|
+
oscillator: {
|
5
|
+
type: 'sine',
|
6
|
+
},
|
7
|
+
envelope: { attack: 0.001, decay: 0.001, sustain: 1, release: 0.001 },
|
8
|
+
}).toDestination()
|
9
|
+
|
10
|
+
export const soundFeedback = () => {
|
11
|
+
feedbackSynth.triggerAttackRelease(2000, 0.05)
|
12
|
+
}
|
package/src/const.js
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
import RemoteCalibrator from './core'
|
2
|
+
|
3
|
+
RemoteCalibrator.prototype._CONST = Object.freeze({
|
4
|
+
CREDIT_TEXT: {
|
5
|
+
BLIND_SPOT_TEST: `As suggested by the Li et al. (2020) "Virtual Chinrest" paper.`,
|
6
|
+
CREDIT_CARD: `Credit card suggested by the Li et al. (2020) "Virtual Chinrest" paper.`,
|
7
|
+
},
|
8
|
+
})
|
package/src/core.js
CHANGED
@@ -533,6 +533,30 @@ RemoteCalibrator.prototype._constructFloatInstructionElement = function (
|
|
533
533
|
return (this._background.instructionElement = instP)
|
534
534
|
}
|
535
535
|
|
536
|
+
RemoteCalibrator.prototype._setFloatInstructionElementPos = function (
|
537
|
+
side,
|
538
|
+
yOffset = 16
|
539
|
+
) {
|
540
|
+
// For blind spot test instructions
|
541
|
+
const r = this.instructionElement.getBoundingClientRect()
|
542
|
+
this.instructionElement.style.top = `calc(50% + ${yOffset + 10}px)`
|
543
|
+
if (side === 'left') {
|
544
|
+
this.instructionElement.style.left = '10%'
|
545
|
+
this.instructionElement.style.right = 'unset'
|
546
|
+
this.instructionElement.style.transform = `translate(${-r.width / 2}px, 0)`
|
547
|
+
} else if (side === 'right') {
|
548
|
+
this.instructionElement.style.right = '10%'
|
549
|
+
this.instructionElement.style.left = 'unset'
|
550
|
+
this.instructionElement.style.transform = `translate(${r.width / 2}px, 0)`
|
551
|
+
} else {
|
552
|
+
// Reset to center
|
553
|
+
this.instructionElement.style.left = '50%'
|
554
|
+
this.instructionElement.style.right = 'unset'
|
555
|
+
this.instructionElement.style.top = 'unset'
|
556
|
+
this.instructionElement.style.transform = 'translate(-50%, 0)'
|
557
|
+
}
|
558
|
+
}
|
559
|
+
|
536
560
|
RemoteCalibrator.prototype._removeFloatInstructionElement = function () {
|
537
561
|
if (this.instructionElement) {
|
538
562
|
this.background.removeChild(this.instructionElement)
|
@@ -542,4 +566,16 @@ RemoteCalibrator.prototype._removeFloatInstructionElement = function () {
|
|
542
566
|
return false
|
543
567
|
}
|
544
568
|
|
569
|
+
RemoteCalibrator.prototype._addCreditOnBackground = function (creditText) {
|
570
|
+
if (this.background === null) this._addBackground()
|
571
|
+
|
572
|
+
const p = document.createElement('p')
|
573
|
+
p.className = 'calibration-credit-text'
|
574
|
+
p.id = 'calibration-credit-text'
|
575
|
+
p.innerHTML = creditText
|
576
|
+
this.background.appendChild(p)
|
577
|
+
|
578
|
+
return p
|
579
|
+
}
|
580
|
+
|
545
581
|
export default RemoteCalibrator
|
package/src/css/distance.scss
CHANGED
package/src/css/main.css
CHANGED
@@ -82,3 +82,14 @@
|
|
82
82
|
.swal2-container {
|
83
83
|
z-index: 999997 !important;
|
84
84
|
}
|
85
|
+
|
86
|
+
/* -------------------------------------------------------------------------- */
|
87
|
+
|
88
|
+
.calibration-credit-text {
|
89
|
+
position: fixed;
|
90
|
+
left: 3px;
|
91
|
+
bottom: 3px;
|
92
|
+
font-size: 0.9rem;
|
93
|
+
color: #999;
|
94
|
+
margin: 0;
|
95
|
+
}
|
package/src/css/panel.scss
CHANGED
package/src/distance/distance.js
CHANGED
@@ -15,6 +15,7 @@ import {
|
|
15
15
|
} from '../components/onCanvas'
|
16
16
|
import { bindKeys, unbindKeys } from '../components/keyBinder'
|
17
17
|
import { addButtons } from '../components/buttons'
|
18
|
+
import { soundFeedback } from '../components/sound'
|
18
19
|
import text from '../text.json'
|
19
20
|
|
20
21
|
const blindSpotHTML = `<canvas id="blind-spot-canvas"></canvas>`
|
@@ -39,8 +40,9 @@ export function blindSpotTest(RC, options, toTrackDistance = false, callback) {
|
|
39
40
|
RC.background.appendChild(blindSpotDiv)
|
40
41
|
RC._constructFloatInstructionElement(
|
41
42
|
'blind-spot-instruction',
|
42
|
-
`
|
43
|
+
`Close <span id="eye-side"></span> eye.`
|
43
44
|
)
|
45
|
+
RC._addCreditOnBackground(RC._CONST.CREDIT_TEXT.BLIND_SPOT_TEST)
|
44
46
|
|
45
47
|
// Get HTML elements
|
46
48
|
const c = document.querySelector('#blind-spot-canvas')
|
@@ -48,6 +50,7 @@ export function blindSpotTest(RC, options, toTrackDistance = false, callback) {
|
|
48
50
|
|
49
51
|
const eyeSideEle = document.getElementById('eye-side')
|
50
52
|
let eyeSide = (eyeSideEle.innerText = 'LEFT').toLocaleLowerCase()
|
53
|
+
RC._setFloatInstructionElementPos(eyeSide, 16)
|
51
54
|
let crossX = _getCrossX(eyeSide, c.width)
|
52
55
|
|
53
56
|
let circleBounds
|
@@ -81,6 +84,8 @@ export function blindSpotTest(RC, options, toTrackDistance = false, callback) {
|
|
81
84
|
|
82
85
|
// SPACE
|
83
86
|
const finishFunction = () => {
|
87
|
+
soundFeedback()
|
88
|
+
|
84
89
|
tested += 1
|
85
90
|
// Average
|
86
91
|
dist.push(
|
@@ -113,6 +118,8 @@ export function blindSpotTest(RC, options, toTrackDistance = false, callback) {
|
|
113
118
|
if (eyeSide === 'left')
|
114
119
|
eyeSide = (eyeSideEle.innerText = 'RIGHT').toLocaleLowerCase()
|
115
120
|
else eyeSide = (eyeSideEle.innerText = 'LEFT').toLocaleLowerCase()
|
121
|
+
RC._setFloatInstructionElementPos(eyeSide, 16)
|
122
|
+
|
116
123
|
circleBounds = _getCircleBounds(eyeSide, crossX, c.width)
|
117
124
|
circleX = circleBounds[eyeSide === 'left' ? 0 : 1]
|
118
125
|
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',
|
package/src/index.js
CHANGED
package/src/panel/panel.js
CHANGED
@@ -88,9 +88,12 @@ RemoteCalibrator.prototype.panel = async function (
|
|
88
88
|
{
|
89
89
|
headline: text.panel.headline,
|
90
90
|
description: text.panel.description,
|
91
|
+
showNextButton: false,
|
92
|
+
nextHeadline: text.panel.nextHeadline,
|
93
|
+
nextDescription: text.panel.nextDescription,
|
91
94
|
nextButton: text.panel.nextButton,
|
92
95
|
color: '#3490de',
|
93
|
-
_demoActivateAll: false, // !
|
96
|
+
_demoActivateAll: false, // ! Private
|
94
97
|
},
|
95
98
|
options
|
96
99
|
)
|
@@ -116,10 +119,8 @@ RemoteCalibrator.prototype.panel = async function (
|
|
116
119
|
|
117
120
|
const panel = document.createElement('div')
|
118
121
|
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
|
-
: ''
|
122
|
+
panel.innerHTML = `<h1 class="rc-panel-title" id="rc-panel-title">${options.headline}</h1>`
|
123
|
+
panel.innerHTML += `<p class="rc-panel-description" id="rc-panel-description">${options.description}</p>`
|
123
124
|
panel.innerHTML += '<div class="rc-panel-steps" id="rc-panel-steps"></div>'
|
124
125
|
|
125
126
|
if (!_reset) parentElement.appendChild(panel)
|
@@ -143,7 +144,8 @@ RemoteCalibrator.prototype.panel = async function (
|
|
143
144
|
}
|
144
145
|
}
|
145
146
|
|
146
|
-
|
147
|
+
if (options.showNextButton || options._demoActivateAll)
|
148
|
+
steps.appendChild(_nextStepBlock(tasks.length, options))
|
147
149
|
|
148
150
|
// Activate the first one
|
149
151
|
let current = { index: 0, finished: [] }
|
@@ -168,7 +170,7 @@ RemoteCalibrator.prototype.panel = async function (
|
|
168
170
|
clearInterval(_)
|
169
171
|
resolve(resolveOnFinish)
|
170
172
|
}
|
171
|
-
},
|
173
|
+
}, 200)
|
172
174
|
})
|
173
175
|
}
|
174
176
|
|
@@ -286,20 +288,40 @@ const _setStepsClassesSL = (steps, panelWidth) => {
|
|
286
288
|
|
287
289
|
const _activateStepAt = (RC, current, tasks, options, finalCallback) => {
|
288
290
|
document.querySelectorAll('.rc-panel-step').forEach((e, ind) => {
|
291
|
+
const eIndex = Number(e.dataset.index)
|
292
|
+
|
289
293
|
if (!options._demoActivateAll) {
|
290
294
|
// Default situation
|
291
|
-
if (
|
295
|
+
if (eIndex === current.index) {
|
292
296
|
e.classList.replace('rc-panel-step-inactive', 'rc-panel-step-active')
|
293
|
-
if (
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
297
|
+
if (eIndex !== tasks.length) {
|
298
|
+
if (eIndex === tasks.length - 1 && !options.showNextButton) {
|
299
|
+
e.onclick = () => {
|
300
|
+
RC[_getTaskName(tasks[current.index])](
|
301
|
+
..._getTaskOptionsCallbacks(tasks[current.index], finalCallback)
|
302
|
+
)
|
303
|
+
_finishStepAt(current.index)
|
304
|
+
}
|
305
|
+
} else {
|
306
|
+
e.onclick = () => {
|
307
|
+
RC[_getTaskName(tasks[current.index])](
|
308
|
+
..._getTaskOptionsCallbacks(tasks[current.index])
|
309
|
+
)
|
310
|
+
_finishStepAt(current.index)
|
311
|
+
current.index++
|
312
|
+
_activateStepAt(RC, current, tasks, options, finalCallback)
|
313
|
+
}
|
301
314
|
}
|
302
|
-
} else {
|
315
|
+
} else if (eIndex === tasks.length && options.showNextButton) {
|
316
|
+
// Change headline and description
|
317
|
+
const { headline, nextHeadline, description, nextDescription } =
|
318
|
+
options
|
319
|
+
if (headline !== nextHeadline)
|
320
|
+
document.querySelector('#rc-panel-title').innerHTML = nextHeadline
|
321
|
+
if (description !== nextDescription)
|
322
|
+
document.querySelector('#rc-panel-description').innerHTML =
|
323
|
+
nextDescription
|
324
|
+
|
303
325
|
e.onclick = () => {
|
304
326
|
RC._panelFinished = true
|
305
327
|
if (finalCallback && typeof finalCallback === 'function')
|
@@ -347,17 +369,27 @@ const _getTaskName = task => {
|
|
347
369
|
return task.name
|
348
370
|
}
|
349
371
|
|
350
|
-
const _getTaskOptionsCallbacks = task => {
|
372
|
+
const _getTaskOptionsCallbacks = (task, finalCallback = null) => {
|
351
373
|
if (typeof task === 'string') return []
|
352
374
|
|
375
|
+
const _ = () => {
|
376
|
+
if (task.callback && typeof task.callback === 'function') task.callback()
|
377
|
+
if (finalCallback && typeof finalCallback === 'function') finalCallback()
|
378
|
+
}
|
379
|
+
|
353
380
|
if (['displaySize', 'environment'].includes(task.name)) {
|
354
|
-
return [
|
381
|
+
return [_]
|
355
382
|
} else if (['screenSize', 'trackGaze'].includes(task.name)) {
|
356
|
-
return [task.options || {},
|
383
|
+
return [task.options || {}, _]
|
357
384
|
} else if (['trackDistance'].includes(task.name)) {
|
358
385
|
return [
|
359
386
|
task.options || {},
|
360
|
-
|
387
|
+
() => {
|
388
|
+
if (task.callbackStatic && typeof task.callbackStatic === 'function')
|
389
|
+
task.callbackStatic()
|
390
|
+
if (finalCallback && typeof finalCallback === 'function')
|
391
|
+
finalCallback()
|
392
|
+
},
|
361
393
|
task.callbackTrack || null,
|
362
394
|
]
|
363
395
|
}
|
package/src/screenSize.js
CHANGED
@@ -58,10 +58,11 @@ RemoteCalibrator.prototype.screenSize = function (options = {}, callback) {
|
|
58
58
|
|
59
59
|
this.getFullscreen(options.fullscreen)
|
60
60
|
|
61
|
-
options.description += `<br /><b>I have a <select id="matching-obj"><option value="usba" selected>USB Type-A Connector</option><option value="usbc">USB Type-C Connector</option><option value="card">Credit Card</option></select> with me.</b>`
|
61
|
+
options.description += `<br /><b style="display: inline-flex">I have a <select id="matching-obj"><option value="usba" selected>USB Type-A Connector</option><option value="usbc">USB Type-C Connector</option><option value="card">Credit Card</option></select> with me.</b>`
|
62
62
|
|
63
63
|
this._addBackground()
|
64
64
|
this._addBackgroundText(options.headline, options.description)
|
65
|
+
this._addCreditOnBackground(this._CONST.CREDIT_TEXT.CREDIT_CARD)
|
65
66
|
|
66
67
|
getSize(this, this.background, options, callback)
|
67
68
|
|
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
|
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
|
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",
|
@@ -20,8 +20,10 @@
|
|
20
20
|
"description": "We will measure your gaze accuracy. Please do not move the mouse and look at the fixation at the middle of the screen for the next 5 seconds."
|
21
21
|
},
|
22
22
|
"panel": {
|
23
|
-
"headline": "
|
23
|
+
"headline": "Please press a button, to calibrate.",
|
24
24
|
"description": "",
|
25
|
+
"nextHeadline": "Thanks for calibrating. Hit the button to continue.",
|
26
|
+
"nextDescription": "",
|
25
27
|
"nextButton": "Done"
|
26
28
|
},
|
27
29
|
"measurePD": {
|