Update config.py

This commit is contained in:
Prohurtz 2024-07-10 14:45:25 -07:00 committed by GitHub
parent 9c98f62ce3
commit 1d69fc110f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,29 +1,124 @@
"""
------------------------------------------------------------------------------------------------------
,@@@@@@
@@@@@@@@@@@ @@@
@@@@@@@@@@@@ @@@@@@@@@@@
@@@@@@@@@@@@@ @@@@@@@@@@@@@@
@@@@@@@/ ,@@@@@@@@@@@@@
/@@@@@@@@@@@@@@@ @@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@
@@@@@@@@ @@@@@
,@@@ @@@@&
@@@@@@. @@@@
@@@ @@@@@@@@@/ @@@@@
,@@@. @@@@@@((@ @@@@(
//@@@ ,, @@@@ @@@@@
@@@( @@@@@@@
@@@ @ @@@@@@@@#
@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@(
Copyright (c) 2023 EyeTrackVR <3
LICENSE: GNU GPLv3
------------------------------------------------------------------------------------------------------
"""
import json import json
import os.path import os.path
import shutil import shutil
from eye import EyeId
from colorama import Fore
from pydantic import BaseModel from pydantic import BaseModel
from typing import Union from typing import Any, Union, List
import os
from eye import EyeId
CONFIG_FILE_NAME: str = "eyetrack_settings.json" CONFIG_FILE_NAME: str = "eyetrack_settings.json"
BACKUP_CONFIG_FILE_NAME: str = "eyetrack_settings.backup" BACKUP_CONFIG_FILE_NAME: str = "eyetrack_settings.backup"
class EyeTrackCameraConfig(BaseModel): class EyeTrackCameraConfig(BaseModel):
rotation_angle: int = 0
gui_rotation_ui_padding: bool = True gui_rotation_ui_padding: bool = True
rotation_angle: int = 0
roi_window_x: int = 0 roi_window_x: int = 0
roi_window_y: int = 0 roi_window_y: int = 0
roi_window_w: int = 240 roi_window_w: int = 240
roi_window_h: int = 240 roi_window_h: int = 240
focal_length: int = 30 focal_length: int = 30
capture_source: Union[int, str, None] = None capture_source: Union[int, str, None] = None
calib_XMAX: Union[int, None] = None calib_XMAX: Union[float, None] = None
calib_XMIN: Union[int, None] = None calib_XMIN: Union[float, None] = None
calib_YMAX: Union[int, None] = None calib_YMAX: Union[float, None] = None
calib_YMIN: Union[int, None] = None calib_YMIN: Union[float, None] = None
calib_XOFF: Union[int, None] = None calib_XOFF: Union[float, None] = None
calib_YOFF: Union[int, None] = None calib_YOFF: Union[float, None] = None
calibration_points: List[List[Union[float, None]]] = []
calibration_points_3d: List[List[Union[float, None]]] = []
def update_capture_source(self, new_camera_address: str):
if not new_camera_address:
self.capture_source = None
return
if new_camera_address.isnumeric():
self.capture_source = int(new_camera_address)
return
# we were passed an IP, probably, lets add HTTP:// to it
if len(new_camera_address) > 5 and not (
not new_camera_address.startswith(("http", "/dev")) or not new_camera_address.endswith(".mp4")
):
self.capture_source = f"http://{new_camera_address}"
return
self.capture_source = new_camera_address
def update(self, data: dict[str, Any]) -> bool:
"""
Updates the model one field at a time based on the provided data dict.
The dict has to be defined like
```
data = {
"model_field": value
}
```
If stale data is provided,
ex. User clicked on save and restart but didn't provide a new field
we skip it, assuming that it was just a call to restart the tracking, or a miss-click.
Some fields may require more validation, we take care of that with special methods.
defining a method like
```
def update_custom_field(value: type):
pass
```
will cause it to be picked up by this method and called with the current value.
Return values are ignored.
"""
for key, value in data.items():
old_value = getattr(self, key, None)
# no reason to update if it's the same value
if old_value == value:
return False
if hasattr(self, f"update_{key}"):
update_attr = getattr(self, f"update_{key}")
if callable(update_attr):
update_attr(value)
else:
setattr(self, "key", value)
return True
else:
print(f"\033[93m[WARN] Field {key} does not exist on {self}.\033[0m")
return False
class EyeTrackSettingsConfig(BaseModel): class EyeTrackSettingsConfig(BaseModel):
@ -34,7 +129,9 @@ class EyeTrackSettingsConfig(BaseModel):
gui_HSF: bool = False gui_HSF: bool = False
gui_BLOB: bool = False gui_BLOB: bool = False
gui_BLINK: bool = False gui_BLINK: bool = False
gui_HSRAC: bool = True gui_HSRAC: bool = False
gui_AHSFRAC: bool = True
gui_AHSF: bool = False
gui_DADDY: bool = False gui_DADDY: bool = False
gui_LEAP: bool = False gui_LEAP: bool = False
gui_HSF_radius: int = 15 gui_HSF_radius: int = 15
@ -50,14 +147,18 @@ class EyeTrackSettingsConfig(BaseModel):
gui_blob_maxsize: float = 25 gui_blob_maxsize: float = 25
gui_blob_minsize: float = 10 gui_blob_minsize: float = 10
gui_recenter_eyes: bool = False gui_recenter_eyes: bool = False
gui_3d_calibration: bool = False
grab_3d_point: bool = False
tracker_single_eye: int = 0 tracker_single_eye: int = 0
gui_threshold: int = 65 gui_threshold: int = 65
gui_HSRACP: int = 1 gui_AHSFRACP: int = 1
gui_HSFP: int = 2 gui_AHSFP: int = 2
gui_DADDYP: int = 3 gui_HSRACP: int = 3
gui_RANSAC3DP: int = 4 gui_HSFP: int = 4
gui_BLOBP: int = 5 gui_DADDYP: int = 5
gui_LEAPP: int = 6 gui_RANSAC3DP: int = 6
gui_BLOBP: int = 7
gui_LEAPP: int = 8
gui_IBO: bool = True gui_IBO: bool = True
gui_skip_autoradius: bool = False gui_skip_autoradius: bool = False
gui_thresh_add: int = 11 gui_thresh_add: int = 11
@ -74,12 +175,13 @@ class EyeTrackSettingsConfig(BaseModel):
osc_left_eye_x_address: str = "/avatar/parameters/LeftEyeX" osc_left_eye_x_address: str = "/avatar/parameters/LeftEyeX"
osc_right_eye_x_address: str = "/avatar/parameters/RightEyeX" osc_right_eye_x_address: str = "/avatar/parameters/RightEyeX"
osc_eyes_y_address: str = "/avatar/parameters/EyesY" osc_eyes_y_address: str = "/avatar/parameters/EyesY"
osc_eyes_pupil_dilation_address: str = "/avatar/parameters/EyesDilation"
osc_invert_eye_close: bool = False osc_invert_eye_close: bool = False
gui_RANSACBLINK: bool = False gui_RANSACBLINK: bool = False
gui_right_eye_dominant: bool = False gui_right_eye_dominant: bool = False
gui_left_eye_dominant: bool = False gui_left_eye_dominant: bool = False
gui_outer_side_falloff: bool = True gui_outer_side_falloff: bool = False
gui_eye_dominant_diff_thresh: float = 0.3 gui_eye_dominant_diff_thresh: float = 0.3
gui_legacy_ransac: bool = False gui_legacy_ransac: bool = False
@ -91,6 +193,24 @@ class EyeTrackSettingsConfig(BaseModel):
gui_vrc_native: bool = True gui_vrc_native: bool = True
gui_pupil_dilation: bool = True gui_pupil_dilation: bool = True
gui_VRCFTModulePort: int = 8889
gui_VRCFTModuleIPAddress: str = "127.0.0.1"
gui_ShouldEmulateEyeWiden: bool = False
gui_ShouldEmulateEyeSquint: bool = False
gui_ShouldEmulateEyebrows: bool = False
gui_WidenThresholdV1_min: float = 0.60
gui_WidenThresholdV1_max: float = 1
gui_WidenThresholdV2_min: float = 0.60
gui_WidenThresholdV2_max: float = 1.05
gui_SqueezeThresholdV1_min: float = 0.07
gui_SqueezeThresholdV1_max: float = 0.5
gui_SqueezeThresholdV2_min: float = 0.07
gui_SqueezeThresholdV2_max: float = -1
gui_EyebrowThresholdRising: float = 0.8
gui_EyebrowThresholdLowering: float = 0.15
gui_OutputMultiplier: float = 1
gui_use_module: bool = False
class EyeTrackConfig(BaseModel): class EyeTrackConfig(BaseModel):
version: int = 1 version: int = 1
@ -123,10 +243,62 @@ class EyeTrackConfig(BaseModel):
load_config = EyeTrackConfig() load_config = EyeTrackConfig()
return load_config return load_config
def validate_camera_address_conflict(self, eye_id, capture_source):
match eye_id:
case EyeId.RIGHT:
if self.left_eye.capture_source == capture_source:
print(
f"{Fore.YELLOW}[WARN] Capture source {capture_source} already in use by the left camera.{Fore.RESET}"
)
return False
case EyeId.LEFT:
if self.right_eye.capture_source == capture_source:
print(
f"{Fore.YELLOW}[WARN] Capture source {capture_source} already in use by the right camera.{Fore.RESET}"
)
return False
case _:
return False
return True
def update_eye_model_config(self, eye_id: EyeId, data: dict, should_save=True, should_notify=True) -> bool:
"""
A more granular method for updating a particular model so that everything that relies on it
will get notified about any changes. Note, it acts a bit like pub-sub,
we don't care what changes got passed, we will notify the listeners with them.
It's the listeners job to check if they want that update.
"""
# The app really doesn't like address clashes, so we have to validate it as soon as possible
# otherwise we crash
if "capture_source" in data and not self.validate_camera_address_conflict(eye_id, data["capture_source"]):
return False
match eye_id:
case EyeId.RIGHT:
changed = self.right_eye.update(data)
case EyeId.LEFT:
changed = self.left_eye.update(data)
case _:
return False
if should_save:
self.save()
if should_notify:
self.__notify_listeners(data)
return changed
def update(self, data, save=False): def update(self, data, save=False):
"""
More of an internal method for modules to be able to update the config
and have other parts of the system react to changes
"""
for field, value in data.items(): for field, value in data.items():
setattr(self.settings, field, value) setattr(self.settings, field, value)
self.__notify_listeners() self.__notify_listeners(data)
if save: if save:
self.save() self.save()
@ -150,6 +322,6 @@ class EyeTrackConfig(BaseModel):
print(f"[DEBUG] Registering listener {callback}") print(f"[DEBUG] Registering listener {callback}")
self.__listeners.append(callback) self.__listeners.append(callback)
def __notify_listeners(self): def __notify_listeners(self, data: dict):
for listener in self.__listeners: for listener in self.__listeners:
listener() listener(data)