remote-calibrator 0.2.1 → 0.2.2-beta.3
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 +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": {
|