@jspsych/plugin-tobii-user-position 0.1.1
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/README.md +133 -0
- package/dist/index.browser.js +690 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.browser.min.js +141 -0
- package/dist/index.browser.min.js.map +1 -0
- package/dist/index.cjs +689 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +282 -0
- package/dist/index.js +687 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
- package/src/index.d.ts +279 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +337 -0
- package/src/index.js.map +1 -0
- package/src/index.spec.d.ts +2 -0
- package/src/index.spec.d.ts.map +1 -0
- package/src/index.spec.js +102 -0
- package/src/index.spec.js.map +1 -0
- package/src/index.spec.ts +126 -0
- package/src/index.ts +368 -0
- package/src/position-display.d.ts +59 -0
- package/src/position-display.d.ts.map +1 -0
- package/src/position-display.js +421 -0
- package/src/position-display.js.map +1 -0
- package/src/position-display.ts +532 -0
- package/src/types.d.ts +18 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.js +2 -0
- package/src/types.js.map +1 -0
- package/src/types.ts +18 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../package.json","../src/position-display.js","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-tobii-user-position\",\n \"version\": \"0.1.1\",\n \"description\": \"jsPsych plugin for Tobii eye tracker user position guide\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-tobii.git\",\n \"directory\": \"packages/plugin-tobii-user-position\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"eye-tracking\",\n \"tobii\",\n \"user-position\",\n \"positioning-guide\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-tobii/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\",\n \"@jspsych/extension-tobii\": \"^0.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"@jspsych/extension-tobii\": \"^0.1.1\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","/**\n * Display component for user position guide\n * Shows a face outline that scales with distance, similar to Tobii Eye Tracker Manager\n */\nexport class PositionDisplay {\n constructor(container, options) {\n // Constants for the display\n this.BOX_WIDTH = 400;\n this.BOX_HEIGHT = 300;\n this.MIN_FACE_SCALE = 0.4; // Scale when far away\n this.MAX_FACE_SCALE = 1.6; // Scale when too close\n this.OPTIMAL_FACE_SCALE = 1.0; // Scale at optimal distance\n this.container = container;\n this.options = options;\n this.createDisplay();\n }\n createDisplay() {\n this.container.innerHTML = '';\n // Message\n this.messageElement = document.createElement('div');\n this.messageElement.className = 'tobii-user-position-message';\n this.messageElement.textContent = this.options.message;\n this.container.appendChild(this.messageElement);\n // Position guide container\n const guideContainer = document.createElement('div');\n guideContainer.className = 'tobii-user-position-guide';\n this.container.appendChild(guideContainer);\n // Tracking box (represents the optimal tracking zone)\n this.trackingBoxElement = document.createElement('div');\n this.trackingBoxElement.className = 'tobii-tracking-box';\n this.trackingBoxElement.setAttribute('role', 'img');\n this.trackingBoxElement.setAttribute('aria-label', 'Head position tracking display');\n this.trackingBoxElement.style.cssText = `\n position: relative;\n width: ${this.BOX_WIDTH}px;\n height: ${this.BOX_HEIGHT}px;\n border: 3px solid #666;\n border-radius: 12px;\n background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);\n overflow: hidden;\n box-shadow: inset 0 0 30px rgba(0,0,0,0.5);\n `;\n guideContainer.appendChild(this.trackingBoxElement);\n // Optimal zone indicator (center rectangle)\n const optimalZone = document.createElement('div');\n optimalZone.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 60%;\n height: 70%;\n border: 2px dashed rgba(255,255,255,0.2);\n border-radius: 8px;\n `;\n this.trackingBoxElement.appendChild(optimalZone);\n // Face outline container (this moves and scales)\n this.faceOutlineElement = document.createElement('div');\n this.faceOutlineElement.className = 'tobii-face-outline';\n this.faceOutlineElement.setAttribute('aria-hidden', 'true');\n this.faceOutlineElement.style.cssText = `\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n transition: all 0.1s ease-out;\n `;\n this.trackingBoxElement.appendChild(this.faceOutlineElement);\n // Create face SVG\n this.createFaceSVG();\n // Distance bar (vertical bar on the side)\n if (this.options.showDistanceFeedback) {\n this.distanceBarContainer = document.createElement('div');\n this.distanceBarContainer.style.cssText = `\n position: absolute;\n right: -40px;\n top: 10%;\n width: 20px;\n height: 80%;\n background: rgba(0,0,0,0.3);\n border-radius: 10px;\n border: 2px solid #444;\n overflow: hidden;\n `;\n guideContainer.appendChild(this.distanceBarContainer);\n // Distance labels\n const closeLabel = document.createElement('div');\n closeLabel.textContent = 'Close';\n closeLabel.style.cssText = `\n position: absolute;\n right: -70px;\n top: 0;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(closeLabel);\n const farLabel = document.createElement('div');\n farLabel.textContent = 'Far';\n farLabel.style.cssText = `\n position: absolute;\n right: -55px;\n bottom: 10%;\n font-size: 10px;\n color: #888;\n `;\n guideContainer.appendChild(farLabel);\n this.distanceBarFill = document.createElement('div');\n this.distanceBarFill.style.cssText = `\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 50%;\n background: ${this.options.goodColor};\n border-radius: 8px;\n transition: all 0.1s ease-out;\n `;\n this.distanceBarContainer.appendChild(this.distanceBarFill);\n // Optimal zone marker on distance bar\n const optimalMarker = document.createElement('div');\n optimalMarker.style.cssText = `\n position: absolute;\n left: -2px;\n right: -2px;\n top: 40%;\n height: 20%;\n border: 2px solid rgba(255,255,255,0.5);\n border-radius: 4px;\n pointer-events: none;\n `;\n this.distanceBarContainer.appendChild(optimalMarker);\n }\n // Textual feedback\n this.feedbackElement = document.createElement('div');\n this.feedbackElement.className = 'tobii-position-feedback';\n this.feedbackElement.setAttribute('role', 'status');\n this.feedbackElement.setAttribute('aria-live', 'polite');\n this.feedbackElement.style.cssText = `\n margin-top: 20px;\n font-size: 1.1em;\n font-weight: 600;\n text-align: center;\n `;\n this.container.appendChild(this.feedbackElement);\n }\n createFaceSVG() {\n // Base size for the face\n const baseWidth = 120;\n const baseHeight = 150;\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('width', `${baseWidth}`);\n svg.setAttribute('height', `${baseHeight}`);\n svg.setAttribute('viewBox', `0 0 ${baseWidth} ${baseHeight}`);\n svg.style.cssText = 'overflow: visible;';\n // Face outline (oval)\n const faceOutline = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');\n faceOutline.setAttribute('cx', '60');\n faceOutline.setAttribute('cy', '80');\n faceOutline.setAttribute('rx', '50');\n faceOutline.setAttribute('ry', '65');\n faceOutline.setAttribute('fill', 'none');\n faceOutline.setAttribute('stroke', '#888');\n faceOutline.setAttribute('stroke-width', '3');\n faceOutline.setAttribute('stroke-dasharray', '8,4');\n svg.appendChild(faceOutline);\n // Left eye socket\n this.leftEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.leftEyeElement.setAttribute('cx', '40');\n this.leftEyeElement.setAttribute('cy', '65');\n this.leftEyeElement.setAttribute('r', '12');\n this.leftEyeElement.setAttribute('fill', this.options.poorColor);\n this.leftEyeElement.setAttribute('stroke', '#fff');\n this.leftEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.leftEyeElement);\n // Right eye socket\n this.rightEyeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n this.rightEyeElement.setAttribute('cx', '80');\n this.rightEyeElement.setAttribute('cy', '65');\n this.rightEyeElement.setAttribute('r', '12');\n this.rightEyeElement.setAttribute('fill', this.options.poorColor);\n this.rightEyeElement.setAttribute('stroke', '#fff');\n this.rightEyeElement.setAttribute('stroke-width', '2');\n svg.appendChild(this.rightEyeElement);\n // Nose hint\n const nose = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n nose.setAttribute('d', 'M60,75 L55,95 L65,95 Z');\n nose.setAttribute('fill', 'none');\n nose.setAttribute('stroke', '#666');\n nose.setAttribute('stroke-width', '2');\n svg.appendChild(nose);\n this.faceOutlineElement.appendChild(svg);\n }\n /**\n * Update the display with new position data\n */\n updatePosition(positionData) {\n if (!positionData) {\n this.showNoData();\n return;\n }\n // Calculate average position from both eyes\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n // Update face position and scale\n this.updateFaceDisplay(avgX, avgY, avgZ, positionData);\n // Update distance bar\n if (this.options.showDistanceFeedback && avgZ !== null) {\n this.updateDistanceBar(avgZ);\n }\n // Update eye indicators\n this.updateEyeIndicators(positionData);\n // Update textual feedback\n this.updateTextualFeedback(avgX, avgY, avgZ);\n }\n getAveragePosition(left, right, leftValid, rightValid) {\n if (leftValid && rightValid && left !== null && right !== null) {\n return (left + right) / 2;\n }\n else if (leftValid && left !== null) {\n return left;\n }\n else if (rightValid && right !== null) {\n return right;\n }\n return null;\n }\n updateFaceDisplay(x, y, z, _positionData) {\n if (x === null || y === null) {\n this.faceOutlineElement.style.opacity = '0.3';\n return;\n }\n this.faceOutlineElement.style.opacity = '1';\n // Calculate position offset from center\n // x, y are 0-1 where 0.5 is center\n // Y axis is inverted: y=0 is bottom, y=1 is top, so we invert it for screen coordinates\n const offsetX = (x - 0.5) * this.BOX_WIDTH * 0.8;\n const offsetY = (0.5 - y) * this.BOX_HEIGHT * 0.8; // Inverted Y\n // Calculate scale based on distance (z)\n // z is 0-1 where ~0.5 is optimal\n // Based on track box: z=0 means at back (far), z=1 means at front (close)\n let scale = this.OPTIMAL_FACE_SCALE;\n if (z !== null) {\n // z=1 (close) -> MAX_FACE_SCALE, z=0.5 -> OPTIMAL_FACE_SCALE, z=0 (far) -> MIN_FACE_SCALE\n scale = this.MIN_FACE_SCALE + z * (this.MAX_FACE_SCALE - this.MIN_FACE_SCALE);\n scale = Math.max(this.MIN_FACE_SCALE, Math.min(this.MAX_FACE_SCALE, scale));\n }\n this.faceOutlineElement.style.transform = `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY}px)) scale(${scale})`;\n }\n updateDistanceBar(z) {\n if (!this.distanceBarFill)\n return;\n // z is 0-1, where z=1 is close (top of bar) and z=0 is far (bottom)\n // Fill from bottom, so height represents z directly (closeness)\n const fillPercent = z * 100;\n this.distanceBarFill.style.height = `${fillPercent}%`;\n // Color based on optimal range derived from distance thresholds\n const goodMin = 0.5 - this.options.distanceThresholdGood;\n const goodMax = 0.5 + this.options.distanceThresholdGood;\n const fairMin = 0.5 - this.options.distanceThresholdFair;\n const fairMax = 0.5 + this.options.distanceThresholdFair;\n let color;\n if (z >= goodMin && z <= goodMax) {\n color = this.options.goodColor;\n }\n else if (z >= fairMin && z <= fairMax) {\n color = this.options.fairColor;\n }\n else {\n color = this.options.poorColor;\n }\n this.distanceBarFill.style.background = color;\n }\n updateEyeIndicators(positionData) {\n // Update left eye color based on validity\n if (this.leftEyeElement) {\n const leftColor = positionData.leftValid ? this.options.goodColor : this.options.poorColor;\n this.leftEyeElement.setAttribute('fill', leftColor);\n }\n // Update right eye color based on validity\n if (this.rightEyeElement) {\n const rightColor = positionData.rightValid ? this.options.goodColor : this.options.poorColor;\n this.rightEyeElement.setAttribute('fill', rightColor);\n }\n }\n updateTextualFeedback(x, y, z) {\n if (!this.feedbackElement)\n return;\n if (x === null || y === null || z === null) {\n this.feedbackElement.textContent =\n 'Eyes not detected - please position yourself in front of the tracker';\n this.feedbackElement.style.color = this.options.poorColor;\n return;\n }\n const quality = this.assessPositionQuality(x, y, z);\n let feedback;\n let color;\n if (quality.isGoodPosition) {\n feedback = '✓ Position is good';\n color = this.options.goodColor;\n }\n else {\n const issues = [];\n // Horizontal feedback — use configurable threshold (offset from 0.5 center)\n const posFairThreshold = this.options.positionThresholdFair;\n if (x < 0.5 - posFairThreshold)\n issues.push('move right');\n else if (x > 0.5 + posFairThreshold)\n issues.push('move left');\n // Vertical feedback (y=0 is bottom, y=1 is top)\n if (y < 0.5 - posFairThreshold)\n issues.push('move up');\n else if (y > 0.5 + posFairThreshold)\n issues.push('move down');\n // Distance feedback (z=1 is close, z=0 is far)\n const distFairThreshold = this.options.distanceThresholdFair;\n if (z > 0.5 + distFairThreshold)\n issues.push('move back');\n else if (z < 0.5 - distFairThreshold)\n issues.push('move closer');\n if (issues.length > 0) {\n feedback = `Please ${issues.join(' and ')}`;\n }\n else {\n feedback = 'Position: Almost there...';\n }\n color =\n quality.distanceStatus === 'poor' ||\n quality.horizontalStatus === 'poor' ||\n quality.verticalStatus === 'poor'\n ? this.options.poorColor\n : this.options.fairColor;\n }\n this.feedbackElement.textContent = feedback;\n this.feedbackElement.style.color = color;\n }\n assessPositionQuality(x, y, z) {\n // Assess horizontal position (0.5 is center)\n const xOffset = Math.abs(x - 0.5);\n let horizontalStatus;\n if (xOffset < this.options.positionThresholdGood)\n horizontalStatus = 'good';\n else if (xOffset < this.options.positionThresholdFair)\n horizontalStatus = 'fair';\n else\n horizontalStatus = 'poor';\n // Assess vertical position (0.5 is center)\n const yOffset = Math.abs(y - 0.5);\n let verticalStatus;\n if (yOffset < this.options.positionThresholdGood)\n verticalStatus = 'good';\n else if (yOffset < this.options.positionThresholdFair)\n verticalStatus = 'fair';\n else\n verticalStatus = 'poor';\n // Assess distance (0.5 is optimal)\n const zOffset = Math.abs(z - 0.5);\n let distanceStatus;\n if (zOffset < this.options.distanceThresholdGood)\n distanceStatus = 'good';\n else if (zOffset < this.options.distanceThresholdFair)\n distanceStatus = 'fair';\n else\n distanceStatus = 'poor';\n const isGoodPosition = horizontalStatus === 'good' && verticalStatus === 'good' && distanceStatus === 'good';\n return {\n isGoodPosition,\n horizontalStatus,\n verticalStatus,\n distanceStatus,\n averageX: x,\n averageY: y,\n averageZ: z,\n };\n }\n showNoData() {\n if (this.feedbackElement) {\n this.feedbackElement.textContent = 'Waiting for tracker data...';\n this.feedbackElement.style.color = this.options.poorColor;\n }\n this.faceOutlineElement.style.opacity = '0.3';\n }\n /**\n * Get current position quality\n */\n getCurrentQuality(positionData) {\n if (!positionData) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: null,\n averageY: null,\n averageZ: null,\n };\n }\n const avgX = this.getAveragePosition(positionData.leftX, positionData.rightX, positionData.leftValid, positionData.rightValid);\n const avgY = this.getAveragePosition(positionData.leftY, positionData.rightY, positionData.leftValid, positionData.rightValid);\n const avgZ = this.getAveragePosition(positionData.leftZ, positionData.rightZ, positionData.leftValid, positionData.rightValid);\n if (avgX === null || avgY === null || avgZ === null) {\n return {\n isGoodPosition: false,\n horizontalStatus: 'poor',\n verticalStatus: 'poor',\n distanceStatus: 'poor',\n averageX: avgX,\n averageY: avgY,\n averageZ: avgZ,\n };\n }\n return this.assessPositionQuality(avgX, avgY, avgZ);\n }\n /**\n * Remove the display\n */\n destroy() {\n this.container.innerHTML = '';\n }\n}\n//# sourceMappingURL=position-display.js.map","/**\n * @title Tobii User Position\n * @description jsPsych plugin for Tobii eye tracker user position guide. Displays real-time\n * head position feedback to help participants maintain optimal positioning for eye tracking.\n * @version 1.0.0\n * @author jsPsych Team\n * @see {@link https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme Documentation}\n */\n\nimport { JsPsych, JsPsychPlugin, ParameterType, TrialType } from 'jspsych';\nimport { version } from '../package.json';\nimport type TobiiExtension from '@jspsych/extension-tobii';\nimport { PositionDisplay } from './position-display';\nimport type { PositionQuality } from './types';\n\nconst info = <const>{\n name: 'tobii-user-position',\n version: version,\n parameters: {\n /** Duration to show the position guide (ms), null for manual */\n duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Message to display */\n message: {\n type: ParameterType.STRING,\n default: 'Please position yourself so the indicators are green',\n },\n /** Update interval (ms) */\n update_interval: {\n type: ParameterType.INT,\n default: 100,\n },\n /** Show distance feedback */\n show_distance_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Show position feedback */\n show_position_feedback: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** Button text for manual continuation */\n button_text: {\n type: ParameterType.STRING,\n default: 'Continue',\n },\n /** Only show button when position is good */\n require_good_position: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Background color */\n background_color: {\n type: ParameterType.STRING,\n default: '#f0f0f0',\n },\n /** Good position color */\n good_color: {\n type: ParameterType.STRING,\n default: '#28a745',\n },\n /** Fair position color */\n fair_color: {\n type: ParameterType.STRING,\n default: '#ffc107',\n },\n /** Poor position color */\n poor_color: {\n type: ParameterType.STRING,\n default: '#dc3545',\n },\n /** Button color */\n button_color: {\n type: ParameterType.STRING,\n default: '#007bff',\n },\n /** Button hover color */\n button_hover_color: {\n type: ParameterType.STRING,\n default: '#0056b3',\n },\n /** Font size */\n font_size: {\n type: ParameterType.STRING,\n default: '18px',\n },\n /** Position offset threshold for \"good\" status (normalized, default 0.15) */\n position_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.15,\n },\n /** Position offset threshold for \"fair\" status (normalized, default 0.25) */\n position_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.25,\n },\n /** Distance offset threshold for \"good\" status (normalized, default 0.1) */\n distance_threshold_good: {\n type: ParameterType.FLOAT,\n default: 0.1,\n },\n /** Distance offset threshold for \"fair\" status (normalized, default 0.2) */\n distance_threshold_fair: {\n type: ParameterType.FLOAT,\n default: 0.2,\n },\n },\n data: {\n /** Average X position during trial */\n average_x: {\n type: ParameterType.FLOAT,\n },\n /** Average Y position during trial */\n average_y: {\n type: ParameterType.FLOAT,\n },\n /** Average Z position (distance) during trial */\n average_z: {\n type: ParameterType.FLOAT,\n },\n /** Whether position was good at end */\n position_good: {\n type: ParameterType.BOOL,\n },\n /** Horizontal position status */\n horizontal_status: {\n type: ParameterType.STRING,\n },\n /** Vertical position status */\n vertical_status: {\n type: ParameterType.STRING,\n },\n /** Distance status */\n distance_status: {\n type: ParameterType.STRING,\n },\n /** Duration of trial */\n rt: {\n type: ParameterType.INT,\n },\n },\n};\n\ntype Info = typeof info;\n\nclass TobiiUserPositionPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n private static removeStyles(): void {\n const el = document.getElementById('tobii-user-position-styles');\n if (el) {\n el.remove();\n }\n }\n\n private injectStyles(trial: TrialType<Info>): void {\n // Remove existing styles so each trial gets its own colors\n TobiiUserPositionPlugin.removeStyles();\n\n const css = `\n .tobii-user-position-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100vh;\n font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n font-size: ${trial.font_size};\n }\n\n .tobii-user-position-message {\n margin-bottom: 40px;\n text-align: center;\n font-weight: 500;\n color: #333;\n }\n\n .tobii-user-position-guide {\n position: relative;\n margin-bottom: 40px;\n }\n\n .tobii-position-feedback {\n text-align: center;\n margin-bottom: 30px;\n font-weight: 600;\n font-size: 1.1em;\n }\n\n .tobii-user-position-button {\n padding: 12px 32px;\n font-size: 16px;\n font-weight: 500;\n border: none;\n border-radius: 6px;\n background-color: ${trial.button_color};\n color: white;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n\n .tobii-user-position-button:hover:not(:disabled) {\n background-color: ${trial.button_hover_color};\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);\n transform: translateY(-1px);\n }\n\n .tobii-user-position-button:disabled {\n background-color: #ccc;\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n .tobii-center-marker {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 30px;\n height: 30px;\n border: 2px dashed #666;\n border-radius: 50%;\n opacity: 0.5;\n }\n `;\n\n const styleElement = document.createElement('style');\n styleElement.id = 'tobii-user-position-styles';\n styleElement.textContent = css;\n document.head.appendChild(styleElement);\n }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n return new Promise<void>((resolve) => {\n // Inject CSS\n this.injectStyles(trial);\n\n // Check for Tobii extension\n const tobiiExtension = this.jsPsych.extensions.tobii as unknown as TobiiExtension;\n if (!tobiiExtension) {\n throw new Error('Tobii extension not loaded');\n }\n\n // Create container\n display_element.innerHTML = `\n <div class=\"tobii-user-position-container\">\n </div>\n `;\n\n const container = display_element.querySelector(\n '.tobii-user-position-container'\n ) as HTMLElement;\n\n // Create position display\n const positionDisplay = new PositionDisplay(container, {\n message: trial.message!,\n showDistanceFeedback: trial.show_distance_feedback!,\n showPositionFeedback: trial.show_position_feedback!,\n backgroundColor: trial.background_color!,\n goodColor: trial.good_color!,\n fairColor: trial.fair_color!,\n poorColor: trial.poor_color!,\n fontSize: trial.font_size!,\n positionThresholdGood: trial.position_threshold_good!,\n positionThresholdFair: trial.position_threshold_fair!,\n distanceThresholdGood: trial.distance_threshold_good!,\n distanceThresholdFair: trial.distance_threshold_fair!,\n });\n\n // Add continue button if no duration specified\n let continueButton: HTMLButtonElement | null = null;\n if (trial.duration === null) {\n continueButton = document.createElement('button');\n continueButton.className = 'tobii-user-position-button';\n continueButton.textContent = trial.button_text!;\n if (trial.require_good_position) {\n continueButton.disabled = true;\n }\n container.appendChild(continueButton);\n }\n\n // Track position data\n const positionSamples: PositionQuality[] = [];\n const startTime = performance.now();\n\n // Update position display periodically\n const updateInterval = setInterval(async () => {\n try {\n const positionData = await tobiiExtension.getUserPosition();\n positionDisplay.updatePosition(positionData);\n\n // Track position quality\n const quality = positionDisplay.getCurrentQuality(positionData);\n positionSamples.push(quality);\n\n // Update button state if required\n if (continueButton && trial.require_good_position) {\n continueButton.disabled = !quality.isGoodPosition;\n }\n } catch (error) {\n console.error('Error updating user position:', error);\n }\n }, trial.update_interval!);\n\n // Cleanup helper to ensure DOM and styles are always cleaned up\n const cleanup = () => {\n clearInterval(updateInterval);\n positionDisplay.destroy();\n display_element.innerHTML = '';\n TobiiUserPositionPlugin.removeStyles();\n };\n\n // Handle trial end\n const endTrial = () => {\n // Calculate average position\n const validSamples = positionSamples.filter(\n (s) => s.averageX !== null && s.averageY !== null && s.averageZ !== null\n );\n\n let averageX = null;\n let averageY = null;\n let averageZ = null;\n let finalQuality: PositionQuality | null = null;\n\n if (validSamples.length > 0) {\n averageX = validSamples.reduce((sum, s) => sum + s.averageX!, 0) / validSamples.length;\n averageY = validSamples.reduce((sum, s) => sum + s.averageY!, 0) / validSamples.length;\n averageZ = validSamples.reduce((sum, s) => sum + s.averageZ!, 0) / validSamples.length;\n finalQuality = positionSamples[positionSamples.length - 1];\n }\n\n const trialData = {\n average_x: averageX,\n average_y: averageY,\n average_z: averageZ,\n position_good: finalQuality?.isGoodPosition ?? false,\n horizontal_status: finalQuality?.horizontalStatus ?? 'poor',\n vertical_status: finalQuality?.verticalStatus ?? 'poor',\n distance_status: finalQuality?.distanceStatus ?? 'poor',\n rt: Math.round(performance.now() - startTime),\n };\n\n cleanup();\n this.jsPsych.finishTrial(trialData);\n resolve();\n };\n\n // Set up continue button\n if (continueButton) {\n continueButton.addEventListener('click', endTrial);\n }\n\n // Set up duration timeout\n if (trial.duration != null) {\n this.jsPsych.pluginAPI.setTimeout(endTrial, trial.duration);\n }\n });\n }\n}\n\nexport default TobiiUserPositionPlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { JsPsychPlugin, ParameterType, JsPsych, TrialType } from 'jspsych';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @title Tobii User Position
|
|
5
|
+
* @description jsPsych plugin for Tobii eye tracker user position guide. Displays real-time
|
|
6
|
+
* head position feedback to help participants maintain optimal positioning for eye tracking.
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
* @author jsPsych Team
|
|
9
|
+
* @see {@link https://github.com/jspsych/jspsych-tobii/tree/main/packages/plugin-tobii-user-position#readme Documentation}
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
declare const info: {
|
|
13
|
+
readonly name: "tobii-user-position";
|
|
14
|
+
readonly version: string;
|
|
15
|
+
readonly parameters: {
|
|
16
|
+
/** Duration to show the position guide (ms), null for manual */
|
|
17
|
+
readonly duration: {
|
|
18
|
+
readonly type: ParameterType.INT;
|
|
19
|
+
readonly default: null;
|
|
20
|
+
};
|
|
21
|
+
/** Message to display */
|
|
22
|
+
readonly message: {
|
|
23
|
+
readonly type: ParameterType.STRING;
|
|
24
|
+
readonly default: "Please position yourself so the indicators are green";
|
|
25
|
+
};
|
|
26
|
+
/** Update interval (ms) */
|
|
27
|
+
readonly update_interval: {
|
|
28
|
+
readonly type: ParameterType.INT;
|
|
29
|
+
readonly default: 100;
|
|
30
|
+
};
|
|
31
|
+
/** Show distance feedback */
|
|
32
|
+
readonly show_distance_feedback: {
|
|
33
|
+
readonly type: ParameterType.BOOL;
|
|
34
|
+
readonly default: true;
|
|
35
|
+
};
|
|
36
|
+
/** Show position feedback */
|
|
37
|
+
readonly show_position_feedback: {
|
|
38
|
+
readonly type: ParameterType.BOOL;
|
|
39
|
+
readonly default: true;
|
|
40
|
+
};
|
|
41
|
+
/** Button text for manual continuation */
|
|
42
|
+
readonly button_text: {
|
|
43
|
+
readonly type: ParameterType.STRING;
|
|
44
|
+
readonly default: "Continue";
|
|
45
|
+
};
|
|
46
|
+
/** Only show button when position is good */
|
|
47
|
+
readonly require_good_position: {
|
|
48
|
+
readonly type: ParameterType.BOOL;
|
|
49
|
+
readonly default: false;
|
|
50
|
+
};
|
|
51
|
+
/** Background color */
|
|
52
|
+
readonly background_color: {
|
|
53
|
+
readonly type: ParameterType.STRING;
|
|
54
|
+
readonly default: "#f0f0f0";
|
|
55
|
+
};
|
|
56
|
+
/** Good position color */
|
|
57
|
+
readonly good_color: {
|
|
58
|
+
readonly type: ParameterType.STRING;
|
|
59
|
+
readonly default: "#28a745";
|
|
60
|
+
};
|
|
61
|
+
/** Fair position color */
|
|
62
|
+
readonly fair_color: {
|
|
63
|
+
readonly type: ParameterType.STRING;
|
|
64
|
+
readonly default: "#ffc107";
|
|
65
|
+
};
|
|
66
|
+
/** Poor position color */
|
|
67
|
+
readonly poor_color: {
|
|
68
|
+
readonly type: ParameterType.STRING;
|
|
69
|
+
readonly default: "#dc3545";
|
|
70
|
+
};
|
|
71
|
+
/** Button color */
|
|
72
|
+
readonly button_color: {
|
|
73
|
+
readonly type: ParameterType.STRING;
|
|
74
|
+
readonly default: "#007bff";
|
|
75
|
+
};
|
|
76
|
+
/** Button hover color */
|
|
77
|
+
readonly button_hover_color: {
|
|
78
|
+
readonly type: ParameterType.STRING;
|
|
79
|
+
readonly default: "#0056b3";
|
|
80
|
+
};
|
|
81
|
+
/** Font size */
|
|
82
|
+
readonly font_size: {
|
|
83
|
+
readonly type: ParameterType.STRING;
|
|
84
|
+
readonly default: "18px";
|
|
85
|
+
};
|
|
86
|
+
/** Position offset threshold for "good" status (normalized, default 0.15) */
|
|
87
|
+
readonly position_threshold_good: {
|
|
88
|
+
readonly type: ParameterType.FLOAT;
|
|
89
|
+
readonly default: 0.15;
|
|
90
|
+
};
|
|
91
|
+
/** Position offset threshold for "fair" status (normalized, default 0.25) */
|
|
92
|
+
readonly position_threshold_fair: {
|
|
93
|
+
readonly type: ParameterType.FLOAT;
|
|
94
|
+
readonly default: 0.25;
|
|
95
|
+
};
|
|
96
|
+
/** Distance offset threshold for "good" status (normalized, default 0.1) */
|
|
97
|
+
readonly distance_threshold_good: {
|
|
98
|
+
readonly type: ParameterType.FLOAT;
|
|
99
|
+
readonly default: 0.1;
|
|
100
|
+
};
|
|
101
|
+
/** Distance offset threshold for "fair" status (normalized, default 0.2) */
|
|
102
|
+
readonly distance_threshold_fair: {
|
|
103
|
+
readonly type: ParameterType.FLOAT;
|
|
104
|
+
readonly default: 0.2;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
readonly data: {
|
|
108
|
+
/** Average X position during trial */
|
|
109
|
+
readonly average_x: {
|
|
110
|
+
readonly type: ParameterType.FLOAT;
|
|
111
|
+
};
|
|
112
|
+
/** Average Y position during trial */
|
|
113
|
+
readonly average_y: {
|
|
114
|
+
readonly type: ParameterType.FLOAT;
|
|
115
|
+
};
|
|
116
|
+
/** Average Z position (distance) during trial */
|
|
117
|
+
readonly average_z: {
|
|
118
|
+
readonly type: ParameterType.FLOAT;
|
|
119
|
+
};
|
|
120
|
+
/** Whether position was good at end */
|
|
121
|
+
readonly position_good: {
|
|
122
|
+
readonly type: ParameterType.BOOL;
|
|
123
|
+
};
|
|
124
|
+
/** Horizontal position status */
|
|
125
|
+
readonly horizontal_status: {
|
|
126
|
+
readonly type: ParameterType.STRING;
|
|
127
|
+
};
|
|
128
|
+
/** Vertical position status */
|
|
129
|
+
readonly vertical_status: {
|
|
130
|
+
readonly type: ParameterType.STRING;
|
|
131
|
+
};
|
|
132
|
+
/** Distance status */
|
|
133
|
+
readonly distance_status: {
|
|
134
|
+
readonly type: ParameterType.STRING;
|
|
135
|
+
};
|
|
136
|
+
/** Duration of trial */
|
|
137
|
+
readonly rt: {
|
|
138
|
+
readonly type: ParameterType.INT;
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
type Info = typeof info;
|
|
143
|
+
declare class TobiiUserPositionPlugin implements JsPsychPlugin<Info> {
|
|
144
|
+
private jsPsych;
|
|
145
|
+
static info: {
|
|
146
|
+
readonly name: "tobii-user-position";
|
|
147
|
+
readonly version: string;
|
|
148
|
+
readonly parameters: {
|
|
149
|
+
/** Duration to show the position guide (ms), null for manual */
|
|
150
|
+
readonly duration: {
|
|
151
|
+
readonly type: ParameterType.INT;
|
|
152
|
+
readonly default: null;
|
|
153
|
+
};
|
|
154
|
+
/** Message to display */
|
|
155
|
+
readonly message: {
|
|
156
|
+
readonly type: ParameterType.STRING;
|
|
157
|
+
readonly default: "Please position yourself so the indicators are green";
|
|
158
|
+
};
|
|
159
|
+
/** Update interval (ms) */
|
|
160
|
+
readonly update_interval: {
|
|
161
|
+
readonly type: ParameterType.INT;
|
|
162
|
+
readonly default: 100;
|
|
163
|
+
};
|
|
164
|
+
/** Show distance feedback */
|
|
165
|
+
readonly show_distance_feedback: {
|
|
166
|
+
readonly type: ParameterType.BOOL;
|
|
167
|
+
readonly default: true;
|
|
168
|
+
};
|
|
169
|
+
/** Show position feedback */
|
|
170
|
+
readonly show_position_feedback: {
|
|
171
|
+
readonly type: ParameterType.BOOL;
|
|
172
|
+
readonly default: true;
|
|
173
|
+
};
|
|
174
|
+
/** Button text for manual continuation */
|
|
175
|
+
readonly button_text: {
|
|
176
|
+
readonly type: ParameterType.STRING;
|
|
177
|
+
readonly default: "Continue";
|
|
178
|
+
};
|
|
179
|
+
/** Only show button when position is good */
|
|
180
|
+
readonly require_good_position: {
|
|
181
|
+
readonly type: ParameterType.BOOL;
|
|
182
|
+
readonly default: false;
|
|
183
|
+
};
|
|
184
|
+
/** Background color */
|
|
185
|
+
readonly background_color: {
|
|
186
|
+
readonly type: ParameterType.STRING;
|
|
187
|
+
readonly default: "#f0f0f0";
|
|
188
|
+
};
|
|
189
|
+
/** Good position color */
|
|
190
|
+
readonly good_color: {
|
|
191
|
+
readonly type: ParameterType.STRING;
|
|
192
|
+
readonly default: "#28a745";
|
|
193
|
+
};
|
|
194
|
+
/** Fair position color */
|
|
195
|
+
readonly fair_color: {
|
|
196
|
+
readonly type: ParameterType.STRING;
|
|
197
|
+
readonly default: "#ffc107";
|
|
198
|
+
};
|
|
199
|
+
/** Poor position color */
|
|
200
|
+
readonly poor_color: {
|
|
201
|
+
readonly type: ParameterType.STRING;
|
|
202
|
+
readonly default: "#dc3545";
|
|
203
|
+
};
|
|
204
|
+
/** Button color */
|
|
205
|
+
readonly button_color: {
|
|
206
|
+
readonly type: ParameterType.STRING;
|
|
207
|
+
readonly default: "#007bff";
|
|
208
|
+
};
|
|
209
|
+
/** Button hover color */
|
|
210
|
+
readonly button_hover_color: {
|
|
211
|
+
readonly type: ParameterType.STRING;
|
|
212
|
+
readonly default: "#0056b3";
|
|
213
|
+
};
|
|
214
|
+
/** Font size */
|
|
215
|
+
readonly font_size: {
|
|
216
|
+
readonly type: ParameterType.STRING;
|
|
217
|
+
readonly default: "18px";
|
|
218
|
+
};
|
|
219
|
+
/** Position offset threshold for "good" status (normalized, default 0.15) */
|
|
220
|
+
readonly position_threshold_good: {
|
|
221
|
+
readonly type: ParameterType.FLOAT;
|
|
222
|
+
readonly default: 0.15;
|
|
223
|
+
};
|
|
224
|
+
/** Position offset threshold for "fair" status (normalized, default 0.25) */
|
|
225
|
+
readonly position_threshold_fair: {
|
|
226
|
+
readonly type: ParameterType.FLOAT;
|
|
227
|
+
readonly default: 0.25;
|
|
228
|
+
};
|
|
229
|
+
/** Distance offset threshold for "good" status (normalized, default 0.1) */
|
|
230
|
+
readonly distance_threshold_good: {
|
|
231
|
+
readonly type: ParameterType.FLOAT;
|
|
232
|
+
readonly default: 0.1;
|
|
233
|
+
};
|
|
234
|
+
/** Distance offset threshold for "fair" status (normalized, default 0.2) */
|
|
235
|
+
readonly distance_threshold_fair: {
|
|
236
|
+
readonly type: ParameterType.FLOAT;
|
|
237
|
+
readonly default: 0.2;
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
readonly data: {
|
|
241
|
+
/** Average X position during trial */
|
|
242
|
+
readonly average_x: {
|
|
243
|
+
readonly type: ParameterType.FLOAT;
|
|
244
|
+
};
|
|
245
|
+
/** Average Y position during trial */
|
|
246
|
+
readonly average_y: {
|
|
247
|
+
readonly type: ParameterType.FLOAT;
|
|
248
|
+
};
|
|
249
|
+
/** Average Z position (distance) during trial */
|
|
250
|
+
readonly average_z: {
|
|
251
|
+
readonly type: ParameterType.FLOAT;
|
|
252
|
+
};
|
|
253
|
+
/** Whether position was good at end */
|
|
254
|
+
readonly position_good: {
|
|
255
|
+
readonly type: ParameterType.BOOL;
|
|
256
|
+
};
|
|
257
|
+
/** Horizontal position status */
|
|
258
|
+
readonly horizontal_status: {
|
|
259
|
+
readonly type: ParameterType.STRING;
|
|
260
|
+
};
|
|
261
|
+
/** Vertical position status */
|
|
262
|
+
readonly vertical_status: {
|
|
263
|
+
readonly type: ParameterType.STRING;
|
|
264
|
+
};
|
|
265
|
+
/** Distance status */
|
|
266
|
+
readonly distance_status: {
|
|
267
|
+
readonly type: ParameterType.STRING;
|
|
268
|
+
};
|
|
269
|
+
/** Duration of trial */
|
|
270
|
+
readonly rt: {
|
|
271
|
+
readonly type: ParameterType.INT;
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
constructor(jsPsych: JsPsych);
|
|
276
|
+
private static removeStyles;
|
|
277
|
+
private injectStyles;
|
|
278
|
+
trial(display_element: HTMLElement, trial: TrialType<Info>): Promise<void>;
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=index.d.ts.map
|
|
281
|
+
|
|
282
|
+
export { TobiiUserPositionPlugin as default };
|