diff --git a/EyeTrackApp/config.py b/EyeTrackApp/config.py index cac38ee..14803fb 100644 --- a/EyeTrackApp/config.py +++ b/EyeTrackApp/config.py @@ -23,7 +23,12 @@ class EyeTrackCameraConfig: gui_flip_x_axis_left: "bool" = False gui_flip_x_axis_right: "bool" = False gui_flip_y_axis: "bool" = False - + gui_blob_fallback: "bool" = True + gui_min_cutoff_slider: "int" = 4 + gui_speed_coefficient_slider: "int" = 9 + gui_osc_address: "str" = "127.0.0.1" + gui_osc_port: "int" = 9000 + CONFIG_FILE_NAME = "eyetrack_settings.json" diff --git a/EyeTrackApp/eye_processor.py b/EyeTrackApp/eye_processor.py index 5c4de06..e2a6ba0 100644 --- a/EyeTrackApp/eye_processor.py +++ b/EyeTrackApp/eye_processor.py @@ -176,8 +176,8 @@ class EyeProcessor: self.calibration_frame_counter - min_cutoff = 0.0004 - beta = 0.9 + min_cutoff = (self.config.gui_min_cutoff_slider / 10000) #0.0004 + beta = (self.config.gui_speed_coefficient_slider / 10) #0.9 noisy_point = np.array([1, 1]) self.one_euro_filter = OneEuroFilter( noisy_point, @@ -395,14 +395,27 @@ class EyeProcessor: # print(self.) out_x = 0 out_y = 0 - if xr > 0: - out_x = max(0.0, min(1.0, xr)) - if xl > 0: - out_x = -abs(max(0.0, min(1.0, xl))) - if yd > 0: - out_y = -abs(max(0.0, min(1.0, yd))) - if yu > 0: - out_y = max(0.0, min(1.0, yu)) + if self.config.gui_flip_y_axis == True: #check config on flipped values settings and apply accordingly + if yd > 0: + out_y = max(0.0, min(1.0, yd)) + if yu > 0: + out_y = -abs(max(0.0, min(1.0, yu))) + else: + if yd > 0: + out_y = -abs(max(0.0, min(1.0, yd))) + if yu > 0: + out_y = max(0.0, min(1.0, yu)) + + if self.config.gui_flip_x_axis == True: + if xr > 0: + out_x = -abs(max(0.0, min(1.0, xr))) + if xl > 0: + out_x = max(0.0, min(1.0, xl)) + else: + if xr > 0: + out_x = max(0.0, min(1.0, xr)) + if xl > 0: + out_x = -abs(max(0.0, min(1.0, xl))) try: noisy_point = np.array([out_x, out_y]) #fliter our values with a One Euro Filter @@ -561,7 +574,11 @@ class EyeProcessor: # using blob tracking. # if len(convex_hulls) == 0: - self.blob_tracking_fallback() + if self.config.gui_blob_fallback == True: + self.blob_tracking_fallback() + else: + print("[INFO] Blob fallback disabled. Assuming blink.") + self.output_images_and_update(thresh, EyeInformation(InformationOrigin.RANSAC, 0, 0, 0, False)) continue # Find our largest hull, which we expect will probably be the ellipse that represents the 2d @@ -576,7 +593,11 @@ class EyeProcessor: largest_hull.reshape(-1, 2) ) except: - self.blob_tracking_fallback() + if self.config.gui_blob_fallback == True: + self.blob_tracking_fallback() + else: + print("[INFO] Blob fallback disabled. Assuming blink.") + self.output_images_and_update(thresh, EyeInformation(InformationOrigin.RANSAC, 0, 0, 0, False)) continue # Get axis and angle of the ellipse, using pupil labs 2d algos. The next bit of code ranges @@ -655,15 +676,28 @@ class EyeProcessor: out_x = 0 out_y = 0 - if xr > 0: - out_x = max(0.0, min(1.0, xr)) - if xl > 0: - out_x = -abs(max(0.0, min(1.0, xl))) - if yd > 0: - out_y = -abs(max(0.0, min(1.0, yd))) - if yu > 0: - out_y = max(0.0, min(1.0, yu)) + if self.config.gui_flip_y_axis == True: + if yd > 0: + out_y = max(0.0, min(1.0, yd)) + if yu > 0: + out_y = -abs(max(0.0, min(1.0, yu))) + else: + if yd > 0: + out_y = -abs(max(0.0, min(1.0, yd))) + if yu > 0: + out_y = max(0.0, min(1.0, yu)) + + if self.config.gui_flip_x_axis == True: + if xr > 0: + out_x = -abs(max(0.0, min(1.0, xr))) + if xl > 0: + out_x = max(0.0, min(1.0, xl)) + else: + if xr > 0: + out_x = max(0.0, min(1.0, xr)) + if xl > 0: + out_x = -abs(max(0.0, min(1.0, xl))) try: diff --git a/EyeTrackApp/eyetrackapp.py b/EyeTrackApp/eyetrackapp.py index a99119e..5c951d7 100644 --- a/EyeTrackApp/eyetrackapp.py +++ b/EyeTrackApp/eyetrackapp.py @@ -35,7 +35,7 @@ def main(): # Spawn worker threads osc_queue: "queue.Queue[tuple[bool, int, int]]" = queue.Queue() - osc = VRChatOSC(cancellation_event, osc_queue) + osc = VRChatOSC(cancellation_event, osc_queue, config) osc_thread = threading.Thread(target=osc.run) osc_thread.start() @@ -119,7 +119,7 @@ def main(): settings[0].start() # Create the window - window = sg.Window("EyeTrackVR v0.1.4 nightly", layout, icon='Images/logo.ico', background_color='#292929') + window = sg.Window("EyeTrackVR v0.1.4 dev", layout, icon='Images/logo.ico', background_color='#292929') # GUI Render loop while True: diff --git a/EyeTrackApp/osc.py b/EyeTrackApp/osc.py index df90e72..57bc6d5 100644 --- a/EyeTrackApp/osc.py +++ b/EyeTrackApp/osc.py @@ -9,21 +9,17 @@ class EyeId(IntEnum): LEFT = 1 BOTH = 2 SETTINGS = 3 - +from config import EyeTrackConfig class VRChatOSC: - # VRChat OSC Networking Info. For now, we'll assume it's always local. - OSC_IP = "127.0.0.1" - OSC_PORT = 9000 # VR Chat OSC port # Use a tuple of blink (true, blinking, false, not), x, y for now. Probably clearer as a class but # we're stuck in python 3.6 so still no dataclasses. God I hate python. - def __init__( - self, - cancellation_event: "threading.Event", - msg_queue: "queue.Queue[tuple[bool, int, int, int]]", - ): - self.client = udp_client.SimpleUDPClient(VRChatOSC.OSC_IP, VRChatOSC.OSC_PORT) + def __init__(self, cancellation_event: "threading.Event", msg_queue: "queue.Queue[tuple[bool, int, int, int]]", main_config: EyeTrackConfig,): + + self.main_config = main_config + self.config = main_config.settings + self.client = udp_client.SimpleUDPClient(self.config.gui_osc_address, self.config.gui_osc_port) # use OSC port and address that was set in the config self.cancellation_event = cancellation_event self.msg_queue = msg_queue @@ -42,7 +38,7 @@ class VRChatOSC: return try: (eye_id, eye_info) = self.msg_queue.get(block=True, timeout=0.1) - except queue.Empty: + except: continue diff --git a/EyeTrackApp/settings_widget.py b/EyeTrackApp/settings_widget.py index 4e5af80..5555d4a 100644 --- a/EyeTrackApp/settings_widget.py +++ b/EyeTrackApp/settings_widget.py @@ -36,12 +36,20 @@ class SettingsWidget: self.gui_save_button = f"-SAVE{widget_id}-" self.gui_general_settings_layout = f"-GENERALSETTINGSLAYOUT{widget_id}-" + + self.gui_osc_address = f"-OSCADDRESS{widget_id}-" + self.gui_osc_port = f"-OSCPORT{widget_id}-" # self.gui_algo_settings_layout = f"-ALGOSETTINGSLAYOUT{widget_id}-" self.gui_blob_fallback = f"-BLOBFALLBACK{widget_id}-" self.gui_speed_coefficient_slider = f"-SPEEDCOEFFICIENTSLIDER{widget_id}-" self.gui_min_cutoff_slider = f"-MINCUTOFFSLIDER{widget_id}-" self.main_config = main_config + + self.configr = main_config.right_eye + + self.configl = main_config.left_eye + self.config = main_config.settings self.osc_queue = osc_queue @@ -82,7 +90,7 @@ class SettingsWidget: [sg.Checkbox( "Blob Fallback", - default=True, + default=self.config.gui_blob_fallback, key=self.gui_blob_fallback, background_color='#424042', ), @@ -96,7 +104,7 @@ class SettingsWidget: sg.Text("Min Frequency Cutoff", background_color='#424042'), sg.Slider( range=(0, 10), - default_value=4, + default_value=self.config.gui_min_cutoff_slider, orientation="h", key=self.gui_min_cutoff_slider, background_color='#424042' @@ -112,6 +120,18 @@ class SettingsWidget: background_color='#424042' ), ], + [ + sg.Text("OSC Settings:", background_color='#242224'), + ], + [ + sg.Text("OSC Address:", background_color='#424042'), + sg.InputText(self.config.gui_osc_address, key=self.gui_osc_address), + ], + [ + sg.Text("OSC Port:", background_color='#424042'), + #print(self.config.gui_osc_port) + sg.InputText(self.config.gui_osc_port, key=self.gui_osc_port), + ], #[sg.Image(filename="", key=self.gui_tracking_image)], @@ -215,9 +235,18 @@ class SettingsWidget: # self.config.capture_source = values[self.gui_camera_addr] # changed = True - #if self.config.threshold != values[self.gui_threshold_slider]: - # self.config.threshold = int(values[self.gui_threshold_slider]) - # changed = True + if self.config.gui_min_cutoff_slider != values[self.gui_min_cutoff_slider]: + self.config.gui_min_cutoff_slider = int(values[self.gui_min_cutoff_slider]) + self.configl.gui_min_cutoff_slider = int(values[self.gui_min_cutoff_slider]) + self.configr.gui_min_cutoff_slider = int(values[self.gui_min_cutoff_slider]) + changed = True + + if self.config.gui_speed_coefficient_slider != values[self.gui_speed_coefficient_slider]: + self.config.gui_speed_coefficient_slider = int(values[self.gui_speed_coefficient_slider]) + self.configl.gui_speed_coefficient_slider = int(values[self.gui_speed_coefficient_slider]) + self.configr.gui_speed_coefficient_slider = int(values[self.gui_speed_coefficient_slider]) + changed = True + #if self.config.rotation_angle != values[self.gui_rotation_slider]: # self.config.rotation_angle = int(values[self.gui_rotation_slider]) @@ -225,15 +254,27 @@ class SettingsWidget: # print(self.config.gui_flip_x_axis, values[self.gui_flip_x_axis]) if self.config.gui_flip_x_axis_right != values[self.gui_flip_x_axis_right]: self.config.gui_flip_x_axis_right = values[self.gui_flip_x_axis_right] + self.configl.gui_flip_x_axis_right = values[self.gui_flip_x_axis_right] + self.configr.gui_flip_x_axis_right = values[self.gui_flip_x_axis_right] changed = True if self.config.gui_flip_x_axis_left != values[self.gui_flip_x_axis_left]: self.config.gui_flip_x_axis_left = values[self.gui_flip_x_axis_left] + self.configl.gui_flip_x_axis_left = values[self.gui_flip_x_axis_left] + self.configr.gui_flip_x_axis_left = values[self.gui_flip_x_axis_left] changed = True if self.config.gui_flip_y_axis != values[self.gui_flip_y_axis]: self.config.gui_flip_y_axis = values[self.gui_flip_y_axis] + self.configl.gui_flip_y_axis = values[self.gui_flip_y_axis] + self.configr.gui_flip_y_axis = values[self.gui_flip_y_axis] + changed = True + + if self.config.gui_blob_fallback != values[self.gui_blob_fallback]: + self.config.gui_blob_fallback = values[self.gui_blob_fallback] + self.configl.gui_blob_fallback = values[self.gui_blob_fallback] + self.configr.gui_blob_fallback = values[self.gui_blob_fallback] changed = True