-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Expose an API to query if eye tracking is calibrated on HoloLens. (#1…
…1664) Adds the EyeCalibrationChecker helper class with a CalibrationStatus property which tracks whether eyes are present and calibrated. Events are fired when eyes become calibrated or uncalibrated.
- Loading branch information
1 parent
4427460
commit 04ad752
Showing
12 changed files
with
1,107 additions
and
301 deletions.
There are no files selected for viewing
963 changes: 662 additions & 301 deletions
963
UnityProjects/MRTKDevTemplate/Assets/Scenes/EyeGazeExample.unity
Large diffs are not rendered by default.
Oops, something went wrong.
92 changes: 92 additions & 0 deletions
92
UnityProjects/MRTKDevTemplate/Assets/Scripts/EyeCalibrationWarning.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using UnityEngine; | ||
using UnityEngine.Events; | ||
using Microsoft.MixedReality.Toolkit.Input; | ||
using TMPro; | ||
|
||
namespace Microsoft.MixedReality.Toolkit.Examples | ||
{ | ||
/// <summary> | ||
/// Checks whether eyes are calibrated and prompts a notification to encourage the user to calibrate. | ||
/// </summary> | ||
[AddComponentMenu("MRTK/Examples/Eye Calibration Notifier")] | ||
public class EyeCalibrationWarning : MonoBehaviour | ||
{ | ||
[Tooltip("The EyeCalibrationChecker used to notify the user about calibration status.")] | ||
[SerializeField] | ||
private EyeCalibrationChecker checker; | ||
|
||
/// <summary> | ||
/// The EyeCalibrationChecker used to notify the user about calibration status. | ||
/// </summary> | ||
public EyeCalibrationChecker Checker | ||
{ | ||
get => checker; | ||
set => checker = value; | ||
} | ||
|
||
[Tooltip("The TMP Text used to notify the user about calibration status.")] | ||
[SerializeField] | ||
private TMP_Text text; | ||
|
||
/// <summary> | ||
/// The TMP Text used to notify the user about calibration status. | ||
/// </summary> | ||
public TMP_Text Text | ||
{ | ||
get => text; | ||
set => text = value; | ||
} | ||
|
||
private void OnEnable() | ||
{ | ||
if (checker == null) | ||
{ | ||
checker = GetComponent<EyeCalibrationChecker>(); | ||
} | ||
|
||
if (text == null) | ||
{ | ||
text = GetComponent<TMP_Text>(); | ||
} | ||
|
||
if (checker != null) | ||
{ | ||
UpdateMessage(checker.CalibratedStatus); | ||
checker.CalibratedStatusChanged.AddListener(UpdateMessageFromEvent); | ||
} | ||
} | ||
|
||
private void UpdateMessageFromEvent(EyeCalibrationStatusEventArgs args) | ||
{ | ||
UpdateMessage(args.CalibratedStatus); | ||
} | ||
|
||
private void UpdateMessage(EyeCalibrationStatus status) | ||
{ | ||
string warning = "Eye Calibration Status: Not Calibrated. "; | ||
switch (status) | ||
{ | ||
case EyeCalibrationStatus.Unsupported: | ||
text.text = ""; | ||
return; | ||
case EyeCalibrationStatus.Calibrated: | ||
text.text = "Eye Calibration Status: Calibrated."; | ||
text.color = Color.green; | ||
return; | ||
case EyeCalibrationStatus.NotCalibrated: | ||
warning += "Please calibrate eye tracking."; | ||
break; | ||
case EyeCalibrationStatus.NotTracked: | ||
warning += "Please ensure this app is granted the appropriate permissions."; | ||
break; | ||
default: | ||
break; | ||
} | ||
text.text = warning; | ||
text.color = Color.red; | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
UnityProjects/MRTKDevTemplate/Assets/Scripts/EyeCalibrationWarning.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
71 changes: 71 additions & 0 deletions
71
com.microsoft.mrtk.input/Tests/Runtime/EyeCalibrationCheckerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using Microsoft.MixedReality.Toolkit.Core.Tests; | ||
using Microsoft.MixedReality.Toolkit.Input.Tests; | ||
using Microsoft.MixedReality.Toolkit.Input; | ||
using NUnit.Framework; | ||
using System.Collections; | ||
using System.Threading.Tasks; | ||
using UnityEditor; | ||
using UnityEngine; | ||
using UnityEngine.TestTools; | ||
using UnityEngine.UI; | ||
|
||
namespace Microsoft.MixedReality.Toolkit.UX.Runtime.Tests | ||
{ | ||
/// <summary> | ||
/// Tests for the EyeCalibrationChecker. | ||
/// </summary> | ||
public class EyeCalibrationCheckerTests : BaseRuntimeInputTests | ||
{ | ||
private bool isCalibrated; | ||
private EyeCalibrationStatus calibrationStatus; | ||
|
||
[UnityTest] | ||
public IEnumerator TestEyeCalibrationEvents() | ||
{ | ||
// Create an EyeCalibrationChecker and add event listeners | ||
GameObject testButton = new GameObject("EyeCalibrationChecker"); | ||
EyeCalibrationChecker checker = testButton.AddComponent<EyeCalibrationChecker>(); | ||
checker.Calibrated.AddListener(YesEyeCalibration); | ||
checker.NotCalibrated.AddListener(NoEyeCalibration); | ||
checker.CalibratedStatusChanged.AddListener(CalibrationEvent); | ||
yield return null; | ||
|
||
// Test whether the events fire when the status is changed | ||
isCalibrated = true; | ||
checker.EditorTestIsCalibrated = EyeCalibrationStatus.Calibrated; | ||
yield return null; | ||
checker.EditorTestIsCalibrated = EyeCalibrationStatus.NotCalibrated; | ||
yield return null; | ||
Assert.IsFalse(isCalibrated, "NotCalibrated event was not fired."); | ||
Assert.AreEqual(calibrationStatus, EyeCalibrationStatus.NotCalibrated, "CalibratedStatusChanged event was not fired."); | ||
yield return null; | ||
checker.EditorTestIsCalibrated = EyeCalibrationStatus.Calibrated; | ||
yield return null; | ||
Assert.IsTrue(isCalibrated, "Calibrated event was not fired."); | ||
Assert.AreEqual(calibrationStatus, EyeCalibrationStatus.Calibrated, "CalibratedStatusChanged event was not fired."); | ||
yield return null; | ||
|
||
checker.Calibrated.RemoveListener(NoEyeCalibration); | ||
checker.NotCalibrated.RemoveListener(YesEyeCalibration); | ||
checker.CalibratedStatusChanged.RemoveListener(CalibrationEvent); | ||
} | ||
|
||
private void CalibrationEvent(EyeCalibrationStatusEventArgs args) | ||
{ | ||
calibrationStatus = args.CalibratedStatus; | ||
} | ||
|
||
private void YesEyeCalibration() | ||
{ | ||
isCalibrated = true; | ||
} | ||
|
||
private void NoEyeCalibration() | ||
{ | ||
isCalibrated = false; | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
com.microsoft.mrtk.input/Tests/Runtime/EyeCalibrationCheckerTests.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
146 changes: 146 additions & 0 deletions
146
com.microsoft.mrtk.input/Utilities/EyeCalibration/EyeCalibrationChecker.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Runtime.InteropServices.WindowsRuntime; | ||
using UnityEngine; | ||
using UnityEngine.Events; | ||
#if WINDOWS_UWP | ||
using Windows.Perception; | ||
using Windows.Perception.People; | ||
using Windows.Perception.Spatial; | ||
using Windows.UI.Input.Spatial; | ||
#endif | ||
|
||
namespace Microsoft.MixedReality.Toolkit.Input | ||
{ | ||
/// <summary> | ||
/// A helper class used to check eye calibration status. Only for UWP Platforms. | ||
/// </summary> | ||
[AddComponentMenu("MRTK/Input/Eye Calibration Checker")] | ||
public class EyeCalibrationChecker : MonoBehaviour | ||
{ | ||
#region Serialized Fields | ||
|
||
/// <summary> | ||
/// For testing purposes, you can manually assign whether eyes are calibrated or not in editor. | ||
/// </summary> | ||
[field: SerializeField, Tooltip("For testing purposes, you can manually assign whether eyes are calibrated or not in editor.")] | ||
public EyeCalibrationStatus EditorTestIsCalibrated = EyeCalibrationStatus.Calibrated; | ||
|
||
#endregion Serialized Fields | ||
|
||
#region Private Fields | ||
|
||
private EyeCalibrationStatus calibrationStatus; | ||
|
||
/// <summary> | ||
/// Tracks whether eyes are present and calibrated. | ||
/// </summary> | ||
public EyeCalibrationStatus CalibratedStatus | ||
{ | ||
get => calibrationStatus; | ||
} | ||
|
||
private EyeCalibrationStatus prevCalibrationStatus; | ||
private const int maxPoseAgeInSeconds = 1; | ||
|
||
#endregion Private Fields | ||
|
||
#region Events | ||
|
||
[SerializeField] | ||
[Tooltip("Event fired when eye tracking is calibrated.")] | ||
private UnityEvent calibrated = new UnityEvent(); | ||
|
||
/// <summary> | ||
/// Event fired when eye tracking is calibrated. | ||
/// </summary> | ||
public UnityEvent Calibrated => calibrated; | ||
|
||
[SerializeField] | ||
[Tooltip("Event fired when eye tracking is not calibrated.")] | ||
private UnityEvent notCalibrated = new UnityEvent(); | ||
|
||
/// <summary> | ||
/// Event fired when eye tracking is not calibrated. | ||
/// </summary> | ||
public UnityEvent NotCalibrated => notCalibrated; | ||
|
||
[SerializeField] | ||
[Tooltip("Event fired whenever eye tracking status changes.")] | ||
private EyeCalibrationStatusEvent calibratedStatusChanged = new EyeCalibrationStatusEvent(); | ||
|
||
/// <summary> | ||
/// Event fired whenever eye tracking status changes. | ||
/// </summary> | ||
public EyeCalibrationStatusEvent CalibratedStatusChanged => calibratedStatusChanged; | ||
|
||
#endregion Events | ||
|
||
#region MonoBehaviour Functions | ||
private void Update() | ||
{ | ||
if (Application.isEditor) | ||
{ | ||
calibrationStatus = EditorTestIsCalibrated; | ||
} | ||
else | ||
{ | ||
calibrationStatus = CheckCalibrationStatus(); | ||
} | ||
|
||
if (prevCalibrationStatus != calibrationStatus) | ||
{ | ||
if (calibrationStatus == EyeCalibrationStatus.Calibrated) | ||
{ | ||
calibrated.Invoke(); | ||
} | ||
else if (calibrationStatus == EyeCalibrationStatus.NotCalibrated) | ||
{ | ||
notCalibrated.Invoke(); | ||
} | ||
calibratedStatusChanged.Invoke(new EyeCalibrationStatusEventArgs(calibrationStatus)); | ||
prevCalibrationStatus = calibrationStatus; | ||
} | ||
} | ||
#endregion MonoBehaviour Functions | ||
|
||
#region Private Functions | ||
|
||
private EyeCalibrationStatus CheckCalibrationStatus() | ||
{ | ||
#if WINDOWS_UWP | ||
if (MixedReality.OpenXR.PerceptionInterop.GetSceneCoordinateSystem(Pose.identity) is SpatialCoordinateSystem worldOrigin) | ||
{ | ||
SpatialPointerPose pointerPose = SpatialPointerPose.TryGetAtTimestamp(worldOrigin, PerceptionTimestampHelper.FromHistoricalTargetTime(DateTimeOffset.Now)); | ||
if (pointerPose != null) | ||
{ | ||
EyesPose eyes = pointerPose.Eyes; | ||
if (eyes != null) | ||
{ | ||
// If it's been longer than a second since the last perception snapshot, assume the information has expired. | ||
if ((DateTimeOffset.Now - eyes.UpdateTimestamp.TargetTime).TotalSeconds > maxPoseAgeInSeconds) | ||
{ | ||
return EyeCalibrationStatus.NotCalibrated; | ||
} | ||
else if (eyes.IsCalibrationValid) | ||
{ | ||
return EyeCalibrationStatus.Calibrated; | ||
} | ||
else | ||
{ | ||
return EyeCalibrationStatus.NotCalibrated; | ||
} | ||
} | ||
} | ||
} | ||
return EyeCalibrationStatus.NotTracked; | ||
#else | ||
return EyeCalibrationStatus.Unsupported; | ||
#endif // WINDOWS_UWP | ||
} | ||
|
||
#endregion Private Functions | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
com.microsoft.mrtk.input/Utilities/EyeCalibration/EyeCalibrationChecker.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
37 changes: 37 additions & 0 deletions
37
com.microsoft.mrtk.input/Utilities/EyeCalibration/EyeCalibrationStatus.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
namespace Microsoft.MixedReality.Toolkit.Input | ||
{ | ||
/// <summary> | ||
/// Used to track the current eye calibration status. | ||
/// </summary> | ||
public enum EyeCalibrationStatus | ||
{ | ||
/// <summary> | ||
/// The eye calibration status is not defined. | ||
/// </summary> | ||
Undefined, | ||
|
||
/// <summary> | ||
/// The eye calibration status could not be retrieved because this is an unsupported device. | ||
/// </summary> | ||
Unsupported, | ||
|
||
/// <summary> | ||
/// The eye calibration status could not be retrieved because eyes are not being tracked. | ||
/// This usually occurs when SpatialPointerPose's Eyes property is null. | ||
/// </summary> | ||
NotTracked, | ||
|
||
/// <summary> | ||
/// The eye calibration status was retrieved and eyes are not calibrated. | ||
/// </summary> | ||
NotCalibrated, | ||
|
||
/// <summary> | ||
/// The eye calibration status was retrieved and eyes are calibrated. | ||
/// </summary> | ||
Calibrated | ||
}; | ||
} |
Oops, something went wrong.