diff --git a/CodeMaid.config b/CodeMaid.config new file mode 100644 index 0000000..20aa96c --- /dev/null +++ b/CodeMaid.config @@ -0,0 +1,36 @@ + + + + +
+ + + + + + Constructors||6||Constructors + + + Properties||5||Properties + + + Enums||1||Enums + + + Destructors||7||Destructors + + + Delegates||2||Delegates + + + Fields||4||Fields + + + Interfaces||8||Interfaces + + + Events||3||Events + + + + \ No newline at end of file diff --git a/GazeWebSocketServer/App.config b/GazeWebSocketServer/App.config index 6bcc981..26b239c 100644 --- a/GazeWebSocketServer/App.config +++ b/GazeWebSocketServer/App.config @@ -3,6 +3,7 @@ + diff --git a/GazeWebSocketServer/ConfigurationData.cs b/GazeWebSocketServer/ConfigurationData.cs index 1fa58ba..335ce9d 100644 --- a/GazeWebSocketServer/ConfigurationData.cs +++ b/GazeWebSocketServer/ConfigurationData.cs @@ -11,6 +11,7 @@ namespace GazeWebSocketServer { public string TrackerSerialNumber { get; private set; } public float TrackerFrequency { get; private set; } + public float OutputFrequency { get; private set; } public int WebSocketPort { get; private set; } public string WebSocketEndpoint { get; private set; } public bool DebugFlag { get; private set; } @@ -21,6 +22,7 @@ namespace GazeWebSocketServer { TrackerSerialNumber = ConfigurationManager.AppSettings["TrackerSerialNumber"], TrackerFrequency = float.Parse(ConfigurationManager.AppSettings["TrackerFrequency_Hz"]), + OutputFrequency = float.Parse(ConfigurationManager.AppSettings["OutputFrequency_Hz"]), WebSocketPort = int.Parse(ConfigurationManager.AppSettings["WebSocketPort"]), WebSocketEndpoint = ConfigurationManager.AppSettings["WebSocketEndpoint"], DebugFlag = bool.Parse(ConfigurationManager.AppSettings["DebugFlag"]) @@ -32,6 +34,7 @@ namespace GazeWebSocketServer { InitializeSetting("TrackerSerialNumber", "TPSP1-"); InitializeSetting("TrackerFrequency_Hz", "600"); + InitializeSetting("OutputFrequency_Hz", "60"); InitializeSetting("WebSocketPort", "8001"); InitializeSetting("WebSocketEndpoint", "/gaze"); InitializeSetting("DebugFlag", "false"); diff --git a/GazeWebSocketServer/Program.cs b/GazeWebSocketServer/Program.cs index 3c74670..be567ae 100644 --- a/GazeWebSocketServer/Program.cs +++ b/GazeWebSocketServer/Program.cs @@ -4,12 +4,15 @@ using TrackerBridge; using System.Windows.Forms; using System.Drawing; using System.Timers; +using TrackerBridge.DSP; namespace GazeWebSocketServer { public class Program { private static GazeServer gazeServer; + private static GazeDecimator decimator = new GazeDecimator(1, 600); + public static void Main(string[] args) { ConfigurationData.InitializeUnexistingWithDefaults(); @@ -17,7 +20,7 @@ namespace GazeWebSocketServer if (args.Length > 0 && args[0] == "-d") { - Task.Run(() => SimulateGazeDataHz(120)); + Task.Run(() => SimulateGazeDataHz(10)); } else { @@ -35,14 +38,16 @@ namespace GazeWebSocketServer private static void SimulateGazeDataHz(Int32 frequency) { - System.Timers.Timer timer = new System.Timers.Timer(1.0/frequency); + System.Timers.Timer timer = new System.Timers.Timer(1000.0/frequency); timer.Elapsed += (Object sender, ElapsedEventArgs e) => { Point mousePosition = Control.MousePosition; GazeData data = new GazeData(mousePosition.X, mousePosition.Y, mousePosition.X, mousePosition.Y, 0, 0); if (gazeServer != null && gazeServer.isRunning) { - gazeServer.Publish(data); + decimator.Input(data); + if(decimator.Output.HasValue) + gazeServer.Publish(data); } }; timer.AutoReset = true; diff --git a/TrackerBridge/DSP/GazeDecimator.cs b/TrackerBridge/DSP/GazeDecimator.cs new file mode 100644 index 0000000..39eca61 --- /dev/null +++ b/TrackerBridge/DSP/GazeDecimator.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TrackerBridge.DSP +{ + public class GazeDecimator : IGazeFilter + { + private GazeData[] buffer; + private Int32 currentValueIndex; + private Single inputFrequency; + private GazeData? output; + private Single outputFrequency; + + private Object outputLock = new Object(); + private Object inputLock = new Object(); + + public Single InputFrequency { + get { return inputFrequency; } + set { + inputFrequency = value; + Reset(); + } + } + public GazeData? Output { + get { + lock (outputLock) + { + GazeData? temp = null; + if (output.HasValue) + { + temp = output.Value; + output = null; + + } + return temp; + } + } + } + public Single OutputFrequency { + get { return outputFrequency; } + set { + outputFrequency = value; + Reset(); + } + } + + public GazeDecimator(Single outputFrequency, Single inputFrequency) + { + this.outputFrequency = outputFrequency; + this.inputFrequency = inputFrequency; + Reset(); + } + + public void Input(GazeData input) + { + lock (inputLock) + { + buffer[currentValueIndex] = input; + currentValueIndex = (currentValueIndex + 1) % buffer.Length; + + if (currentValueIndex == 0) + { + GazeData sum = buffer[0]; + for (Int32 index = 1; index < buffer.Length; index++) + { + sum += buffer[index]; + } + lock (outputLock) + { + output = sum / buffer.Length; + } + } + } + } + + public void Reset() + { + Int32 bufferLength = Convert.ToInt32(InputFrequency / OutputFrequency); + buffer = new GazeData[bufferLength]; + currentValueIndex = 0; + output = null; + } + } +} diff --git a/TrackerBridge/DSP/IGazeFilter.cs b/TrackerBridge/DSP/IGazeFilter.cs new file mode 100644 index 0000000..c383b77 --- /dev/null +++ b/TrackerBridge/DSP/IGazeFilter.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TrackerBridge.DSP +{ + interface IGazeFilter + { + GazeData? Output { get; } + void Input(GazeData input); + void Reset(); + } +} diff --git a/TrackerBridge/GazeData.cs b/TrackerBridge/GazeData.cs index 152a70b..bf52a61 100644 --- a/TrackerBridge/GazeData.cs +++ b/TrackerBridge/GazeData.cs @@ -31,5 +31,29 @@ namespace TrackerBridge FormattableString message = $"{leftX};{leftY};{rightX};{rightY};{trackerTimeStamp};{systemTimeStamp}"; return FormattableString.Invariant(message); } + + public static GazeData operator +(GazeData a, GazeData b) + { + GazeData result = new GazeData(a.leftX + b.leftX, + a.leftY + b.leftY, + a.rightX + b.rightX, + a.rightY + b.rightY, + Math.Abs(a.trackerTimeStamp - b.trackerTimeStamp), + Math.Abs(a.systemTimeStamp - b.systemTimeStamp)); + + return result; + } + + public static GazeData operator /(GazeData a, Single b) + { + GazeData result = new GazeData(a.leftX / b, + a.leftY / b, + a.rightX / b, + a.rightY / b, + a.trackerTimeStamp / Convert.ToInt64(b), + a.systemTimeStamp / Convert.ToInt64(b)); + + return result; + } } } diff --git a/TrackerBridge/GazeDataProcessor.cs b/TrackerBridge/GazeDataProcessor.cs index 2817f09..b67088f 100644 --- a/TrackerBridge/GazeDataProcessor.cs +++ b/TrackerBridge/GazeDataProcessor.cs @@ -5,21 +5,44 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Tobii.Research; +using TrackerBridge.DSP; namespace TrackerBridge { public class GazeDataProcessor { - readonly float screenHeight; - readonly float screenWidth; + public event EventHandler ProcessedGazeDataAvailable; - public GazeDataProcessor() + private readonly IGazeFilter[] gazeFilters; + private readonly Single screenHeight; + private readonly Single screenWidth; + private Single outputFrequency; + + public Single OutputFrequency { + get { return outputFrequency; } + set { + outputFrequency = value; + Initialize(); + } + } + + public GazeDataProcessor(IBridgeTracker tracker) { + tracker.TrackingFrequencyChanged += OnTrackingFrequencyChanged; screenHeight = Convert.ToSingle(SystemParameters.PrimaryScreenHeight); screenWidth = Convert.ToSingle(SystemParameters.PrimaryScreenWidth); + gazeFilters = new IGazeFilter[1]; + gazeFilters[0] = new GazeDecimator(outputFrequency, tracker.GetTrackingFrequency()); + } + public void Initialize() + { + foreach (IGazeFilter filter in gazeFilters) + { + filter.Reset(); + } } - public GazeData Extract(GazeDataEventArgs e) + public GazeData? Process(GazeDataEventArgs e) { GazeData data = new GazeData(e.LeftEye.GazePoint.PositionOnDisplayArea.X * screenWidth, e.LeftEye.GazePoint.PositionOnDisplayArea.Y * screenHeight, @@ -27,7 +50,28 @@ namespace TrackerBridge e.RightEye.GazePoint.PositionOnDisplayArea.Y * screenHeight, e.DeviceTimeStamp, e.SystemTimeStamp); - return data; + + gazeFilters[0].Input(data); + for (Int32 index = 1; index < gazeFilters.Length; index++) + { + if (gazeFilters[index - 1].Output.HasValue) + { + gazeFilters[index].Input(gazeFilters[index - 1].Output.Value); + } + } + //TODO: If output, invoke event + return gazeFilters[gazeFilters.Length - 1].Output; + } + private void OnTrackingFrequencyChanged(Object sender, Single frequency) + { + foreach(IGazeFilter filter in gazeFilters) + { + if(filter.GetType() == typeof(GazeDecimator)) + { + GazeDecimator decimator = (GazeDecimator)filter; + decimator.InputFrequency = frequency; + } + } } } } diff --git a/TrackerBridge/IBridgeTracker.cs b/TrackerBridge/IBridgeTracker.cs new file mode 100644 index 0000000..ee5f5b6 --- /dev/null +++ b/TrackerBridge/IBridgeTracker.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TrackerBridge +{ + public interface IBridgeTracker + { + event EventHandler TrackingFrequencyChanged; + void SetTrackingFrequency(Single value); + Single GetTrackingFrequency(); + } +} diff --git a/TrackerBridge/TobiiEyeTracker.cs b/TrackerBridge/TobiiEyeTracker.cs index c7cf92b..9f5d8a6 100644 --- a/TrackerBridge/TobiiEyeTracker.cs +++ b/TrackerBridge/TobiiEyeTracker.cs @@ -7,32 +7,38 @@ using Tobii.Research; namespace TrackerBridge { - public class TobiiEyeTracker + public class TobiiEyeTracker : IBridgeTracker { public delegate void ConnectionEventHandler(TobiiEyeTracker sender); public delegate void GazeDataHandler(TobiiEyeTracker sender, GazeData data); + public event ConnectionEventHandler ConnectionEvent; public event ConnectionEventHandler ConnectionTimeout; public event GazeDataHandler GazeDataAvailable; - - public Boolean IsConnected { get; private set; } - public String SerialNumber { get => serialNumber; } + public event EventHandler TrackingFrequencyChanged; private IEyeTracker eyeTracker = null; - private String serialNumber = null; - private GazeDataProcessor gazeDataProcessor; + + public GazeDataProcessor GazeDataProcessor { get; set; } + public Boolean IsConnected { get; private set; } = false; + public String SerialNumber { get; private set; } = null; public TobiiEyeTracker() { - gazeDataProcessor = new GazeDataProcessor(); + GazeDataProcessor = new GazeDataProcessor(this); } public TobiiEyeTracker(String serialNumber) : this() { - this.serialNumber = serialNumber; + SerialNumber = serialNumber; + } + public void Connect() + { + Task task = new Task(() => InitiateConnection()); + task.Start(); } - public float GetTrackingFrequency() + public Single GetTrackingFrequency() { if (eyeTracker != null) { @@ -43,18 +49,22 @@ namespace TrackerBridge throw new NullReferenceException(); } } + + Single IBridgeTracker.GetTrackingFrequency() + { + throw new NotImplementedException(); + } + public void SetTrackingFrequency(float value) { - if (eyeTracker != null) + if (eyeTracker != null && eyeTracker.GetAllGazeOutputFrequencies().Contains(value)) { eyeTracker.SetGazeOutputFrequency(value); } } - - public void Connect() + void IBridgeTracker.SetTrackingFrequency(Single value) { - Task task = new Task(() => InitiateConnection()); - task.Start(); + throw new NotImplementedException(); } private void InitiateConnection() @@ -65,7 +75,7 @@ namespace TrackerBridge EyeTrackerCollection eyeTrackers = EyeTrackingOperations.FindAllEyeTrackers(); if (eyeTrackers.Count > 0) { - if (serialNumber == null) + if (SerialNumber == null) { eyeTracker = eyeTrackers[0]; break; @@ -74,7 +84,7 @@ namespace TrackerBridge { foreach (IEyeTracker t in eyeTrackers) { - if (serialNumber == t.SerialNumber) + if (SerialNumber == t.SerialNumber) { eyeTracker = t; break; @@ -94,15 +104,24 @@ namespace TrackerBridge } } eyeTracker.GazeDataReceived += OnGazeDataReceived; - serialNumber = eyeTracker.SerialNumber; + eyeTracker.GazeOutputFrequencyChanged += OnGazeOutputFrequencyChanged; + SerialNumber = eyeTracker.SerialNumber; SetTrackingFrequency(600f); ConnectionEvent?.Invoke(this); } private void OnGazeDataReceived(object sender, GazeDataEventArgs e) { - GazeData gazeData = gazeDataProcessor.Extract(e); - GazeDataAvailable?.Invoke(this, gazeData); + GazeData? gazeData = GazeDataProcessor.Process(e); + if (gazeData.HasValue) + { + GazeDataAvailable?.Invoke(this, gazeData.Value); + } + } + + private void OnGazeOutputFrequencyChanged(Object sender, GazeOutputFrequencyEventArgs e) + { + TrackingFrequencyChanged?.Invoke(this, e.GazeOutputFrequency); } } } \ No newline at end of file diff --git a/TrackerBridge/TrackerBridge.csproj b/TrackerBridge/TrackerBridge.csproj index 2b99db3..53502a0 100644 --- a/TrackerBridge/TrackerBridge.csproj +++ b/TrackerBridge/TrackerBridge.csproj @@ -48,8 +48,11 @@ + + +