From 3663b358302dca7113dace156644c78c8214ee0e Mon Sep 17 00:00:00 2001 From: Stefan Schreistetter Date: Mon, 7 Oct 2019 17:16:38 +0200 Subject: [PATCH] Added Fake Tracker Class. re-implemented Gaze decimator. --- GazeWebSocketServer/Program.cs | 33 ++++++++------------------------- TrackerBridge/DSP/GazeDecimator.cs | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TrackerBridge/DSP/GazeDecimatorFixedLength.cs | 93 --------------------------------------------------------------------------------------------- TrackerBridge/DSP/IGazeFilter.cs | 5 ++--- TrackerBridge/FakeTracker.cs | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TrackerBridge/GazeData.cs | 12 ++++++++++++ TrackerBridge/GazeDataProcessor.cs | 49 ++++++++++++++++++++++++++++++++----------------- TrackerBridge/IBridgeTracker.cs | 11 ++++++++++- TrackerBridge/TobiiEyeTracker.cs | 27 ++++++++++----------------- TrackerBridge/TrackerBridge.csproj | 6 +++++- 10 files changed, 236 insertions(+), 157 deletions(-) create mode 100644 TrackerBridge/DSP/GazeDecimator.cs delete mode 100644 TrackerBridge/DSP/GazeDecimatorFixedLength.cs create mode 100644 TrackerBridge/FakeTracker.cs diff --git a/GazeWebSocketServer/Program.cs b/GazeWebSocketServer/Program.cs index 51f01a3..da983f2 100644 --- a/GazeWebSocketServer/Program.cs +++ b/GazeWebSocketServer/Program.cs @@ -1,9 +1,6 @@ using System; using System.Threading.Tasks; using TrackerBridge; -using System.Windows.Forms; -using System.Drawing; -using System.Timers; using TrackerBridge.DSP; namespace GazeWebSocketServer @@ -11,16 +8,18 @@ namespace GazeWebSocketServer public class Program { private static GazeServer gazeServer; - private static GazeDecimatorFixedLength decimator = new GazeDecimatorFixedLength(1, 600); public static void Main(string[] args) { ConfigurationData.InitializeUnexistingWithDefaults(); ConfigurationData config = ConfigurationData.ParseToObject(); + IBridgeTracker tracker; if (args.Length > 0 && args[0] == "-d") { - Task.Run(() => SimulateGazeDataHz(600)); + tracker = new FakeTracker(600); + tracker.GazeDataAvailable += OnGazeDataAvailable; //TODO: Extend interface usage + //tracker.GazeDataProcessor.OutputFrequency = config.OutputFrequency; } else { @@ -36,25 +35,9 @@ namespace GazeWebSocketServer Console.ReadLine(); } - private static void SimulateGazeDataHz(Int32 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) - { - decimator.Input(data); - if(decimator.Output.HasValue) - gazeServer.Publish(data); - } - }; - timer.AutoReset = true; - timer.Start(); - } + - private static void OnGazeDataAvailable(TobiiEyeTracker sender, GazeData data) + private static void OnGazeDataAvailable(GazeData data, Object sender) { if (gazeServer != null && gazeServer.isRunning) { @@ -62,12 +45,12 @@ namespace GazeWebSocketServer } } - private static void OnConnectionTimeout(TobiiEyeTracker sender) + private static void OnConnectionTimeout(IBridgeTracker sender) { Console.WriteLine("Timeout while searching for trackers."); } - private static void OnConnectionEstablished(TobiiEyeTracker sender) + private static void OnConnectionEstablished(IBridgeTracker sender) { Console.WriteLine($"Tracker {sender.SerialNumber} connected."); ConfigurationData config = ConfigurationData.ParseToObject(); diff --git a/TrackerBridge/DSP/GazeDecimator.cs b/TrackerBridge/DSP/GazeDecimator.cs new file mode 100644 index 0000000..e7e4cb7 --- /dev/null +++ b/TrackerBridge/DSP/GazeDecimator.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Timers; + +namespace TrackerBridge.DSP +{ + class GazeDecimator : IGazeFilter + { + public event GazeDataHandler OutputAvailable; + + private Single inputFrequency; + private GazeData output; + private Single outputFrequency; + private Object outputLock = new Object(); + private Object valueCountLock = new Object(); + private Int64 valueCount; + + public Single InputFrequency { get => inputFrequency; set => inputFrequency = value; } + private Int64 ValueCount { + get { + lock (valueCountLock) + { + return valueCount; + } + } + set { + lock (valueCountLock) + { + valueCount = value; + } + } + } + + private GazeData Output { + get { + lock (outputLock) + { + return output; + } + } + set { + lock (outputLock) + { + output = value; + } + } + } + + public GazeDecimator(Single outputFrequency, Single inputFrequency) + { + this.outputFrequency = outputFrequency; + this.InputFrequency = inputFrequency; + Task.Run(() => TriggerOutput(outputFrequency)); + } + public void Input(GazeData input, Object sender = null) + { + if (ValueCount == 0) + { + Output = input; + } + else + { + //Cumulative moving average (CMA) -> https://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average + lock (outputLock) + { + output += (input - output) / (ValueCount + 1); + } + + } + ValueCount++; + } + + public void Reset() + { + ValueCount = 0; + } + + private void TriggerOutput(Single frequency) + { + Timer timer = new Timer(1000.0 / frequency); //TODO: Implement Dispose + timer.Elapsed += (Object sender, ElapsedEventArgs e) => + { + if (ValueCount > 0) + { + OutputAvailable?.Invoke(Output, this); + Reset(); + } + }; + timer.AutoReset = true; + timer.Start(); + } + } +} diff --git a/TrackerBridge/DSP/GazeDecimatorFixedLength.cs b/TrackerBridge/DSP/GazeDecimatorFixedLength.cs deleted file mode 100644 index 6c42673..0000000 --- a/TrackerBridge/DSP/GazeDecimatorFixedLength.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace TrackerBridge.DSP -{ - public class GazeDecimatorFixedLength : 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 event EventHandler OutputAvailable; - - 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 GazeDecimatorFixedLength(Single outputFrequency, Single inputFrequency) - { - this.outputFrequency = outputFrequency; - this.inputFrequency = inputFrequency; - Reset(); - } - - public void Input(GazeData input) - { - lock (inputLock) - { - currentValueIndex = (currentValueIndex + 1) % buffer.Length; - } - buffer[currentValueIndex] = input; - - if (currentValueIndex == buffer.Length-1) - { - GazeData sum = buffer[0]; - for (Int32 index = 1; index < buffer.Length; index++) - { - sum += buffer[index]; - } - sum /= buffer.Length; - - lock (outputLock) - { - output = sum; - } - } - - } - - public void Reset() - { - Int32 bufferLength = Convert.ToInt32(InputFrequency / OutputFrequency); - buffer = new GazeData[bufferLength]; - currentValueIndex = -1; - output = null; - } - } -} diff --git a/TrackerBridge/DSP/IGazeFilter.cs b/TrackerBridge/DSP/IGazeFilter.cs index 31e1fe0..c23c6e7 100644 --- a/TrackerBridge/DSP/IGazeFilter.cs +++ b/TrackerBridge/DSP/IGazeFilter.cs @@ -8,9 +8,8 @@ namespace TrackerBridge.DSP { interface IGazeFilter { - event EventHandler OutputAvailable; - GazeData? Output { get; } - void Input(GazeData input); + event GazeDataHandler OutputAvailable; + void Input(GazeData input, Object sender = null); void Reset(); } } diff --git a/TrackerBridge/FakeTracker.cs b/TrackerBridge/FakeTracker.cs new file mode 100644 index 0000000..344b94e --- /dev/null +++ b/TrackerBridge/FakeTracker.cs @@ -0,0 +1,61 @@ +using System; +using System.Drawing; +using System.Threading.Tasks; +using System.Timers; +using System.Windows.Forms; + +namespace TrackerBridge +{ + public class FakeTracker : IBridgeTracker + { + private event GazeDataHandler gazeDataAvailable; + public event GazeDataHandler GazeDataAvailable { + add { + gazeDataAvailable += value; + GazeDataProcessor.AddClient(value); + } + remove { + gazeDataAvailable -= value; + GazeDataProcessor.RemoveClient(value); + } + } + public event EventHandler TrackingFrequencyChanged; + + private Single trackerFrequency; + + public GazeDataHandler GazeDataAvailableMembers => gazeDataAvailable; + public String SerialNumber => throw new NotImplementedException(); + + public GazeDataProcessor GazeDataProcessor { get; } + + public FakeTracker(Single frequency) + { + trackerFrequency = frequency; + GazeDataProcessor = new GazeDataProcessor(this); + //GazeDataProcessor.OutputFrequency = 1; + Task.Run(() => SimulateGazeDataHz(frequency)); + } + + private void SimulateGazeDataHz(Single frequency) + { + System.Timers.Timer timer = new System.Timers.Timer(1000.0 / frequency); //TODO: Implement Dispose + timer.Elapsed += (Object sender, ElapsedEventArgs e) => + { + Point mousePosition = Control.MousePosition; + GazeData data = new GazeData(mousePosition.X, mousePosition.Y, mousePosition.X, mousePosition.Y, 0, 0); + GazeDataProcessor.Process(this, data); + }; + timer.AutoReset = true; + timer.Start(); + } + + public Single GetTrackingFrequency() + { + return trackerFrequency; + } + public void SetTrackingFrequency(Single value) + { + throw new NotImplementedException(); + } + } +} diff --git a/TrackerBridge/GazeData.cs b/TrackerBridge/GazeData.cs index bf52a61..de94dc2 100644 --- a/TrackerBridge/GazeData.cs +++ b/TrackerBridge/GazeData.cs @@ -44,6 +44,18 @@ namespace TrackerBridge return result; } + 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, diff --git a/TrackerBridge/GazeDataProcessor.cs b/TrackerBridge/GazeDataProcessor.cs index 19fa3b3..71ee8d3 100644 --- a/TrackerBridge/GazeDataProcessor.cs +++ b/TrackerBridge/GazeDataProcessor.cs @@ -11,8 +11,6 @@ namespace TrackerBridge { public class GazeDataProcessor { - public event EventHandler ProcessedGazeDataAvailable; - private readonly IGazeFilter[] gazeFilters; private readonly Single screenHeight; private readonly Single screenWidth; @@ -26,15 +24,37 @@ namespace TrackerBridge } } - public GazeDataProcessor(IBridgeTracker tracker) { tracker.TrackingFrequencyChanged += OnTrackingFrequencyChanged; screenHeight = Convert.ToSingle(SystemParameters.PrimaryScreenHeight); screenWidth = Convert.ToSingle(SystemParameters.PrimaryScreenWidth); gazeFilters = new IGazeFilter[1]; - gazeFilters[0] = new GazeDecimatorFixedLength(outputFrequency, tracker.GetTrackingFrequency()); + gazeFilters[0] = new GazeDecimator(60, tracker.GetTrackingFrequency()); //TODO: Refactor frequency passthrough + + //Chain filters + for(Int32 index= 0; index < gazeFilters.Length; index++) + { + if(index == gazeFilters.Length - 1) + { + gazeFilters[index].OutputAvailable += tracker.GazeDataAvailableMembers; + } + else + { + gazeFilters[index].OutputAvailable += gazeFilters[index + 1].Input; + } + } + } + + public void AddClient(GazeDataHandler gazeDataHandler) + { + gazeFilters[gazeFilters.Length - 1].OutputAvailable += gazeDataHandler; + } + public void RemoveClient(GazeDataHandler gazeDataHandler) + { + gazeFilters[gazeFilters.Length - 1].OutputAvailable -= gazeDataHandler; } + public void Initialize() { foreach (IGazeFilter filter in gazeFilters) @@ -42,8 +62,8 @@ namespace TrackerBridge filter.Reset(); } } - - public GazeData? Process(GazeDataEventArgs e) + + public void Process(Object sender, GazeDataEventArgs e) { GazeData data = new GazeData(e.LeftEye.GazePoint.PositionOnDisplayArea.X * screenWidth, e.LeftEye.GazePoint.PositionOnDisplayArea.Y * screenHeight, @@ -53,23 +73,18 @@ namespace TrackerBridge e.SystemTimeStamp); 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; + } + public void Process(Object sender, GazeData input) + { + gazeFilters[0].Input(input); } private void OnTrackingFrequencyChanged(Object sender, Single frequency) { foreach(IGazeFilter filter in gazeFilters) { - if(filter.GetType() == typeof(GazeDecimatorFixedLength)) + if(filter.GetType() == typeof(GazeDecimator)) { - GazeDecimatorFixedLength decimator = (GazeDecimatorFixedLength)filter; + GazeDecimator decimator = (GazeDecimator)filter; decimator.InputFrequency = frequency; } } diff --git a/TrackerBridge/IBridgeTracker.cs b/TrackerBridge/IBridgeTracker.cs index ee5f5b6..a2c2f14 100644 --- a/TrackerBridge/IBridgeTracker.cs +++ b/TrackerBridge/IBridgeTracker.cs @@ -6,10 +6,19 @@ using System.Threading.Tasks; namespace TrackerBridge { + public delegate void ConnectionEventHandler(IBridgeTracker sender); + public delegate void GazeDataHandler(GazeData data, Object sender); + public interface IBridgeTracker { + event GazeDataHandler GazeDataAvailable; event EventHandler TrackingFrequencyChanged; - void SetTrackingFrequency(Single value); + + GazeDataHandler GazeDataAvailableMembers { get; } + GazeDataProcessor GazeDataProcessor { get;} + String SerialNumber { get; } + Single GetTrackingFrequency(); + void SetTrackingFrequency(Single value); } } diff --git a/TrackerBridge/TobiiEyeTracker.cs b/TrackerBridge/TobiiEyeTracker.cs index 9f5d8a6..22269a4 100644 --- a/TrackerBridge/TobiiEyeTracker.cs +++ b/TrackerBridge/TobiiEyeTracker.cs @@ -9,8 +9,8 @@ namespace TrackerBridge { public class TobiiEyeTracker : IBridgeTracker { - public delegate void ConnectionEventHandler(TobiiEyeTracker sender); - public delegate void GazeDataHandler(TobiiEyeTracker sender, GazeData data); + //public delegate void ConnectionEventHandler(TobiiEyeTracker sender); + //public delegate void GazeDataHandler(TobiiEyeTracker sender, GazeData data); public event ConnectionEventHandler ConnectionEvent; public event ConnectionEventHandler ConnectionTimeout; @@ -19,10 +19,16 @@ namespace TrackerBridge private IEyeTracker eyeTracker = null; - public GazeDataProcessor GazeDataProcessor { get; set; } + public GazeDataProcessor GazeDataProcessor { get; } public Boolean IsConnected { get; private set; } = false; public String SerialNumber { get; private set; } = null; + public GazeDataHandler GazeDataAvailableMembers { + get { + return GazeDataAvailable; + } + } + public TobiiEyeTracker() { GazeDataProcessor = new GazeDataProcessor(this); @@ -62,10 +68,6 @@ namespace TrackerBridge eyeTracker.SetGazeOutputFrequency(value); } } - void IBridgeTracker.SetTrackingFrequency(Single value) - { - throw new NotImplementedException(); - } private void InitiateConnection() { @@ -103,22 +105,13 @@ namespace TrackerBridge ConnectionTimeout?.Invoke(this); } } - eyeTracker.GazeDataReceived += OnGazeDataReceived; + eyeTracker.GazeDataReceived += GazeDataProcessor.Process; eyeTracker.GazeOutputFrequencyChanged += OnGazeOutputFrequencyChanged; SerialNumber = eyeTracker.SerialNumber; SetTrackingFrequency(600f); ConnectionEvent?.Invoke(this); } - private void OnGazeDataReceived(object sender, GazeDataEventArgs e) - { - 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); diff --git a/TrackerBridge/TrackerBridge.csproj b/TrackerBridge/TrackerBridge.csproj index c0c5052..3ff6ee5 100644 --- a/TrackerBridge/TrackerBridge.csproj +++ b/TrackerBridge/TrackerBridge.csproj @@ -34,9 +34,12 @@ 4 + + + @@ -48,7 +51,8 @@ - + + -- libgit2 0.26.0