diff --git a/EyeTrackApp/config.py b/EyeTrackApp/config.py index 54a1b0f..448444e 100644 --- a/EyeTrackApp/config.py +++ b/EyeTrackApp/config.py @@ -222,7 +222,7 @@ class EyeTrackSettingsConfig(BaseModel): gui_smartinversion_select_right: bool = True gui_smartinversion_thresh: float = 0.25 gui_smartinversion_frame_count: int = 10 - + gui_smartinversion_smoothing_rate: float = 0.025 class EyeTrackConfig(BaseModel): version: int = 1 diff --git a/EyeTrackApp/settings/modules/SmartInversionSettingsModule.py b/EyeTrackApp/settings/modules/SmartInversionSettingsModule.py index 1ea8c18..cc980e9 100644 --- a/EyeTrackApp/settings/modules/SmartInversionSettingsModule.py +++ b/EyeTrackApp/settings/modules/SmartInversionSettingsModule.py @@ -8,13 +8,12 @@ import PySimpleGUI as sg from settings.modules.CommonFieldValidators import try_convert_to_float from settings.modules.CommonFieldValidators import try_convert_to_int - - class SmartInversionValidationModule(BaseValidationModel): gui_smartinversion_enabled: bool gui_smartinversion_select_right: bool gui_smartinversion_thresh: Annotated[float, AfterValidator(try_convert_to_float)] gui_smartinversion_frame_count: Annotated[int, AfterValidator(try_convert_to_int)] + gui_smartinversion_smoothing_rate: Annotated[float, AfterValidator(try_convert_to_float)] class SmartInversionSettingsModule(BaseSettingsModule): def __init__(self, config, widget_id, **kwargs): @@ -24,6 +23,7 @@ class SmartInversionSettingsModule(BaseSettingsModule): self.gui_smartinversion_select_right = f"-gui_smartinversion_select_right{widget_id}-" self.gui_smartinversion_thresh = f"-gui_smartinversion_thresh{widget_id}-" self.gui_smartinversion_frame_count =f"-gui_smartinversion_frame_count{widget_id}" + self.gui_smartinversion_smoothing_rate =f"-gui_smartinversion_smoothing_rate{widget_id}" def get_layout(self): @@ -62,7 +62,10 @@ class SmartInversionSettingsModule(BaseSettingsModule): self.config.gui_smartinversion_thresh, key=self.gui_smartinversion_thresh, size=(0, 10), - tooltip="Sets the maximum allowed difference in eye position (x-axis) to determine if the eyes are inverted or not." + tooltip=( + "Sets the maximum allowed difference in eye position (x-axis) to determine if the eyes are cross-eyed or not." + "\n Lower value will make cross-eye detection more sensitive." + ) ), ], [ @@ -71,7 +74,22 @@ class SmartInversionSettingsModule(BaseSettingsModule): self.config.gui_smartinversion_frame_count, key=self.gui_smartinversion_frame_count, size=(0, 10), - tooltip="How many frames the inversion conditions must be true (or no longer true) before the inversion state is toggled on or back off." + tooltip=( + "How long it takes to detect you are cross-eyed, or no longer cross-eyed." + "\n Higher number means longer duration before changing in or out of being cross-eyed state." + ) + ), + ], + [ + sg.Text("Smoothing Decay Rate", background_color=BACKGROUND_COLOR), + sg.InputText( + self.config.gui_smartinversion_smoothing_rate, + key=self.gui_smartinversion_smoothing_rate, + size=(0, 10), + tooltip=( + "How quickly eye smoothing decays when you enter or leave a cross-eyed state." + "\nHigher number = shorter smoothing duration." + ) ), ], ] \ No newline at end of file diff --git a/EyeTrackApp/utils/smart_inversion.py b/EyeTrackApp/utils/smart_inversion.py index 03364ab..05c3815 100644 --- a/EyeTrackApp/utils/smart_inversion.py +++ b/EyeTrackApp/utils/smart_inversion.py @@ -3,15 +3,19 @@ from utils.misc_utils import clamp def smart_inversion(self, var, out_x, out_y): - #Checks to see if the class already has frame counts or inversion attributes - if not hasattr(self, "inverted_frame_count"): - self.inverted_frame_count = 0 - - if not hasattr(self, "normal_frame_count"): - self.normal_frame_count = 0 - - if not hasattr(self, "is_inverted"): - self.is_inverted = False + #Checks to see if the class already has frame counts, inversion attributes or smoothing attributes + if not hasattr(self, "smartinversion_inverted_frame_count"): + self.smartinversion_inverted_frame_count = 0 + if not hasattr(self, "smartinversion_normal_frame_count"): + self.smartinversion_normal_frame_count = 0 + if not hasattr(self, "smartinversion_is_inverted"): + self.smartinversion_is_inverted = False + if not hasattr(self, "smartinversion_smoothing_progress"): + self.smartinversion_smoothing_progress = 0 + if not hasattr(self, "smartinversion_smoothed_eye_x"): + self.smartinversion_smoothed_eye_x = 0.0 + if not hasattr(self, "smartinversion_previous_inversion_state"): + self.smartinversion_previous_inversion_state = False #Updates eye positions with latest if self.eye_id == EyeId.LEFT: @@ -24,48 +28,63 @@ def smart_inversion(self, var, out_x, out_y): #Checks if eyes are inverted, and then activates inversion if the conditions have been true for a specified number of frames. if (var.l_eye_x > 0 and var.r_eye_x < 0) and (abs(var.l_eye_x - var.r_eye_x) > self.settings.gui_smartinversion_thresh): - self.inverted_frame_count = min(self.inverted_frame_count + 1, self.settings.gui_smartinversion_frame_count) + self.smartinversion_inverted_frame_count = min(self.smartinversion_inverted_frame_count + 1, self.settings.gui_smartinversion_frame_count) - if self.inverted_frame_count == self.settings.gui_smartinversion_frame_count: - self.is_inverted = True - self.normal_frame_count = 0 - print(f"Inversion Activated") + if self.smartinversion_inverted_frame_count == self.settings.gui_smartinversion_frame_count: + if self.smartinversion_previous_inversion_state != self.smartinversion_is_inverted: + self.smartinversion_normal_frame_count = 0 + self.smartinversion_is_inverted = True + print(f"Inversion Activated") #Checks if the eyes are no longer inverted, and then clears inversion if the conditions haven't been true for a specified number of frames. - elif self.is_inverted and ( + elif self.smartinversion_is_inverted and ( not (var.l_eye_x > 0 and var.r_eye_x < 0) or abs(var.l_eye_x - var.r_eye_x) <= self.settings.gui_smartinversion_thresh ): - self.normal_frame_count = min(self.normal_frame_count + 1, self.settings.gui_smartinversion_frame_count) + self.smartinversion_normal_frame_count = min(self.smartinversion_normal_frame_count + 1, self.settings.gui_smartinversion_frame_count) - if self.normal_frame_count == self.settings.gui_smartinversion_frame_count: - self.is_inverted = False - self.inverted_frame_count = 0 - print(f"Inversion Cleared") + if self.smartinversion_normal_frame_count == self.settings.gui_smartinversion_frame_count: + if self.smartinversion_previous_inversion_state != self.smartinversion_is_inverted: + self.smartinversion_is_inverted = False + self.smartinversion_inverted_frame_count = 0 + print(f"Inversion Cleared") + #Checks if the inversion state has recently been toggled, and activates smoothing + if self.smartinversion_previous_inversion_state != self.smartinversion_is_inverted: + self.smartinversion_smoothing_progress = 1 + self.smartinversion_previous_inversion_state = self.smartinversion_is_inverted #Determines which eye is being tracked based off selection and sets values accordingly if self.settings.gui_smartinversion_select_right: tracked_eye_x = var.r_eye_x tracked_eye_y = var.right_y recessive_eye = EyeId.LEFT + dominant_eye = EyeId.RIGHT else: tracked_eye_x = var.l_eye_x tracked_eye_y = var.left_y recessive_eye = EyeId.RIGHT + dominant_eye = EyeId.LEFT out_x = tracked_eye_x out_y = tracked_eye_y - #If eyes are inverted, and eye being processed is recessive, invert x value. - if self.eye_id == recessive_eye: - if self.is_inverted: - out_x = -tracked_eye_x + #Logic if smoothing is activated + if self.smartinversion_smoothing_progress > 0: + smartinversion_lerp_factor = (1 - self.smartinversion_smoothing_progress) + + if self.smartinversion_is_inverted and self.eye_id == recessive_eye: + self.smartinversion_smoothed_eye_x += (-tracked_eye_x - self.smartinversion_smoothed_eye_x) * smartinversion_lerp_factor else: - out_x = tracked_eye_x - else: - out_x = tracked_eye_x + self.smartinversion_smoothed_eye_x += (tracked_eye_x - self.smartinversion_smoothed_eye_x) * smartinversion_lerp_factor + + self.smartinversion_smoothing_progress = max(self.smartinversion_smoothing_progress - self.settings.gui_smartinversion_smoothing_rate, 0) + out_x = self.smartinversion_smoothed_eye_x + + #Logic if inversion is active, but smoothing is not active + elif self.smartinversion_is_inverted and self.eye_id == recessive_eye: + out_x = -tracked_eye_x out_y = tracked_eye_y diff --git a/conftest.py b/conftest.py index 6fe7217..f6d8a01 100644 --- a/conftest.py +++ b/conftest.py @@ -80,6 +80,7 @@ def eyetrack_settings_config(): gui_smartinversion_select_right=True, gui_smartinversion_thresh=0.5, gui_smartinversion_frame_count=10, + gui_smartinversion_smoothing_rate=0.025, )