diff --git a/src/Baballonia/Models/SliderBindableSetting.cs b/src/Baballonia/Models/SliderBindableSetting.cs index ad4d8ff6..6ec5859d 100644 --- a/src/Baballonia/Models/SliderBindableSetting.cs +++ b/src/Baballonia/Models/SliderBindableSetting.cs @@ -1,4 +1,9 @@ using CommunityToolkit.Mvvm.ComponentModel; +using Baballonia.Contracts; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using Avalonia.Threading; namespace Baballonia.Models; @@ -18,7 +23,77 @@ public SliderBindableSetting(string name, float lower = 0f, float upper = 1f, fl Name = name; Lower = lower; Upper = upper; - Min = max; - Max = min; + Min = min; + Max = max; + } +} + +public partial class ParameterGroupCollection( + string groupName, + IFilterSettings filterSettings, + IEnumerable items) + : ObservableCollection(items) +{ + public string GroupName { get; } = groupName; + public IFilterSettings FilterSettings { get; } = filterSettings; +} + +public interface IFilterSettings : INotifyPropertyChanged +{ + bool Enabled { get; set; } + float MinFreqCutoff { get; set; } + float SpeedCutoff { get; set; } +} + +public partial class GroupFilterSettings : ObservableObject, IFilterSettings +{ + private readonly ILocalSettingsService _localSettingsService; + private readonly string _prefix; + + [ObservableProperty] + private bool _enabled; + + [ObservableProperty] + private float _minFreqCutoff; + + [ObservableProperty] + private float _speedCutoff; + + public GroupFilterSettings(ILocalSettingsService localSettingsService, string settingPrefix, + bool defaultEnabled, float defaultMinFreqCutoff, float defaultSpeedCutoff) + { + _localSettingsService = localSettingsService; + _prefix = settingPrefix; + + Enabled = defaultEnabled; + MinFreqCutoff = defaultMinFreqCutoff; + SpeedCutoff = defaultSpeedCutoff; + + var enabled = _localSettingsService.ReadSetting($"{_prefix}_Enabled", defaultEnabled); + var min = _localSettingsService.ReadSetting($"{_prefix}_MinFreq", defaultMinFreqCutoff); + var speed = _localSettingsService.ReadSetting($"{_prefix}_Speed", defaultSpeedCutoff); + + Dispatcher.UIThread.Post(() => + { + Enabled = enabled; + MinFreqCutoff = min; + SpeedCutoff = speed; + }); + + PropertyChanged += (_, e) => + { + switch (e.PropertyName) + { + case nameof(Enabled): + _localSettingsService.SaveSetting($"{_prefix}_Enabled", Enabled); + break; + case nameof(MinFreqCutoff): + _localSettingsService.SaveSetting($"{_prefix}_MinFreq", MinFreqCutoff); + break; + case nameof(SpeedCutoff): + _localSettingsService.SaveSetting($"{_prefix}_Speed", SpeedCutoff); + break; + } + }; } } diff --git a/src/Baballonia/Services/EyePipelineManager.cs b/src/Baballonia/Services/EyePipelineManager.cs index 68899e93..0d6c16fc 100644 --- a/src/Baballonia/Services/EyePipelineManager.cs +++ b/src/Baballonia/Services/EyePipelineManager.cs @@ -108,13 +108,7 @@ public void LoadFilter() if (!enabled) return; - var eyeArray = new float[Utils.EyeRawExpressions]; - var eyeFilter = new OneEuroFilter( - eyeArray, - minCutoff: cutoff, - beta: speedCutoff - ); - + var eyeFilter = new GroupedOneEuroFilter(); _pipeline.Filter = eyeFilter; } diff --git a/src/Baballonia/Services/Inference/FacePipelineManager.cs b/src/Baballonia/Services/Inference/FacePipelineManager.cs index 24cf7d74..b2a65ee5 100644 --- a/src/Baballonia/Services/Inference/FacePipelineManager.cs +++ b/src/Baballonia/Services/Inference/FacePipelineManager.cs @@ -69,13 +69,7 @@ public void LoadFilter() if (!enabled) return; - var faceArray = new float[Utils.FaceRawExpressions]; - var faceFilter = new OneEuroFilter( - faceArray, - minCutoff: cutoff, - beta: speedCutoff - ); - + var faceFilter = new GroupedOneEuroFilter(); _pipeline.Filter = faceFilter; } diff --git a/src/Baballonia/Services/Inference/Filters/OneEuroFilter.cs b/src/Baballonia/Services/Inference/Filters/OneEuroFilter.cs index 1477b066..f6282bc8 100644 --- a/src/Baballonia/Services/Inference/Filters/OneEuroFilter.cs +++ b/src/Baballonia/Services/Inference/Filters/OneEuroFilter.cs @@ -3,100 +3,109 @@ namespace Baballonia.Services.Inference.Filters; -public class OneEuroFilter : IFilter +public class GroupedOneEuroFilter : IFilter { - private float[] minCutoff; - private float[] beta; - private float[] dCutoff; - private float[] xPrev; - private float[] dxPrev; - private DateTime tPrev; - public OneEuroFilter(float[] x0, float minCutoff = 1.0f, float beta = 0.0f) + private sealed class GroupState { - float dx0 = 0.0f; - float dCutoff = 1.0f; - int length = x0.Length; - this.minCutoff = CreateFilledArray(length, minCutoff); - this.beta = CreateFilledArray(length, beta); - this.dCutoff = CreateFilledArray(length, dCutoff); - // Previous values. - this.xPrev = (float[])x0.Clone(); - this.dxPrev = CreateFilledArray(length, dx0); - this.tPrev = DateTime.UtcNow; + public int[] Indices = Array.Empty(); + public float[] XPrev = Array.Empty(); + public float[] DxPrev = Array.Empty(); + public float MinCutoff; + public float Beta; + public float DCutoff = 1.0f; + public DateTime TPrev; + public bool Initialized; } - public float[] Filter(float[] x) - { - if (x.Length != xPrev.Length) - throw new ArgumentException($"Input shape does not match initial shape. Expected: {xPrev.Length}, got: {x.Length}"); - - DateTime now = DateTime.UtcNow; - float elapsedTime = (float)(now - tPrev).TotalSeconds; - - if (elapsedTime == 0.0f) - { - xPrev = (float[])x.Clone(); - return x; - } - - float[] t_e = CreateFilledArray(x.Length, elapsedTime); + private readonly Dictionary _groups = new(); - // Derivative - float[] dx = new float[x.Length]; - for (int i = 0; i < x.Length; i++) - { - dx[i] = (x[i] - xPrev[i]) / t_e[i]; - } - - float[] a_d = SmoothingFactor(t_e, dCutoff); - float[] dxHat = ExponentialSmoothing(a_d, dx, dxPrev); + public void ConfigureGroup(string groupName, int[] parameterIndices, float minCutoff, float beta) + { + if (parameterIndices.Length == 0) + return; - // Adjusted cutoff - float[] cutoff = new float[x.Length]; - for (int i = 0; i < x.Length; i++) + var state = new GroupState { - cutoff[i] = minCutoff[i] + beta[i] * Math.Abs(dxHat[i]); - } - - float[] a = SmoothingFactor(t_e, cutoff); - float[] xHat = ExponentialSmoothing(a, x, xPrev); - - // Store previous values - xPrev = xHat; - dxPrev = dxHat; - tPrev = now; - - return xHat; + Indices = (int[])parameterIndices.Clone(), + XPrev = new float[parameterIndices.Length], + DxPrev = new float[parameterIndices.Length], + MinCutoff = Math.Max(0.001f, minCutoff), + Beta = Math.Max(0f, beta), + TPrev = DateTime.UtcNow, + Initialized = false + }; + + _groups[groupName] = state; } - private float[] CreateFilledArray(int length, float value) + public void DisableGroup(string groupName) { - float[] arr = new float[length]; - for (int i = 0; i < length; i++) arr[i] = value; - return arr; + _groups.Remove(groupName); } - private float[] SmoothingFactor(float[] t_e, float[] cutoff) + public float[] Filter(float[] input) { - int length = t_e.Length; - float[] result = new float[length]; - for (int i = 0; i < length; i++) + if (_groups.Count == 0) + return input; + + var now = DateTime.UtcNow; + float[] result = (float[])input.Clone(); + + foreach (var kvp in _groups) { - float r = 2 * (float)Math.PI * cutoff[i] * t_e[i]; - result[i] = r / (r + 1); + var state = kvp.Value; + if (state.Indices.Length == 0) + continue; + + int n = state.Indices.Length; + float[] x = new float[n]; + var indices = state.Indices; + for (int i = 0; i < n; i++) + { + x[i] = input[indices[i]]; + } + + float dt = (float)(now - state.TPrev).TotalSeconds; + if (!state.Initialized || dt <= 0f) + { + for (int i = 0; i < n; i++) + state.XPrev[i] = x[i]; + state.TPrev = now; + state.Initialized = true; + continue; + } + + // dx = (x - xPrev) / dt + for (int i = 0; i < n; i++) + { + state.DxPrev[i] = OneEuroSmooth(state.DCutoff, dt, (x[i] - state.XPrev[i]) / dt, state.DxPrev[i]); + } + + // cutoff = minCutoff + beta * |dxHat| + for (int i = 0; i < n; i++) + { + float cutoff = state.MinCutoff + state.Beta * MathF.Abs(state.DxPrev[i]); + float a = SmoothingFactor(cutoff, dt); + float xHat = a * x[i] + (1f - a) * state.XPrev[i]; + state.XPrev[i] = xHat; + result[indices[i]] = xHat; + } + + state.TPrev = now; } + return result; } - private float[] ExponentialSmoothing(float[] a, float[] x, float[] xPrev) + private static float OneEuroSmooth(float cutoff, float dt, float value, float prev) { - int length = a.Length; - float[] result = new float[length]; - for (int i = 0; i < length; i++) - { - result[i] = a[i] * x[i] + (1 - a[i]) * xPrev[i]; - } - return result; + float a = SmoothingFactor(cutoff, dt); + return a * value + (1f - a) * prev; } + private static float SmoothingFactor(float cutoff, float dt) + { + float r = 2f * MathF.PI * cutoff * dt; + return r / (r + 1f); + } } diff --git a/src/Baballonia/Services/Inference/Platforms/PlatformSettings.cs b/src/Baballonia/Services/Inference/Platforms/PlatformSettings.cs index 5575b77e..f906e0e2 100644 --- a/src/Baballonia/Services/Inference/Platforms/PlatformSettings.cs +++ b/src/Baballonia/Services/Inference/Platforms/PlatformSettings.cs @@ -10,7 +10,7 @@ public class PlatformSettings( Size inputSize, InferenceSession session, DenseTensor tensor, - OneEuroFilter oneEuroFilter, + IFilter oneEuroFilter, float lastTime, string inputName, string modelName) @@ -19,7 +19,7 @@ public class PlatformSettings( public InferenceSession Session { get; } = session; public DenseTensor Tensor { get; } = tensor; - public OneEuroFilter Filter { get; } = oneEuroFilter; + public IFilter Filter { get; } = oneEuroFilter; public float LastTime { get; set; } = lastTime; public string InputName { get; } = inputName; public string ModelName { get; } = modelName; diff --git a/src/Baballonia/ViewModels/SplitViewPane/AppSettingsViewModel.cs b/src/Baballonia/ViewModels/SplitViewPane/AppSettingsViewModel.cs index 47e3d22e..c1d3b084 100644 --- a/src/Baballonia/ViewModels/SplitViewPane/AppSettingsViewModel.cs +++ b/src/Baballonia/ViewModels/SplitViewPane/AppSettingsViewModel.cs @@ -37,18 +37,6 @@ public partial class AppSettingsViewModel : ViewModelBase [property: SavedSetting("AppSettings_OSCPrefix", "")] private string _oscPrefix; - [ObservableProperty] - [property: SavedSetting("AppSettings_OneEuroEnabled", true)] - private bool _oneEuroMinEnabled; - - [ObservableProperty] - [property: SavedSetting("AppSettings_OneEuroMinFreqCutoff", 1f)] - private float _oneEuroMinFreqCutoff; - - [ObservableProperty] - [property: SavedSetting("AppSettings_OneEuroSpeedCutoff", 1f)] - private float _oneEuroSpeedCutoff; - [ObservableProperty] [property: SavedSetting("AppSettings_UseGPU", true)] private bool _useGPU; diff --git a/src/Baballonia/ViewModels/SplitViewPane/CalibrationViewModel.cs b/src/Baballonia/ViewModels/SplitViewPane/CalibrationViewModel.cs index c12a7a78..f514a645 100644 --- a/src/Baballonia/ViewModels/SplitViewPane/CalibrationViewModel.cs +++ b/src/Baballonia/ViewModels/SplitViewPane/CalibrationViewModel.cs @@ -10,18 +10,20 @@ using Avalonia.Threading; using Baballonia.Helpers; using Baballonia.Services; +using Baballonia.Services.Inference.Filters; using CommunityToolkit.Mvvm.Input; namespace Baballonia.ViewModels.SplitViewPane; public partial class CalibrationViewModel : ViewModelBase, IDisposable { - public ObservableCollection EyeSettings { get; set; } - public ObservableCollection JawSettings { get; set; } - public ObservableCollection MouthSettings { get; set; } - public ObservableCollection TongueSettings { get; set; } - public ObservableCollection NoseSettings { get; set; } - public ObservableCollection CheekSettings { get; set; } + public ParameterGroupCollection EyeMovementSettings { get; set; } + public ParameterGroupCollection EyeBlinkingSettings { get; set; } + public ParameterGroupCollection JawSettings { get; set; } + public ParameterGroupCollection MouthSettings { get; set; } + public ParameterGroupCollection TongueSettings { get; set; } + public ParameterGroupCollection NoseSettings { get; set; } + public ParameterGroupCollection CheekSettings { get; set; } private ILocalSettingsService _settingsService { get; } private readonly ICalibrationService _calibrationService; @@ -39,41 +41,43 @@ public CalibrationViewModel(EyePipelineManager eyePipelineManager) _parameterSenderService = Ioc.Default.GetService()!; _processingLoopService = Ioc.Default.GetService()!; - EyeSettings = + EyeMovementSettings = new ParameterGroupCollection("EyeMovement", new GroupFilterSettings(_settingsService, "Filter_Gaze", false, 0.1f, 0.1f), + [ + new("LeftEyeX", -1f, 1f, -1f, 1f), + new("LeftEyeY", -1f, 1f, -1f, 1f), + new("RightEyeX", -1f, 1f, -1f, 1f), + new("RightEyeY", -1f, 1f, -1f, 1f) + ]); + + EyeBlinkingSettings = new ParameterGroupCollection("EyeBlinking", new GroupFilterSettings(_settingsService, "Filter_Expressions", false, 0.5f, 0.5f), [ new("LeftEyeLid"), - new("RightEyeLid"), - new ("LeftEyeWiden"), - new ("LeftEyeLower"), - new ("LeftEyeBrow"), - new ("RightEyeWiden"), - new ("RightEyeLower"), - new ("RightEyeBrow"), - ]; - - JawSettings = + new("RightEyeLid") + ]); + + JawSettings = new ParameterGroupCollection("Jaw", new GroupFilterSettings(_settingsService, "Filter_Expressions", false, 1.0f, 1.0f), [ new("JawOpen"), new("JawForward"), new("JawLeft"), new("JawRight") - ]; + ]); - CheekSettings = + CheekSettings = new ParameterGroupCollection("Cheek", new GroupFilterSettings(_settingsService, "Filter_Expressions", false, 1.0f, 1.0f), [ new("CheekPuffLeft"), new("CheekPuffRight"), new("CheekSuckLeft"), new("CheekSuckRight") - ]; + ]); - NoseSettings = + NoseSettings = new ParameterGroupCollection("Nose", new GroupFilterSettings(_settingsService, "Filter_Expressions", false, 1.0f, 1.0f), [ new("NoseSneerLeft"), new("NoseSneerRight") - ]; + ]); - MouthSettings = + MouthSettings = new ParameterGroupCollection("Mouth", new GroupFilterSettings(_settingsService, "Filter_Expressions", false, 1.0f, 1.0f), [ new("MouthFunnel"), new("MouthPucker"), @@ -98,9 +102,9 @@ public CalibrationViewModel(EyePipelineManager eyePipelineManager) new("MouthPressRight"), new("MouthStretchLeft"), new("MouthStretchRight") - ]; + ]); - TongueSettings = + TongueSettings = new ParameterGroupCollection("Tongue", new GroupFilterSettings(_settingsService, "Filter_Tongue", false, 1.0f, 1.0f), [ new("TongueOut"), new("TongueUp"), @@ -114,30 +118,20 @@ public CalibrationViewModel(EyePipelineManager eyePipelineManager) new("TongueFlat"), new("TongueTwistLeft"), new("TongueTwistRight") - ]; + ]); - foreach (var setting in EyeSettings.Concat(JawSettings).Concat(CheekSettings) + foreach (var setting in EyeMovementSettings.Concat(JawSettings).Concat(CheekSettings) .Concat(NoseSettings).Concat(MouthSettings).Concat(TongueSettings)) { setting.PropertyChanged += OnSettingChanged; } - // Convert dictionary order into index mapping - _eyeKeyIndexMap = new Dictionary + var allParameterGroups = new[] { EyeMovementSettings, EyeBlinkingSettings, JawSettings, + MouthSettings, TongueSettings, NoseSettings, CheekSettings }; + foreach (var group in allParameterGroups) { - { "LeftEyeX", 0 }, - { "LeftEyeY", 1 }, - { "LeftEyeLid", 2 }, - { "LeftEyeWiden", 3 }, - { "LeftEyeLower", 4 }, - { "LeftEyeBrow", 5 }, - { "RightEyeX", 6 }, - { "RightEyeY", 7 }, - { "RightEyeLid", 8 }, - { "RightEyeWiden", 9 }, - { "RightEyeLower", 10 }, - { "RightEyeBrow", 11 }, - }; + group.FilterSettings.PropertyChanged += OnFilterSettingChanged; + } _faceKeyIndexMap = _parameterSenderService.FaceExpressionMap.Keys .Select((key, index) => new { key, index }) @@ -157,6 +151,7 @@ public CalibrationViewModel(EyePipelineManager eyePipelineManager) _processingLoopService.ExpressionChangeEvent += ExpressionUpdateHandler; LoadInitialSettings(); + UpdateProcessingFilters(); _settingsService.Load(this); } @@ -174,7 +169,8 @@ private void ExpressionUpdateHandler(ProcessingLoopService.Expressions expressio if(expressions.EyeExpression != null) Dispatcher.UIThread.Post(() => { - ApplyCurrentEyeExpressionValues(expressions.EyeExpression, EyeSettings); + ApplyCurrentEyeExpressionValues(expressions.EyeExpression, EyeMovementSettings); + ApplyCurrentEyeExpressionValues(expressions.EyeExpression, EyeBlinkingSettings); }); } private void OnSettingChanged(object? sender, PropertyChangedEventArgs e) @@ -192,7 +188,7 @@ private void OnSettingChanged(object? sender, PropertyChangedEventArgs e) } } - private void ApplyCurrentEyeExpressionValues(float[] values, IEnumerable settings) + private void ApplyCurrentEyeExpressionValues(float[] values, ParameterGroupCollection settings) { foreach (var setting in settings) { @@ -209,7 +205,7 @@ private void ApplyCurrentEyeExpressionValues(float[] values, IEnumerable settings) + private void ApplyCurrentFaceExpressionValues(float[] values, ParameterGroupCollection settings) { foreach (var setting in settings) { @@ -242,7 +238,8 @@ public void ResetMaximums() private void LoadInitialSettings() { - LoadInitialSettings(EyeSettings); + LoadInitialSettings(EyeMovementSettings); + LoadInitialSettings(EyeBlinkingSettings); LoadInitialSettings(CheekSettings); LoadInitialSettings(JawSettings); LoadInitialSettings(MouthSettings); @@ -262,6 +259,70 @@ private void LoadInitialSettings(IEnumerable settings) } } + private void OnFilterSettingChanged(object? sender, PropertyChangedEventArgs e) + { + UpdateProcessingFilters(); + } + + private void UpdateProcessingFilters() + { + var faceGroupFilter = new GroupedOneEuroFilter(); + var eyeGroupFilter = new GroupedOneEuroFilter(); + + // Configure eye tracking filters + if (EyeMovementSettings.FilterSettings.Enabled) + { + var eyeMovementIndices = GetEyeParameterIndices(EyeMovementSettings); + if (eyeMovementIndices.Length > 0) + { + eyeGroupFilter.ConfigureGroup("EyeMovement", eyeMovementIndices, + EyeMovementSettings.FilterSettings.MinFreqCutoff, EyeMovementSettings.FilterSettings.SpeedCutoff); + } + } + + if (EyeBlinkingSettings.FilterSettings.Enabled) + { + var eyeBlinkingIndices = GetEyeParameterIndices(EyeBlinkingSettings); + if (eyeBlinkingIndices.Length > 0) + { + eyeGroupFilter.ConfigureGroup("EyeBlinking", eyeBlinkingIndices, + EyeBlinkingSettings.FilterSettings.MinFreqCutoff, EyeBlinkingSettings.FilterSettings.SpeedCutoff); + } + } + + // Configure face tracking filters + var faceParameterGroups = new[] { JawSettings, MouthSettings, TongueSettings, NoseSettings, CheekSettings }; + foreach (var group in faceParameterGroups) + { + if (group.FilterSettings.Enabled) + { + var indices = GetFaceParameterIndices(group); + if (indices.Length > 0) + { + faceGroupFilter.ConfigureGroup(group.GroupName, indices, + group.FilterSettings.MinFreqCutoff, group.FilterSettings.SpeedCutoff); + } + } + } + + //_processingLoopService.ExpressionChangeEvent.Filter = faceGroupFilter; + //_processingLoopService.EyesProcessingPipeline.Filter = eyeGroupFilter; + } + + private int[] GetFaceParameterIndices(ParameterGroupCollection group) + { + return group.Select(setting => _faceKeyIndexMap.TryGetValue(setting.Name, out var index) ? index : -1) + .Where(index => index >= 0) + .ToArray(); + } + + private int[] GetEyeParameterIndices(ParameterGroupCollection group) + { + return group.Select(setting => _eyeKeyIndexMap.TryGetValue(setting.Name, out var index) ? index : -1) + .Where(index => index >= 0) + .ToArray(); + } + public void Dispose() { // _processingLoopService.ExpressionUpdateEvent -= ExpressionUpdateHandler; diff --git a/src/Baballonia/Views/AppSettingsView.axaml b/src/Baballonia/Views/AppSettingsView.axaml index aca5112d..b57101e1 100644 --- a/src/Baballonia/Views/AppSettingsView.axaml +++ b/src/Baballonia/Views/AppSettingsView.axaml @@ -226,107 +226,72 @@ - + - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + SelectionChanged="GazeComboBox_OnSelectionChanged"> + + + + + Value="0" /> - - + + - - - + SelectionChanged="ExpressionsComboBox_OnSelectionChanged"> + + + + + Value="0" /> diff --git a/src/Baballonia/Views/AppSettingsView.axaml.cs b/src/Baballonia/Views/AppSettingsView.axaml.cs index 9843cb9c..a80e2394 100644 --- a/src/Baballonia/Views/AppSettingsView.axaml.cs +++ b/src/Baballonia/Views/AppSettingsView.axaml.cs @@ -15,14 +15,16 @@ public partial class AppSettingsView : UserControl { private readonly IThemeSelectorService _themeSelectorService; private readonly ILanguageSelectorService _languageSelectorService; + private readonly ILocalSettingsService _localSettingsService; private readonly ComboBox _themeComboBox; private readonly ComboBox _langComboBox; - private readonly NumericUpDown _selectedMinFreqCutoffUpDown; - private readonly NumericUpDown _selectedSpeedCutoffUpDown; + private readonly NumericUpDown _gazeUpDown; + private readonly NumericUpDown _expressionsUpDown; public AppSettingsView() { InitializeComponent(); + _localSettingsService = Ioc.Default.GetService()!; _themeSelectorService = Ioc.Default.GetService()!; _themeComboBox = this.Find("ThemeCombo")!; @@ -32,8 +34,8 @@ public AppSettingsView() _langComboBox = this.Find("LangCombo")!; _langComboBox.SelectionChanged += LangComboBox_SelectionChanged; - _selectedMinFreqCutoffUpDown = this.Find("SelectedMinFreqCutoffUpDown")!; - _selectedSpeedCutoffUpDown = this.Find("SelectedSpeedCutoffUpDown")!; + _gazeUpDown = this.Find("GazeUpDown")!; + _expressionsUpDown = this.Find("ExpressionsUpDown")!; UpdateThemes(); @@ -147,32 +149,50 @@ private void LaunchFirstTimeSetUp(object? sender, RoutedEventArgs e) } } - private void SelectedSpeedCutoffComboBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) + private void ExpressionsComboBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) { if (sender is not ComboBox comboBox) return; - _selectedSpeedCutoffUpDown.Value = comboBox.SelectedIndex switch + _expressionsUpDown.Value = comboBox.SelectedIndex switch { 0 => 0.5m, 1 => 1, 2 => 2, - _ => _selectedSpeedCutoffUpDown.Value + _ => _expressionsUpDown.Value }; } - private void SelectedMinFreqCutoffComboBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) + private void ExpressionsUpDown_OnValueChanged(object? sender, NumericUpDownValueChangedEventArgs e) + { + if (DataContext is not AppSettingsViewModel) return; + + _localSettingsService.SaveSetting("Filter_Expressions_Enabled", _expressionsUpDown.Value != 0); + _localSettingsService.SaveSetting("Filter_Expressions_MinFreq", _expressionsUpDown.Value); + _localSettingsService.SaveSetting("Filter_Expressions_Speed", _expressionsUpDown.Value); + } + + private void GazeComboBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) { if (sender is not ComboBox comboBox) return; - _selectedMinFreqCutoffUpDown.Value = comboBox.SelectedIndex switch + _gazeUpDown.Value = comboBox.SelectedIndex switch { 0 => 0.5m, 1 => 1, 2 => 2, - _ => _selectedMinFreqCutoffUpDown.Value + _ => _gazeUpDown.Value }; } + private void GazeUpDown_OnValueChanged(object? sender, NumericUpDownValueChangedEventArgs e) + { + if (DataContext is not AppSettingsViewModel) return; + + _localSettingsService.SaveSetting("Filter_Gaze_Enabled", _gazeUpDown.Value != 0); + _localSettingsService.SaveSetting("Filter_Gaze_Enabled_MinFreq", (float)_gazeUpDown.Value!); + _localSettingsService.SaveSetting("Filter_Gaze_Speed", (float)_gazeUpDown.Value); + } + public void RequestMachineId(object? sender, RoutedEventArgs routedEventArgs) { if (DataContext is not AppSettingsViewModel vm) return; diff --git a/src/Baballonia/Views/CalibrationView.axaml b/src/Baballonia/Views/CalibrationView.axaml index 69534af8..69d8cbd4 100644 --- a/src/Baballonia/Views/CalibrationView.axaml +++ b/src/Baballonia/Views/CalibrationView.axaml @@ -56,6 +56,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -63,7 +103,7 @@ - +