Merge pull request #93 from lorow/feature/config-rewrite-with-defualts-reset

Settings rewrite for easier data validation and resseting to defaults
This commit is contained in:
Prohurtz 2023-10-11 23:13:48 -05:00 committed by GitHub
commit 0ee5404f5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1082 additions and 982 deletions

View File

@ -1,540 +1,21 @@
import PySimpleGUI as sg
from config import EyeTrackSettingsConfig
from config import EyeTrackConfig
from osc import EyeId
from queue import Queue
from threading import Event
from settings.BaseSettings import BaseSettingsWidget
from settings.modules.AdvancedTrackingAlgoSettingsModule import (
AdvancedTrackingAlgoSettingsModule,
)
from settings.modules.BlinkAlgoModule import BlinkAlgoSettingsModule
from settings.modules.TrackingAlgorithmModule import TrackingAlgorithmModule
class AlgoSettingsWidget:
def __init__(
self, widget_id: EyeId, main_config: EyeTrackSettingsConfig, osc_queue: Queue
):
self.gui_flip_x_axis_left = f"-FLIPXAXISLEFT{widget_id}-"
self.gui_flip_x_axis_right = f"-FLIPXAXISRIGHT{widget_id}-"
self.gui_flip_y_axis = f"-FLIPYAXIS{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_osc_receiver_port = f"OSCRECEIVERPORT{widget_id}-"
self.gui_osc_recenter_address = f"OSCRECENTERADDRESS{widget_id}-"
self.gui_osc_recalibrate_address = f"OSCRECALIBRATEADDRESS{widget_id}-"
self.gui_BLOB = f"-BLOBFALLBACK{widget_id}-"
self.gui_HSF = f"-HSF{widget_id}-"
self.gui_DADDY = f"-DADDY{widget_id}-"
self.gui_DADDYP = f"-DADDYP{widget_id}-"
self.gui_LEAPP = f"-LEAPP{widget_id}-"
self.gui_LEAP = f"-LEAP{widget_id}-"
self.gui_RANSAC3D = f"-RANSAC3D{widget_id}-"
self.gui_BLINK = f"-BLINK{widget_id}-"
self.gui_IBO = f"-IBO{widget_id}-"
self.gui_HSRAC = f"-HSRAC{widget_id}-"
self.gui_HSF_radius = f"-HSFRADIUS{widget_id}-"
self.gui_blob_maxsize = f"-BLOBMAXSIZE{widget_id}-"
self.gui_blob_minsize = f"-BLOBMINSIZE{widget_id}-"
self.gui_speed_coefficient = f"-SPEEDCOEFFICIENT{widget_id}-"
self.gui_min_cutoff = f"-MINCUTOFF{widget_id}-"
self.gui_eye_falloff = f"-EYEFALLOFF{widget_id}-"
self.gui_skip_autoradius = f"-SKIPAUTORADIUS{widget_id}-"
self.gui_HSRACP = f"-HSRACP{widget_id}-"
self.gui_RANSAC3DP = f"-RANSAC3DP{widget_id}-"
self.gui_HSFP = f"-HSFP{widget_id}-"
self.gui_BLOBP = f"-BLOBP{widget_id}-"
self.gui_thresh_add = f"-THRESHADD{widget_id}-"
self.gui_ROSC = f"-ROSC{widget_id}-"
self.gui_RANSACBLINK = f"-RANSACBLINK{widget_id}-"
self.gui_vrc_native = f"-VRCNATIVE{widget_id}-"
self.gui_circular_crop_left = f"-CIRCLECROPLEFT{widget_id}-"
self.gui_circular_crop_right = f"-CIRCLECROPRIGHT{widget_id}-"
self.gui_update_check = f"-UPDATECHECK{widget_id}-"
self.gui_threshold_slider = f"-BLOBTHRESHOLD{widget_id}-"
self.gui_HSF_radius_left = f"-HSFRADIUSLEFT{widget_id}-"
self.gui_HSF_radius_right = f"-HSFRADIUSRIGHT{widget_id}-"
self.ibo_filter_samples = f"-IBOFILTERSAMPLE{widget_id}-"
self.calibration_samples = f"-CALIBRATIONSAMPLES{widget_id}-"
self.ibo_fully_close_eye_threshold = f"-CLOSETHRESH{widget_id}-"
self.gui_legacy_ransac = f"-LEGACYRANSACTHRESH{widget_id}-"
self.gui_legacy_ransac_thresh_right = f"-THRESHRIGHT{widget_id}-"
self.gui_legacy_ransac_thresh_left = f"-THRESHLEFT{widget_id}-"
self.gui_pupil_dilation = f"-EBPD{widget_id}-"
self.gui_LEAP_lid = f"-LEAPLID{widget_id}-"
self.main_config = main_config
self.config = main_config.settings
self.osc_queue = osc_queue
# Define the window's contents
self.general_settings_layout = [
[
sg.Checkbox(
"",
default=self.config.gui_HSRAC,
key=self.gui_HSRAC,
background_color="#424042",
tooltip="Our flagship algorithm, utilizing both HSF and RANSAC for best tracking quality and lighting resistance.",
),
sg.Combo(
["1", "2", "3", "4", "5", "6"],
default_value=self.config.gui_HSRACP,
key=self.gui_HSRACP,
background_color="#424042",
text_color="white",
button_arrow_color="black",
button_background_color="#6f4ca1",
tooltip="Select the priority of eyetracking algorithms.",
),
sg.Text("HSRAC", background_color="#424042"),
# ],
# [
sg.Checkbox(
"",
default=self.config.gui_HSF,
key=self.gui_HSF,
background_color="#424042",
tooltip="HSF Is a new, lower resolution tracking algorithim that provides excelent resilancy to lighting conditions and great speed.",
),
sg.Combo(
["1", "2", "3", "4", "5", "6"],
default_value=self.config.gui_HSFP,
key=self.gui_HSFP,
background_color="#424042",
text_color="white",
button_arrow_color="black",
button_background_color="#6f4ca1",
tooltip="Select the priority of eyetracking algorithims.",
),
sg.Text("Haar Surround Feature", background_color="#424042"),
],
[
sg.Checkbox(
"",
default=self.config.gui_DADDY,
key=self.gui_DADDY,
background_color="#424042",
tooltip="DADDY Uses a Deep learning algorithm. This has a big CPU usage impact.",
),
sg.Combo(
["1", "2", "3", "4", "5", "6"],
default_value=self.config.gui_DADDYP,
key=self.gui_DADDYP,
background_color="#424042",
text_color="white",
button_arrow_color="black",
button_background_color="#6f4ca1",
tooltip="Select the priority of eyetracking algorithms.",
),
sg.Text("DADDY", background_color="#424042"),
# ],
# [
sg.Checkbox(
"",
default=self.config.gui_RANSAC3D,
key=self.gui_RANSAC3D,
background_color="#424042",
tooltip="RANSAC3D provides good tracking quality, however does not do well in bad lighting conditions.",
),
sg.Combo(
["1", "2", "3", "4", "5", "6"],
default_value=self.config.gui_RANSAC3DP,
key=self.gui_RANSAC3DP,
background_color="#424042",
text_color="white",
button_arrow_color="black",
button_background_color="#6f4ca1",
tooltip="Select the priority of eyetracking algorithms.",
),
sg.Text("RANSAC 3D", background_color="#424042"),
sg.Checkbox(
"Legacy RANSAC Thresh",
default=self.config.gui_legacy_ransac,
key=self.gui_legacy_ransac,
background_color="#424042",
),
],
[
sg.Checkbox(
"",
default=self.config.gui_LEAP,
key=self.gui_LEAP,
background_color="#424042",
tooltip="LEAP Uses a lightweight deep learning algorithm.",
),
sg.Combo(
["1", "2", "3", "4", "5", "6"],
default_value=self.config.gui_LEAPP,
key=self.gui_LEAPP,
background_color="#424042",
text_color="white",
button_arrow_color="black",
button_background_color="#6f4ca1",
tooltip="Select the priority of eyetracking algorithms.",
),
sg.Text("LEAP", background_color="#424042"),
sg.Checkbox(
"",
default=self.config.gui_BLOB,
key=self.gui_BLOB,
background_color="#424042",
tooltip="Blob tracking is the oldest and worst tracking algorithm, it provides fast, though sometimes inaccurate tracking.",
),
sg.Combo(
["1", "2", "3", "4", "5", "6"],
default_value=self.config.gui_BLOBP,
key=self.gui_BLOBP,
background_color="#424042",
text_color="white",
button_arrow_color="black",
button_background_color="#6f4ca1",
tooltip="Select the priority of eyetracking algorithms.",
),
sg.Text("Blob", background_color="#424042"),
],
[sg.Text("Eyelid Algo Settings:", background_color="#242224")],
[
sg.Checkbox(
"Intensity Based Openness",
default=self.config.gui_IBO,
key=self.gui_IBO,
background_color="#424042",
),
sg.Checkbox(
"RANSAC Quick Blink Algo",
default=self.config.gui_RANSACBLINK,
key=self.gui_RANSACBLINK,
background_color="#424042",
),
sg.Checkbox(
"Binary Blink Algo",
default=self.config.gui_BLINK,
key=self.gui_BLINK,
background_color="#424042",
),
sg.Checkbox(
"LEAP Lid",
default=self.config.gui_LEAP_lid,
key=self.gui_LEAP_lid,
background_color="#424042",
),
],
[
sg.Text("IBO Filter Sample Size", background_color="#424042"),
sg.InputText(
self.config.ibo_filter_samples,
key=self.ibo_filter_samples,
size=(0, 10),
),
sg.Text("Calibration Samples", background_color="#424042"),
sg.InputText(
self.config.calibration_samples,
key=self.calibration_samples,
size=(0, 10),
),
sg.Text("IBO Close Threshold", background_color="#424042"),
sg.InputText(
self.config.ibo_fully_close_eye_threshold,
key=self.ibo_fully_close_eye_threshold,
size=(0, 10),
),
],
[
sg.Checkbox(
"Left Eye Circle crop",
default=self.config.gui_circular_crop_left,
key=self.gui_circular_crop_left,
background_color="#424042",
),
sg.Checkbox(
"Right Eye Circle crop",
default=self.config.gui_circular_crop_right,
key=self.gui_circular_crop_right,
background_color="#424042",
),
],
[sg.Text("Pupil Dilation Algo Settings:", background_color="#242224")],
[
sg.Checkbox(
"Ellipse Based Pupil Dilation",
default=self.config.gui_pupil_dilation,
key=self.gui_pupil_dilation,
background_color="#424042",
)
],
[
sg.Text(
"Advanced Tracking Algorithim Settings:", background_color="#242224"
)
],
[
sg.Checkbox(
"HSF: Skip Auto Radius",
default=self.config.gui_skip_autoradius,
key=self.gui_skip_autoradius,
background_color="#424042",
tooltip="To gain more control and possibly better tracking quality of HSF, please disable auto radius to enable manual adjustment.",
),
],
[
sg.Text("Left HSF Radius:", background_color="#424042"),
sg.Slider(
range=(1, 50),
default_value=self.config.gui_HSF_radius_left,
orientation="h",
key=self.gui_HSF_radius_left,
background_color="#424042",
tooltip="Adjusts the radius parameter for HSF. Only adjust if you are having tracking issues.",
),
sg.Text("Right HSF Radius:", background_color="#424042"),
sg.Slider(
range=(1, 50),
default_value=self.config.gui_HSF_radius_right,
orientation="h",
key=self.gui_HSF_radius_right,
background_color="#424042",
tooltip="Adjusts the radius parameter for HSF. Only adjust if you are having tracking issues.",
),
],
[
sg.Text("RANSAC Thresh Add", background_color="#424042"),
sg.Slider(
range=(1, 50),
default_value=self.config.gui_thresh_add,
orientation="h",
key=self.gui_thresh_add,
background_color="#424042",
tooltip="Adjusts the amount of threshold to add to RANSAC. Useful for fine tuning your setup.",
),
sg.Text("Blob Threshold", background_color="#424042"),
# TODO make this for right and left eyes? I dont know how vital that is..
sg.Slider(
range=(0, 110),
default_value=self.config.gui_threshold,
orientation="h",
key=self.gui_threshold_slider,
background_color="#424042",
tooltip="Adjusts the threshold for blob tracking.",
),
],
[
sg.Text("Min Blob Size:", background_color="#424042"),
sg.Slider(
range=(1, 50),
default_value=self.config.gui_blob_minsize,
orientation="h",
key=self.gui_blob_minsize,
background_color="#424042",
tooltip="Minimum size a blob has to be for blob tracking.",
),
sg.Text("Max Blob Size:", background_color="#424042"),
sg.Slider(
range=(1, 50),
default_value=self.config.gui_blob_maxsize,
orientation="h",
key=self.gui_blob_maxsize,
background_color="#424042",
tooltip="Maximum size a blob can be for blob tracking.",
),
],
[
sg.Text("Right Eye Thresh:", background_color="#424042"),
sg.Slider(
range=(1, 120),
default_value=self.config.gui_legacy_ransac_thresh_right,
orientation="h",
key=self.gui_legacy_ransac_thresh_right,
background_color="#424042",
tooltip="Threshold for right eye, legacy RANSAC only",
),
sg.Text("Left Eye Thresh:", background_color="#424042"),
sg.Slider(
range=(1, 120),
default_value=self.config.gui_legacy_ransac_thresh_left,
orientation="h",
key=self.gui_legacy_ransac_thresh_left,
background_color="#424042",
tooltip="Threshold for left eye, legacy RANSAC only",
),
],
class AlgoSettingsWidget(BaseSettingsWidget):
def __init__(self, widget_id: EyeId, main_config: EyeTrackConfig):
settings_modules = [
TrackingAlgorithmModule,
BlinkAlgoSettingsModule,
AdvancedTrackingAlgoSettingsModule,
]
self.widget_layout = [
[
sg.Text(
"Tracking Algorithm Order Settings:", background_color="#242224"
),
],
[
sg.Column(
self.general_settings_layout,
key=self.gui_general_settings_layout,
background_color="#424042",
),
],
]
self.cancellation_event = (
Event()
) # Set the event until start is called, otherwise we can block if shutdown is called.
self.cancellation_event.set()
self.image_queue = Queue()
def started(self):
return not self.cancellation_event.is_set()
def start(self):
# If we're already running, bail
if not self.cancellation_event.is_set():
return
self.cancellation_event.clear()
def stop(self):
# If we're not running yet, bail
if self.cancellation_event.is_set():
return
self.cancellation_event.set()
def render(self, window, event, values):
# If anything has changed in our configuration settings, change/update those.
changed = False
if self.config.gui_HSFP != int(values[self.gui_HSFP]):
self.config.gui_HSFP = int(values[self.gui_HSFP])
changed = True
if self.config.gui_HSF != values[self.gui_HSF]:
self.config.gui_HSF = values[self.gui_HSF]
changed = True
if self.config.gui_DADDYP != int(values[self.gui_DADDYP]):
self.config.gui_DADDYP = int(values[self.gui_DADDYP])
changed = True
if self.config.gui_DADDY != values[self.gui_DADDY]:
self.config.gui_DADDY = values[self.gui_DADDY]
changed = True
if self.config.gui_RANSAC3DP != int(
values[self.gui_RANSAC3DP]
): # TODO check that priority order is unique/auto fix it.
self.config.gui_RANSAC3DP = int(values[self.gui_RANSAC3DP])
changed = True
if self.config.gui_RANSAC3D != values[self.gui_RANSAC3D]:
self.config.gui_RANSAC3D = values[self.gui_RANSAC3D]
changed = True
if self.config.gui_legacy_ransac != values[self.gui_legacy_ransac]:
self.config.gui_legacy_ransac = values[self.gui_legacy_ransac]
changed = True
if self.config.gui_HSRACP != int(values[self.gui_HSRACP]):
self.config.gui_HSRACP = int(values[self.gui_HSRACP])
changed = True
if self.config.gui_HSRAC != values[self.gui_HSRAC]:
self.config.gui_HSRAC = values[self.gui_HSRAC]
changed = True
if self.config.gui_LEAPP != int(values[self.gui_LEAPP]):
self.config.gui_LEAPP = int(values[self.gui_LEAPP])
changed = True
if self.config.gui_LEAP != values[self.gui_LEAP]:
self.config.gui_LEAP = values[self.gui_LEAP]
changed = True
if self.config.gui_skip_autoradius != values[self.gui_skip_autoradius]:
self.config.gui_skip_autoradius = values[self.gui_skip_autoradius]
changed = True
if self.config.gui_BLINK != values[self.gui_BLINK]:
self.config.gui_BLINK = values[self.gui_BLINK]
changed = True
if self.config.gui_IBO != values[self.gui_IBO]:
self.config.gui_IBO = values[self.gui_IBO]
changed = True
if self.config.gui_pupil_dilation != values[self.gui_pupil_dilation]:
self.config.gui_pupil_dilation = values[self.gui_pupil_dilation]
changed = True
if self.config.gui_RANSACBLINK != values[self.gui_RANSACBLINK]:
self.config.gui_RANSACBLINK = values[self.gui_RANSACBLINK]
changed = True
if self.config.gui_LEAP_lid != values[self.gui_LEAP_lid]:
self.config.gui_LEAP_lid = values[self.gui_LEAP_lid]
changed = True
if self.config.gui_circular_crop_left != values[self.gui_circular_crop_left]:
self.config.gui_circular_crop_left = values[self.gui_circular_crop_left]
changed = True
if self.config.gui_circular_crop_right != values[self.gui_circular_crop_right]:
self.config.gui_circular_crop_right = values[self.gui_circular_crop_right]
changed = True
if self.config.gui_HSF_radius_left != int(values[self.gui_HSF_radius_left]):
self.config.gui_HSF_radius_left = int(values[self.gui_HSF_radius_left])
changed = True
if self.config.gui_HSF_radius_right != int(values[self.gui_HSF_radius_right]):
self.config.gui_HSF_radius_right = int(values[self.gui_HSF_radius_right])
changed = True
if self.config.gui_BLOB != values[self.gui_BLOB]:
self.config.gui_BLOB = values[self.gui_BLOB]
changed = True
if self.config.gui_BLOBP != int(values[self.gui_BLOBP]):
self.config.gui_BLOBP = int(values[self.gui_BLOBP])
changed = True
if self.config.gui_threshold != values[self.gui_threshold_slider]:
self.config.gui_threshold = int(values[self.gui_threshold_slider])
changed = True
if self.config.gui_thresh_add != values[self.gui_thresh_add]:
self.config.gui_thresh_add = int(values[self.gui_thresh_add])
changed = True
if self.config.gui_blob_maxsize != values[self.gui_blob_maxsize]:
self.config.gui_blob_maxsize = values[self.gui_blob_maxsize]
changed = True
if self.config.ibo_filter_samples != int(values[self.ibo_filter_samples]):
self.config.ibo_filter_samples = int(values[self.ibo_filter_samples])
changed = True
if self.config.ibo_fully_close_eye_threshold != float(
values[self.ibo_fully_close_eye_threshold]
):
self.config.ibo_fully_close_eye_threshold = float(
values[self.ibo_fully_close_eye_threshold]
)
changed = True
if self.config.calibration_samples != int(values[self.calibration_samples]):
self.config.calibration_samples = int(values[self.calibration_samples])
changed = True
if self.config.gui_legacy_ransac_thresh_left != int(
values[self.gui_legacy_ransac_thresh_left]
):
self.config.gui_legacy_ransac_thresh_left = int(
values[self.gui_legacy_ransac_thresh_left]
)
changed = True
if self.config.gui_legacy_ransac_thresh_right != int(
values[self.gui_legacy_ransac_thresh_right]
):
self.config.gui_legacy_ransac_thresh_right = int(
values[self.gui_legacy_ransac_thresh_right]
)
changed = True
if changed:
self.main_config.save()
# print(self.main_config)
self.osc_queue.put(EyeId.ALGOSETTINGS)
super().__init__(widget_id, main_config, settings_modules)

View File

@ -91,8 +91,8 @@ class EyeTrackConfig(BaseModel):
right_eye: EyeTrackCameraConfig = EyeTrackCameraConfig()
left_eye: EyeTrackCameraConfig = EyeTrackCameraConfig()
settings: EyeTrackSettingsConfig = EyeTrackSettingsConfig()
# algo_settings: EyeTrackSettingsConfig = EyeTrackSettingsConfig()
eye_display_id: EyeId = EyeId.RIGHT
__listeners = []
@staticmethod
def load():
@ -117,6 +117,13 @@ class EyeTrackConfig(BaseModel):
load_config = EyeTrackConfig()
return load_config
def update(self, data, save=False):
for field, value in data.items():
setattr(self.settings, field, value)
self.__notify_listeners()
if save:
self.save()
def save(self):
# make sure this is only called if there is a change
if os.path.exists(CONFIG_FILE_NAME):
@ -130,5 +137,13 @@ class EyeTrackConfig(BaseModel):
# No backup because the saved settings file is broken.
pass
with open(CONFIG_FILE_NAME, "w") as settings_file:
json.dump(obj=self.dict(), fp=settings_file)
json.dump(obj=self.model_dump(warnings=False), fp=settings_file)
print(f"\033[92m[INFO] Config Saved Successfully\033[0m")
def register_listener_callback(self, callback):
print(f"[DEBUG] Registering listener {callback}")
self.__listeners.append(callback)
def __notify_listeners(self):
for listener in self.__listeners:
listener()

View File

@ -281,12 +281,12 @@ class EyeProcessor:
self.settings.ibo_average_output_samples,
)
if (
self.eyeopen < self.settings.ibo_fully_close_eye_threshold
self.eyeopen < float(self.settings.ibo_fully_close_eye_threshold)
): # threshold so the eye fully closes
self.eyeopen = 0.0
if self.bd_blink == True:
prin("blinks")
print("blinks")
pass
if self.settings.gui_IBO and self.eyeopen != 0.0:

View File

@ -90,8 +90,8 @@ def main():
]
settings = [
SettingsWidget(EyeId.SETTINGS, config, osc_queue),
AlgoSettingsWidget(EyeId.ALGOSETTINGS, config, osc_queue),
SettingsWidget(EyeId.SETTINGS, config),
AlgoSettingsWidget(EyeId.ALGOSETTINGS, config),
]
layout = [
@ -148,14 +148,14 @@ def main():
background_color="#424042",
),
sg.Column(
settings[0].widget_layout,
settings[0].get_layout(),
vertical_alignment="top",
key=SETTINGS_NAME,
visible=(config.eye_display_id in [EyeId.SETTINGS]),
background_color="#424042",
),
sg.Column(
settings[1].widget_layout,
settings[1].get_layout(),
vertical_alignment="top",
key=ALGO_SETTINGS_NAME,
visible=(config.eye_display_id in [EyeId.ALGOSETTINGS]),

View File

@ -1,340 +1,18 @@
import PySimpleGUI as sg
from config import EyeTrackSettingsConfig
from config import EyeTrackConfig
from osc import EyeId
from queue import Queue
from threading import Event
from settings.BaseSettings import BaseSettingsWidget
from settings.modules.GeneralSettingsModule import GeneralSettingsModule
from settings.modules.OneEuroSettingsModule import OneEuroSettingsModule
from settings.modules.OSCSettingsModule import OSCSettingsModule
class SettingsWidget:
def __init__(
self, widget_id: EyeId, main_config: EyeTrackSettingsConfig, osc_queue: Queue
):
self.gui_flip_x_axis_left = f"-FLIPXAXISLEFT{widget_id}-"
self.gui_flip_x_axis_right = f"-FLIPXAXISRIGHT{widget_id}-"
self.gui_flip_y_axis = f"-FLIPYAXIS{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_osc_receiver_port = f"OSCRECEIVERPORT{widget_id}-"
self.gui_osc_recenter_address = f"OSCRECENTERADDRESS{widget_id}-"
self.gui_osc_recalibrate_address = f"OSCRECALIBRATEADDRESS{widget_id}-"
self.gui_BLOB = f"-BLOBFALLBACK{widget_id}-"
self.gui_HSF = f"-HSF{widget_id}-"
self.gui_DADDY = f"-DADDY{widget_id}-"
self.gui_DADDYP = f"-DADDYP{widget_id}-"
self.gui_RANSAC3D = f"-RANSAC3D{widget_id}-"
self.gui_BLINK = f"-BLINK{widget_id}-"
self.gui_IBO = f"-IBO{widget_id}-"
self.gui_HSRAC = f"-HSRAC{widget_id}-"
self.gui_HSF_radius = f"-HSFRADIUS{widget_id}-"
self.gui_blob_maxsize = f"-BLOBMAXSIZE{widget_id}-"
self.gui_blob_minsize = f"-BLOBMINSIZE{widget_id}-"
self.gui_speed_coefficient = f"-SPEEDCOEFFICIENT{widget_id}-"
self.gui_min_cutoff = f"-MINCUTOFF{widget_id}-"
self.gui_eye_falloff = f"-EYEFALLOFF{widget_id}-"
self.gui_skip_autoradius = f"-SKIPAUTORADIUS{widget_id}-"
self.gui_HSRACP = f"-HSRACP{widget_id}-"
self.gui_RANSAC3DP = f"-RANSAC3DP{widget_id}-"
self.gui_HSFP = f"-HSFP{widget_id}-"
self.gui_BLOBP = f"-BLOBP{widget_id}-"
self.gui_thresh_add = f"-THRESHADD{widget_id}-"
self.gui_ROSC = f"-ROSC{widget_id}-"
self.gui_vrc_native = f"-VRCNATIVE{widget_id}-"
self.gui_circular_crop_left = f"-CIRCLECROPLEFT{widget_id}-"
self.gui_circular_crop_right = f"-CIRCLECROPRIGHT{widget_id}-"
self.gui_update_check = f"-UPDATECHECK{widget_id}-"
self.gui_threshold_slider = f"-BLOBTHRESHOLD{widget_id}-"
self.gui_osc_vrcft_v1 = f"-OSCVRCFTV1{widget_id}-"
self.gui_osc_vrcft_v2 = f"-OSCVRCFTV2{widget_id}-"
self.main_config = main_config
self.config = main_config.settings
self.osc_queue = osc_queue
# Define the window's contents
self.general_settings_layout = [
[
sg.Checkbox(
"Flip Left Eye X Axis",
default=self.config.gui_flip_x_axis_left,
key=self.gui_flip_x_axis_left,
background_color="#424042",
tooltip="Flips the left eye's X axis.",
),
sg.Checkbox(
"Flip Right Eye X Axis",
default=self.config.gui_flip_x_axis_right,
key=self.gui_flip_x_axis_right,
background_color="#424042",
tooltip="Flips the right eye's X axis.",
),
# ],
# [
sg.Checkbox(
"Flip Y Axis",
default=self.config.gui_flip_y_axis,
key=self.gui_flip_y_axis,
background_color="#424042",
tooltip="Flips the eye's Y axis.",
),
],
[
sg.Checkbox(
"Dual Eye Falloff",
default=self.config.gui_eye_falloff,
key=self.gui_eye_falloff,
background_color="#424042",
tooltip="If one eye stops tracking, we send tracking data from your other eye.",
),
],
[
sg.Checkbox(
"Check For Updates",
default=self.config.gui_update_check,
key=self.gui_update_check,
background_color="#424042",
tooltip="Toggle update check on launch.",
),
],
[
sg.Text("One Euro Filter Paramaters:", background_color="#242224"),
],
[
sg.Text("Min Frequency Cutoff", background_color="#424042"),
sg.InputText(
self.config.gui_min_cutoff,
key=self.gui_min_cutoff,
size=(0, 10),
),
# ],
# [
sg.Text("Speed Coefficient", background_color="#424042"),
sg.InputText(
self.config.gui_speed_coefficient,
key=self.gui_speed_coefficient,
size=(0, 10),
),
],
[
sg.Text("OSC Settings:", background_color="#242224"),
],
[
sg.Checkbox(
"VRC Native",
default=self.config.gui_vrc_native,
key=self.gui_vrc_native,
background_color="#424042",
tooltip="Toggle VRC's Native Eyetracking format.",
),
sg.Checkbox(
"VRCFT v1",
default=self.config.gui_osc_vrcft_v1,
key=self.gui_osc_vrcft_v1,
background_color="#424042",
tooltip="Toggle VRCFT's v1 Eyetracking format.",
),
sg.Checkbox(
"VRCFT v2 (UE)",
default=self.config.gui_osc_vrcft_v2,
key=self.gui_osc_vrcft_v2,
background_color="#424042",
tooltip="Toggle VRCFT's v2 (UE) Eyetracking format.",
),
],
[
sg.Text("Address:", background_color="#424042"),
sg.InputText(
self.config.gui_osc_address,
key=self.gui_osc_address,
size=(0, 20),
tooltip="IP address we send OSC data to.",
),
# ],
# [
sg.Text("Port:", background_color="#424042"),
sg.InputText(
self.config.gui_osc_port,
key=self.gui_osc_port,
size=(0, 10),
tooltip="OSC port we send data to.",
),
],
[
sg.Text("Receive functions", background_color="#424042"),
sg.Checkbox(
"",
default=self.config.gui_ROSC,
key=self.gui_ROSC,
background_color="#424042",
size=(0, 10),
tooltip="Toggle OSC receive functions.",
),
],
[
sg.Text("Receiver Port:", background_color="#424042"),
sg.InputText(
self.config.gui_osc_receiver_port,
key=self.gui_osc_receiver_port,
size=(0, 10),
tooltip="Port we receive OSC data from (used to recalibrate or recenter app from within VRChat.",
),
# ],
# [
sg.Text("Recenter Address:", background_color="#424042"),
sg.InputText(
self.config.gui_osc_recenter_address,
key=self.gui_osc_recenter_address,
size=(0, 10),
tooltip="OSC Address used for recentering your eye.",
),
],
[
sg.Text("Recalibrate Address:", background_color="#424042"),
sg.InputText(
self.config.gui_osc_recalibrate_address,
key=self.gui_osc_recalibrate_address,
size=(0, 10),
tooltip="OSC address we use for recalibrating your eye",
),
],
class SettingsWidget(BaseSettingsWidget):
def __init__(self, widget_id: EyeId, main_config: EyeTrackConfig):
settings_modules = [
GeneralSettingsModule,
OneEuroSettingsModule,
OSCSettingsModule,
]
super().__init__(widget_id, main_config, settings_modules)
self.widget_layout = [
[
sg.Text("General Settings:", background_color="#242224"),
],
[
sg.Column(
self.general_settings_layout,
key=self.gui_general_settings_layout,
background_color="#424042",
),
],
]
self.cancellation_event = (
Event()
) # Set the event until start is called, otherwise we can block if shutdown is called.
self.cancellation_event.set()
self.image_queue = Queue()
def started(self):
return not self.cancellation_event.is_set()
def start(self):
# If we're already running, bail
if not self.cancellation_event.is_set():
return
self.cancellation_event.clear()
def stop(self):
# If we're not running yet, bail
if self.cancellation_event.is_set():
return
self.cancellation_event.set()
def render(self, window, event, values):
# If anything has changed in our configuration settings, change/update those.
changed = False
if self.config.gui_osc_port != int(values[self.gui_osc_port]):
print(self.config.gui_osc_port, values[self.gui_osc_port])
try:
int(values[self.gui_osc_port])
if len(values[self.gui_osc_port]) <= 5:
self.config.gui_osc_port = int(values[self.gui_osc_port])
changed = True
else:
print(
"\033[91m[ERROR] OSC port value must be an integer 0-65535\033[0m"
)
except:
print(
"\033[91m[ERROR] OSC port value must be an integer 0-65535\033[0m"
)
if self.config.gui_osc_receiver_port != int(values[self.gui_osc_receiver_port]):
try:
int(values[self.gui_osc_receiver_port])
if len(values[self.gui_osc_receiver_port]) <= 5:
self.config.gui_osc_receiver_port = int(
values[self.gui_osc_receiver_port]
)
changed = True
else:
print(
"\033[91m[ERROR] OSC receive port value must be an integer 0-65535\033[0m"
)
except:
print(
"\033[91m[ERROR] OSC receive port value must be an integer 0-65535\033[0m"
)
if self.config.gui_osc_address != values[self.gui_osc_address]:
self.config.gui_osc_address = values[self.gui_osc_address]
changed = True
if (
self.config.gui_osc_recenter_address
!= values[self.gui_osc_recenter_address]
):
self.config.gui_osc_recenter_address = values[self.gui_osc_recenter_address]
changed = True
if (
self.config.gui_osc_recalibrate_address
!= values[self.gui_osc_recalibrate_address]
):
self.config.gui_osc_recalibrate_address = values[
self.gui_osc_recalibrate_address
]
changed = True
if self.config.gui_min_cutoff != values[self.gui_min_cutoff]:
self.config.gui_min_cutoff = values[self.gui_min_cutoff]
changed = True
if self.config.gui_speed_coefficient != values[self.gui_speed_coefficient]:
self.config.gui_speed_coefficient = values[self.gui_speed_coefficient]
changed = True
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]
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]
changed = True
if self.config.gui_osc_vrcft_v1 != values[self.gui_osc_vrcft_v1]:
self.config.gui_osc_vrcft_v1 = values[self.gui_osc_vrcft_v1]
changed = True
if self.config.gui_osc_vrcft_v2 != values[self.gui_osc_vrcft_v2]:
self.config.gui_osc_vrcft_v2 = values[self.gui_osc_vrcft_v2]
changed = True
if self.config.gui_vrc_native != values[self.gui_vrc_native]:
self.config.gui_vrc_native = values[self.gui_vrc_native]
changed = True
if self.config.gui_update_check != values[self.gui_update_check]:
self.config.gui_update_check = values[self.gui_update_check]
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]
changed = True
if self.config.gui_eye_falloff != values[self.gui_eye_falloff]:
self.config.gui_eye_falloff = values[self.gui_eye_falloff]
changed = True
if self.config.gui_ROSC != values[self.gui_ROSC]:
self.config.gui_ROSC = values[self.gui_ROSC]
changed = True
if changed:
self.main_config.save()
self.osc_queue.put((EyeId.SETTINGS))

View File

@ -1,6 +1,7 @@
from pythonosc import udp_client
from pythonosc import osc_server
from pythonosc import dispatcher
from config import EyeTrackConfig
from utils.misc_utils import PlaySound, SND_FILENAME, SND_ASYNC
import queue
import threading
@ -16,7 +17,6 @@ class EyeId(IntEnum):
ALGOSETTINGS = 4
from config import EyeTrackConfig
def eyelid_transformer(self, eye_blink):

View File

@ -0,0 +1,138 @@
from datetime import datetime, timedelta
from typing import Iterable
import PySimpleGUI as sg
from colorama import Fore
from config import EyeTrackConfig, EyeTrackSettingsConfig
from threading import Event
from osc import EyeId # TODO this is bad, fix this
class BaseSettingsWidget:
def __init__(
self,
widget_id: EyeId,
main_config: EyeTrackConfig,
settings_modules: Iterable,
):
# we put the last time an error was printed in the past so whenever a newone appears
self.last_error_printout = datetime.now() - timedelta(seconds=20)
self.error_printout_timeout = 2
self.gui_general_settings_layout = f"-GENERALSETTINGSLAYOUT{widget_id}-"
self.reset_button_key = f"RESET_SETTINGS{widget_id}"
self.is_saving = False
self.main_config = main_config
self.config = main_config.settings
self.initialized_modules = self._initialize_modules(
settings_modules=settings_modules, widget_id=widget_id
)
self.general_settings_layout = []
for module in self.initialized_modules:
self.general_settings_layout.extend(module.get_layout())
self.widget_layout = [
[
sg.Column(
self.general_settings_layout,
key=self.gui_general_settings_layout,
background_color="#424042",
),
],
[sg.Text("", background_color="#424042"), ],
[sg.Button("Reset settings to default", key=self.reset_button_key, button_color="#c40e23")],
]
self.cancellation_event = (
Event()
) # Set the event until start is called, otherwise we can block if shutdown is called.
self.cancellation_event.set()
def started(self):
return not self.cancellation_event.is_set()
def start(self):
# If we're already running, bail
if not self.cancellation_event.is_set():
return
self.cancellation_event.clear()
def stop(self):
# If we're not running yet, bail
if self.cancellation_event.is_set():
return
self.cancellation_event.set()
def _update_and_save_config(self, validated_data: dict):
self.main_config.update(validated_data, save=True)
self.is_saving = False
def _handle_errors(self, errors):
now = datetime.now()
elapsed_seconds = (datetime.now() - self.last_error_printout).seconds
if elapsed_seconds > self.error_printout_timeout:
self.last_error_printout = now
messages = []
for module_errors in errors:
for error in module_errors:
messages.append(f"{Fore.RED}[ERROR]{Fore.RESET} {error['msg']} \n")
print("".join(messages))
def render(self, window, event, values):
validated_data, errors = {}, []
# we might want to think about event driven architecture here eventually, validate only
# if anything changes instead of checking for changes
for module in self.initialized_modules:
module_validated_data = module.validate(values)
if module_validated_data.changes:
validated_data.update(module_validated_data.changes)
if module_validated_data.errors:
errors.append(module_validated_data.errors)
if not errors and validated_data and not self.is_saving:
self.is_saving = True
self._update_and_save_config(validated_data)
if errors:
self._handle_errors(errors)
self.handle_events(event, window)
def _initialize_modules(self, settings_modules, widget_id):
return [
module(
config=self.config,
settings=self.main_config,
widget_id=widget_id,
)
for module in settings_modules
]
def get_layout(self) -> Iterable:
return self.widget_layout
def handle_events(self, event, window):
if event == "__TIMEOUT__":
return
if event == self.reset_button_key:
self.reset_config(window)
def reset_config(self, window):
default_values = {}
base_settings = EyeTrackSettingsConfig()
print(f"\033[92m[INFO] Resetting config to defaults\033[0m")
for module in self.initialized_modules:
for key in module.get_key_for_panel_defaults():
default_val = getattr(base_settings, key)
widget_key = getattr(module, key)
default_values[key] = default_val
window[widget_key].update(default_val)
print(f"\033[92m[INFO] Config reset, saving\033[0m")
self._update_and_save_config(default_values)

View File

@ -0,0 +1 @@
BACKGROUND_COLOR = "#424042"

View File

@ -0,0 +1,137 @@
from settings.modules.BaseModule import BaseSettingsModule, BaseValidationModel
import PySimpleGUI as sg
class AdvancedTrackingAlgoSettingsValidationModel(BaseValidationModel):
gui_HSF_radius_left: int
gui_HSF_radius_right: int
gui_blob_maxsize: int
gui_blob_minsize: int
gui_legacy_ransac_thresh_left: int
gui_legacy_ransac_thresh_right: int
gui_skip_autoradius: bool
gui_thresh_add: int
gui_threshold: int
gui_pupil_dilation: bool
class AdvancedTrackingAlgoSettingsModule(BaseSettingsModule):
def __init__(self, config, widget_id, **kwargs):
super().__init__(config=config, widget_id=widget_id, **kwargs)
self.validation_model = AdvancedTrackingAlgoSettingsValidationModel
self.gui_blob_maxsize = f"-BLOBMAXSIZE{widget_id}-"
self.gui_blob_minsize = f"-BLOBMINSIZE{widget_id}-"
self.gui_skip_autoradius = f"-SKIPAUTORADIUS{widget_id}-"
self.gui_thresh_add = f"-THRESHADD{widget_id}-"
self.gui_threshold = f"-BLOBTHRESHOLD{widget_id}-"
self.gui_HSF_radius_left = f"-HSFRADIUSLEFT{widget_id}-"
self.gui_HSF_radius_right = f"-HSFRADIUSRIGHT{widget_id}-"
self.gui_legacy_ransac_thresh_right = f"-THRESHRIGHT{widget_id}-"
self.gui_legacy_ransac_thresh_left = f"-THRESHLEFT{widget_id}-"
self.gui_pupil_dilation = f"-EBPD{widget_id}-"
def get_layout(self):
return [
[sg.Text("Pupil Dilation Algo Settings:", background_color="#242224")],
[
sg.Checkbox(
"Ellipse Based Pupil Dilation",
default=self.config.gui_pupil_dilation,
key=self.gui_pupil_dilation,
background_color="#424042",
)
],
[sg.Text("Advanced Tracking Algorithm Settings:", background_color="#242224")],
[
sg.Checkbox(
"HSF: Skip Auto Radius",
default=self.config.gui_skip_autoradius,
key=self.gui_skip_autoradius,
background_color="#424042",
tooltip="To gain more control and possibly better tracking quality of HSF, please disable auto radius to enable manual adjustment.",
),
],
[
sg.Text("Left HSF Radius:", background_color="#424042"),
sg.Slider(
range=(1, 50),
default_value=self.config.gui_HSF_radius_left,
orientation="h",
key=self.gui_HSF_radius_left,
background_color="#424042",
tooltip="Adjusts the radius parameter for HSF. Only adjust if you are having tracking issues.",
),
sg.Text("Right HSF Radius:", background_color="#424042"),
sg.Slider(
range=(1, 50),
default_value=self.config.gui_HSF_radius_right,
orientation="h",
key=self.gui_HSF_radius_right,
background_color="#424042",
tooltip="Adjusts the radius parameter for HSF. Only adjust if you are having tracking issues.",
),
],
[
sg.Text("RANSAC Thresh Add", background_color="#424042"),
sg.Slider(
range=(1, 50),
default_value=self.config.gui_thresh_add,
orientation="h",
key=self.gui_thresh_add,
background_color="#424042",
tooltip="Adjusts the amount of threshold to add to RANSAC. Useful for fine tuning your setup.",
),
sg.Text("Blob Threshold", background_color="#424042"),
# TODO make this for right and left eyes? I dont know how vital that is..
sg.Slider(
range=(0, 110),
default_value=self.config.gui_threshold,
orientation="h",
key=self.gui_threshold,
background_color="#424042",
tooltip="Adjusts the threshold for blob tracking.",
),
],
[
sg.Text("Min Blob Size:", background_color="#424042"),
sg.Slider(
range=(1, 50),
default_value=self.config.gui_blob_minsize,
orientation="h",
key=self.gui_blob_minsize,
background_color="#424042",
tooltip="Minimum size a blob has to be for blob tracking.",
),
sg.Text("Max Blob Size:", background_color="#424042"),
sg.Slider(
range=(1, 50),
default_value=self.config.gui_blob_maxsize,
orientation="h",
key=self.gui_blob_maxsize,
background_color="#424042",
tooltip="Maximum size a blob can be for blob tracking.",
),
],
[
sg.Text("Right Eye Thresh:", background_color="#424042"),
sg.Slider(
range=(1, 120),
default_value=self.config.gui_legacy_ransac_thresh_right,
orientation="h",
key=self.gui_legacy_ransac_thresh_right,
background_color="#424042",
tooltip="Threshold for right eye, legacy RANSAC only",
),
sg.Text("Left Eye Thresh:", background_color="#424042"),
sg.Slider(
range=(1, 120),
default_value=self.config.gui_legacy_ransac_thresh_left,
orientation="h",
key=self.gui_legacy_ransac_thresh_left,
background_color="#424042",
tooltip="Threshold for left eye, legacy RANSAC only",
),
],
]

View File

@ -0,0 +1,68 @@
from typing import Iterable, Optional, NamedTuple, Any
import pydantic
from pydantic import BaseModel
from config import EyeTrackSettingsConfig
class MissingValidationModelException(Exception):
pass
class MissingValidationConfigException(Exception):
pass
class BaseValidationModel(BaseModel):
pass
class ValidationResult(NamedTuple):
changes: Optional[dict[str, str]]
errors: list[Any] # ErrorDict but we cannot import it, it's not exposed
class BaseSettingsModule:
def __init__(self, config: EyeTrackSettingsConfig, widget_id, **kwargs):
self.validation_model: BaseValidationModel = BaseValidationModel # noqa
self.config = config
self.widget_id = widget_id
def get_validation_model(self):
"""Return validation model, can be overridden for custom behaviour"""
return self.validation_model
def initialize_validation_model(self, values):
validation_model = self.get_validation_model()
if not validation_model:
raise MissingValidationModelException()
field_mapping = {}
for field in self.validation_model.model_fields.keys():
field_mapping[field] = values[getattr(self, field)]
return validation_model(**field_mapping)
def validate(self, values, raise_exception=False) -> Optional[ValidationResult]:
"""Return """
if not self.config:
raise MissingValidationConfigException()
try:
changes = {}
validated_model = self.initialize_validation_model(values)
for field, value in validated_model.model_dump().items():
if getattr(self.config, field) != value:
changes[field] = value
return ValidationResult(changes, [])
except pydantic.ValidationError as e:
if not raise_exception:
return ValidationResult(None, e.errors())
raise
def get_layout(self) -> Iterable:
raise NotImplementedError
def get_key_for_panel_defaults(self) -> dict:
return self.validation_model.schema().get("properties").keys()

View File

@ -0,0 +1,100 @@
from pydantic import AfterValidator
from typing_extensions import Annotated
from settings.modules.BaseModule import BaseSettingsModule, BaseValidationModel
import PySimpleGUI as sg
from settings.modules.CommonFieldValidators import check_is_float_convertible
class BlinkAlgoSettingsValidationModel(BaseValidationModel):
gui_IBO: bool
gui_RANSACBLINK: bool
gui_BLINK: bool
gui_LEAP_lid: bool
ibo_filter_samples: int
calibration_samples: int
ibo_fully_close_eye_threshold: Annotated[str, AfterValidator(check_is_float_convertible)]
gui_circular_crop_left: bool
gui_circular_crop_right: bool
class BlinkAlgoSettingsModule(BaseSettingsModule):
def __init__(self, config, widget_id, **kwargs):
super().__init__(config=config, widget_id=widget_id, **kwargs)
self.validation_model = BlinkAlgoSettingsValidationModel
self.gui_IBO = f"-IBO{widget_id}-"
self.gui_RANSACBLINK = f"-RANSACBLINK{widget_id}-"
self.gui_BLINK = f"-BLINK{widget_id}-"
self.gui_LEAP_lid = f"-LEAPLID{widget_id}-"
self.ibo_filter_samples = f"-IBOFILTERSAMPLE{widget_id}-"
self.calibration_samples = f"-CALIBRATIONSAMPLES{widget_id}-"
self.ibo_fully_close_eye_threshold = f"-CLOSETHRESH{widget_id}-"
self.gui_circular_crop_left = f"-CIRCLECROPLEFT{widget_id}-"
self.gui_circular_crop_right = f"-CIRCLECROPRIGHT{widget_id}-"
def get_layout(self):
return [
[sg.Text("Blink Algo Settings:", background_color="#242224")],
[
sg.Checkbox(
"Intensity Based Openness",
default=self.config.gui_IBO,
key=self.gui_IBO,
background_color="#424042",
),
sg.Checkbox(
"RANSAC Quick Blink Algo",
default=self.config.gui_RANSACBLINK,
key=self.gui_RANSACBLINK,
background_color="#424042",
),
sg.Checkbox(
"Binary Blink Algo",
default=self.config.gui_BLINK,
key=self.gui_BLINK,
background_color="#424042",
),
sg.Checkbox(
"LEAP Lid",
default=self.config.gui_LEAP_lid,
key=self.gui_LEAP_lid,
background_color="#424042",
),
],
[
sg.Text("IBO Filter Sample Size", background_color="#424042"),
sg.InputText(
self.config.ibo_filter_samples,
key=self.ibo_filter_samples,
size=(0, 10),
),
sg.Text("Calibration Samples", background_color="#424042"),
sg.InputText(
self.config.calibration_samples,
key=self.calibration_samples,
size=(0, 10),
),
sg.Text("IBO Close Threshold", background_color="#424042"),
sg.InputText(
self.config.ibo_fully_close_eye_threshold,
key=self.ibo_fully_close_eye_threshold,
size=(0, 10),
),
],
[
sg.Checkbox(
"Left Eye Circle crop",
default=self.config.gui_circular_crop_left,
key=self.gui_circular_crop_left,
background_color="#424042",
),
sg.Checkbox(
"Right Eye Circle crop",
default=self.config.gui_circular_crop_right,
key=self.gui_circular_crop_right,
background_color="#424042",
),
],
]

View File

@ -0,0 +1,11 @@
def check_is_float_convertible(v: str):
"""Check if value provided can be converted to a float or double.
PySimpleGUI does not support floats or doubles in UI, so we have to make sure
that what the user typed in is correct
"""
try:
float(v)
return v
except ValueError:
raise ValueError("Please provide a proper number")

View File

@ -0,0 +1,70 @@
from config import EyeTrackSettingsConfig
from settings.modules.BaseModule import BaseSettingsModule, BaseValidationModel
import PySimpleGUI as sg
class GeneralSettingsValidationModel(BaseValidationModel):
gui_flip_x_axis_left: bool
gui_flip_x_axis_right: bool
gui_flip_y_axis: bool
gui_eye_falloff: bool
gui_update_check: bool
class GeneralSettingsModule(BaseSettingsModule):
def __init__(self, config, widget_id, **kwargs):
super().__init__(config=config, widget_id=widget_id, **kwargs)
self.validation_model = GeneralSettingsValidationModel
self.gui_flip_x_axis_left = f"-FLIPXAXISLEFT{widget_id}-"
self.gui_flip_x_axis_right = f"-FLIPXAXISRIGHT{widget_id}-"
self.gui_flip_y_axis = f"-FLIPYAXIS{widget_id}-"
self.gui_eye_falloff = f"-EYEFALLOFF{widget_id}-"
self.gui_update_check = f"-UPDATECHECK{widget_id}-"
def get_layout(self):
return [
[
sg.Text("General Settings:", background_color="#242224"),
],
[
sg.Checkbox(
"Flip Left Eye X Axis",
default=self.config.gui_flip_x_axis_left,
key=self.gui_flip_x_axis_left,
background_color="#424042",
tooltip="Flips the left eye's X axis.",
),
sg.Checkbox(
"Flip Right Eye X Axis",
default=self.config.gui_flip_x_axis_right,
key=self.gui_flip_x_axis_right,
background_color="#424042",
tooltip="Flips the right eye's X axis.",
),
sg.Checkbox(
"Flip Y Axis",
default=self.config.gui_flip_y_axis,
key=self.gui_flip_y_axis,
background_color="#424042",
tooltip="Flips the eye's Y axis.",
),
],
[
sg.Checkbox(
"Dual Eye Falloff",
default=self.config.gui_eye_falloff,
key=self.gui_eye_falloff,
background_color="#424042",
tooltip="If one eye stops tracking, we send tracking data from your other eye.",
),
],
[
sg.Checkbox(
"Check For Updates",
default=self.config.gui_update_check,
key=self.gui_update_check,
background_color="#424042",
tooltip="Toggle update check on launch.",
),
],
]

View File

@ -0,0 +1,126 @@
from pydantic import model_validator
from settings.modules.BaseModule import BaseSettingsModule, BaseValidationModel
from settings.constants import BACKGROUND_COLOR
import PySimpleGUI as sg
class OSCValidationModel(BaseValidationModel):
gui_osc_port: int
gui_osc_address: str
gui_ROSC: bool
gui_osc_receiver_port: int
gui_osc_recenter_address: str
gui_osc_recalibrate_address: str
gui_vrc_native: bool
gui_osc_vrcft_v1: bool
gui_osc_vrcft_v2: bool
@model_validator(mode="after")
def check_osc_vrcft_versions(self):
if self.gui_osc_vrcft_v1 and self.gui_osc_vrcft_v2:
raise ValueError("Only one version of VRCFT params can be turned on")
return self
@model_validator(mode="after")
def check_osc_output_mode(self):
if self.gui_vrc_native and any([self.gui_osc_vrcft_v1, self.gui_osc_vrcft_v2]):
raise ValueError("Either VRCNative or VRCFT output can be active at a time")
return self
class OSCSettingsModule(BaseSettingsModule):
def __init__(self, config, widget_id, **kwargs):
super().__init__(config=config, widget_id=widget_id, **kwargs)
self.validation_model = OSCValidationModel
self.gui_osc_address = f"-OSCADDRESS{widget_id}-"
self.gui_osc_port = f"-OSCPORT{widget_id}-"
self.gui_ROSC = f"-ROSC{widget_id}-"
self.gui_osc_receiver_port = f"OSCRECEIVERPORT{widget_id}-"
self.gui_osc_recenter_address = f"OSCRECENTERADDRESS{widget_id}-"
self.gui_osc_recalibrate_address = f"OSCRECALIBRATEADDRESS{widget_id}-"
self.gui_vrc_native = f"-VRCNATIVE{widget_id}-"
self.gui_osc_vrcft_v1 = f"-OSCVRCFTV1{widget_id}-"
self.gui_osc_vrcft_v2 = f"-OSCVRCFTV2{widget_id}-"
def get_layout(self):
return [
[
sg.Text("OSC Settings:", background_color="#242224"),
],
[
sg.Checkbox(
"VRC Native Eyetracking",
default=self.config.gui_vrc_native,
key=self.gui_vrc_native,
background_color="#424042",
tooltip="Toggle VRCFT output or VRC native",
),
sg.Checkbox(
"VRCFT v1",
default=self.config.gui_osc_vrcft_v1,
key=self.gui_osc_vrcft_v1,
background_color="#424042",
tooltip="Toggle VRCFT's v1 Eyetracking format.",
),
sg.Checkbox(
"VRCFT v2 (UE)",
default=self.config.gui_osc_vrcft_v2,
key=self.gui_osc_vrcft_v2,
background_color="#424042",
tooltip="Toggle VRCFT's v2 (UE) Eyetracking format.",
),
],
[
sg.Text("Address:", background_color=BACKGROUND_COLOR),
sg.InputText(
self.config.gui_osc_address,
key=self.gui_osc_address,
size=(0, 20),
tooltip="IP address we send OSC data to.",
),
sg.Text("Port:", background_color=BACKGROUND_COLOR),
sg.InputText(
self.config.gui_osc_port,
key=self.gui_osc_port,
size=(0, 10),
tooltip="OSC port we send data to.",
),
],
[
sg.Text("Receive functions", background_color=BACKGROUND_COLOR),
sg.Checkbox(
"",
default=self.config.gui_ROSC,
key=self.gui_ROSC,
background_color=BACKGROUND_COLOR,
size=(0, 10),
tooltip="Toggle OSC receive functions.",
),
],
[
sg.Text("Receiver Port:", background_color=BACKGROUND_COLOR),
sg.InputText(
self.config.gui_osc_receiver_port,
key=self.gui_osc_receiver_port,
size=(0, 10),
tooltip="Port we receive OSC data from (used to recalibrate or recenter app from within VRChat.",
),
sg.Text("Recenter Address:", background_color=BACKGROUND_COLOR),
sg.InputText(
self.config.gui_osc_recenter_address,
key=self.gui_osc_recenter_address,
size=(0, 10),
tooltip="OSC Address used for recentering your eye.",
),
],
[
sg.Text("Recalibrate Address:", background_color=BACKGROUND_COLOR),
sg.InputText(
self.config.gui_osc_recalibrate_address,
key=self.gui_osc_recalibrate_address,
size=(0, 10),
tooltip="OSC address we use for recalibrating your eye",
),
],
]

View File

@ -0,0 +1,42 @@
from pydantic import AfterValidator
from typing_extensions import Annotated
from settings.modules.BaseModule import BaseSettingsModule, BaseValidationModel
from settings.constants import BACKGROUND_COLOR
import PySimpleGUI as sg
from settings.modules.CommonFieldValidators import check_is_float_convertible
class OneEuroFilterValidationModel(BaseValidationModel):
gui_speed_coefficient: Annotated[str, AfterValidator(check_is_float_convertible)]
gui_min_cutoff: Annotated[str, AfterValidator(check_is_float_convertible)]
class OneEuroSettingsModule(BaseSettingsModule):
def __init__(self, config, widget_id, **kwargs):
super().__init__(config=config, widget_id=widget_id, **kwargs)
self.gui_speed_coefficient = f"-SPEEDCOEFFICIENT{widget_id}-"
self.gui_min_cutoff = f"-MINCUTOFF{widget_id}-"
self.validation_model = OneEuroFilterValidationModel
def get_layout(self):
return [
[
sg.Text("One Euro Filter Paramaters:", background_color='#242224'),
],
[
sg.Text("Min Frequency Cutoff", background_color=BACKGROUND_COLOR),
sg.InputText(
self.config.gui_min_cutoff,
key=self.gui_min_cutoff,
size=(0, 10),
),
sg.Text("Speed Coefficient", background_color=BACKGROUND_COLOR),
sg.InputText(
self.config.gui_speed_coefficient,
key=self.gui_speed_coefficient,
size=(0, 10),
),
],
]

View File

@ -0,0 +1,190 @@
from pydantic import model_validator
from settings.modules.BaseModule import BaseSettingsModule, BaseValidationModel
import PySimpleGUI as sg
class TrackingAlgorithmValidationModel(BaseValidationModel):
gui_BLOB: bool
gui_DADDY: bool
gui_HSF: bool
gui_HSRAC: bool
gui_LEAP: bool
gui_RANSAC3D: bool
gui_legacy_ransac: bool
gui_BLOBP: int
gui_DADDYP: int
gui_HSFP: int
gui_HSRACP: int
gui_LEAPP: int
gui_RANSAC3DP: int
@model_validator(mode="after")
def check_algorith_order(self):
algos_list = [
self.gui_BLOBP,
self.gui_DADDYP,
self.gui_HSFP,
self.gui_HSRACP,
self.gui_LEAPP,
self.gui_RANSAC3DP,
]
algos_set = set(algos_list)
if len(algos_set) != len(algos_list):
raise ValueError("Please fixup the algorithm order, some algos are doubled")
return self
class TrackingAlgorithmModule(BaseSettingsModule):
def __init__(self, config, widget_id, **kwargs):
super().__init__(config=config, widget_id=widget_id, **kwargs)
self.algo_count = ["1", "2", "3", "4", "5", "6"]
self.validation_model = TrackingAlgorithmValidationModel
self.gui_BLOB = f"-BLOBFALLBACK{widget_id}-"
self.gui_DADDY = f"-DADDY{widget_id}-"
self.gui_HSF = f"-HSF{widget_id}-"
self.gui_HSRAC = f"-HSRAC{widget_id}-"
self.gui_LEAP = f"-LEAP{widget_id}-"
self.gui_RANSAC3D = f"-RANSAC3D{widget_id}-"
self.gui_legacy_ransac = f"-LEGACYRANSACTHRESH{widget_id}-"
self.gui_BLOBP = f"-BLOBP{widget_id}-"
self.gui_DADDYP = f"-DADDYP{widget_id}-"
self.gui_HSFP = f"-HSFP{widget_id}-"
self.gui_HSRACP = f"-HSRACP{widget_id}-"
self.gui_LEAPP = f"-LEAPP{widget_id}-"
self.gui_RANSAC3DP = f"-RANSAC3DP{widget_id}-"
# TODO custom validation, make a set of values, count if there's less than overall, if yeah we have a problem
def get_layout(self):
return [
[
sg.Text(
"Tracking Algorithm Order Settings:",
background_color="#242224",
)
],
[
sg.Checkbox(
"",
default=self.config.gui_HSRAC,
key=self.gui_HSRAC,
background_color="#424042",
tooltip="Our flagship algorithm, utilizing both HSF and RANSAC for best tracking quality and lighting resistance.",
),
sg.Combo(
self.algo_count,
default_value=self.config.gui_HSRACP,
key=self.gui_HSRACP,
background_color="#424042",
text_color="white",
button_arrow_color="black",
button_background_color="#6f4ca1",
tooltip="Select the priority of eyetracking algorithms.",
),
sg.Text("HSRAC", background_color="#424042"),
sg.Checkbox(
"",
default=self.config.gui_HSF,
key=self.gui_HSF,
background_color="#424042",
tooltip="HSF Is a new, lower resolution tracking algorithim that provides excelent resilancy to lighting conditions and great speed.",
),
sg.Combo(
self.algo_count,
default_value=self.config.gui_HSFP,
key=self.gui_HSFP,
background_color="#424042",
text_color="white",
button_arrow_color="black",
button_background_color="#6f4ca1",
tooltip="Select the priority of eyetracking algorithims.",
),
sg.Text("Haar Surround Feature", background_color="#424042"),
],
[
sg.Checkbox(
"",
default=self.config.gui_DADDY,
key=self.gui_DADDY,
background_color="#424042",
tooltip="DADDY Uses a Deep learning algorithm. This has a big CPU usage impact.",
),
sg.Combo(
self.algo_count,
default_value=self.config.gui_DADDYP,
key=self.gui_DADDYP,
background_color="#424042",
text_color="white",
button_arrow_color="black",
button_background_color="#6f4ca1",
tooltip="Select the priority of eyetracking algorithms.",
),
sg.Text("DADDY", background_color="#424042"),
# ],
# [
sg.Checkbox(
"",
default=self.config.gui_RANSAC3D,
key=self.gui_RANSAC3D,
background_color="#424042",
tooltip="RANSAC3D provides good tracking quality, however does not do well in bad lighting conditions.",
),
sg.Combo(
self.algo_count,
default_value=self.config.gui_RANSAC3DP,
key=self.gui_RANSAC3DP,
background_color="#424042",
text_color="white",
button_arrow_color="black",
button_background_color="#6f4ca1",
tooltip="Select the priority of eyetracking algorithms.",
),
sg.Text("RANSAC 3D", background_color="#424042"),
sg.Checkbox(
"Legacy RANSAC Thresh",
default=self.config.gui_legacy_ransac,
key=self.gui_legacy_ransac,
background_color="#424042",
),
],
[
sg.Checkbox(
"",
default=self.config.gui_LEAP,
key=self.gui_LEAP,
background_color="#424042",
tooltip="LEAP Uses a lightweight deep learning algorithm.",
),
sg.Combo(
self.algo_count,
default_value=self.config.gui_LEAPP,
key=self.gui_LEAPP,
background_color="#424042",
text_color="white",
button_arrow_color="black",
button_background_color="#6f4ca1",
tooltip="Select the priority of eyetracking algorithms.",
),
sg.Text("LEAP", background_color="#424042"),
sg.Checkbox(
"",
default=self.config.gui_BLOB,
key=self.gui_BLOB,
background_color="#424042",
tooltip="Blob tracking is the oldest and worst tracking algorithm, it provides fast, though sometimes inaccurate tracking.",
),
sg.Combo(
self.algo_count,
default_value=self.config.gui_BLOBP,
key=self.gui_BLOBP,
background_color="#424042",
text_color="white",
button_arrow_color="black",
button_background_color="#6f4ca1",
tooltip="Select the priority of eyetracking algorithms.",
),
sg.Text("Blob", background_color="#424042"),
],
]

249
poetry.lock generated
View File

@ -1,10 +1,9 @@
# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]]
name = "altgraph"
version = "0.17.3"
description = "Python graph (network) package"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -12,11 +11,21 @@ files = [
{file = "altgraph-0.17.3.tar.gz", hash = "sha256:ad33358114df7c9416cdb8fa1eaa5852166c505118717021c6a8c7c7abbd03dd"},
]
[[package]]
name = "annotated-types"
version = "0.6.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
files = [
{file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
{file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
]
[[package]]
name = "black"
version = "22.12.0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -39,7 +48,6 @@ click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
@ -51,7 +59,6 @@ uvloop = ["uvloop (>=0.15.2)"]
name = "certifi"
version = "2023.5.7"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = ">=3.6"
files = [
@ -63,7 +70,6 @@ files = [
name = "charset-normalizer"
version = "3.1.0"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
python-versions = ">=3.7.0"
files = [
@ -148,7 +154,6 @@ files = [
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -163,7 +168,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
@ -175,7 +179,6 @@ files = [
name = "coloredlogs"
version = "15.0.1"
description = "Colored terminal output for Python's logging module"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
@ -193,7 +196,6 @@ cron = ["capturer (>=2.4)"]
name = "flake8"
version = "5.0.4"
description = "the modular source code checker: pep8 pyflakes and co"
category = "dev"
optional = false
python-versions = ">=3.6.1"
files = [
@ -210,7 +212,6 @@ pyflakes = ">=2.5.0,<2.6.0"
name = "flatbuffers"
version = "23.5.26"
description = "The FlatBuffers serialization format for Python"
category = "main"
optional = false
python-versions = "*"
files = [
@ -222,7 +223,6 @@ files = [
name = "humanfriendly"
version = "10.0"
description = "Human friendly output for text interfaces using Python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
@ -237,7 +237,6 @@ pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_ve
name = "idna"
version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=3.5"
files = [
@ -249,7 +248,6 @@ files = [
name = "macholib"
version = "1.16.2"
description = "Mach-O header analysis and editing"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -264,7 +262,6 @@ altgraph = ">=0.17"
name = "mccabe"
version = "0.7.0"
description = "McCabe checker, plugin for flake8"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@ -276,7 +273,6 @@ files = [
name = "mpmath"
version = "1.3.0"
description = "Python library for arbitrary-precision floating-point arithmetic"
category = "main"
optional = false
python-versions = "*"
files = [
@ -294,7 +290,6 @@ tests = ["pytest (>=4.6)"]
name = "msgpack"
version = "1.0.5"
description = "MessagePack serializer"
category = "main"
optional = false
python-versions = "*"
files = [
@ -367,7 +362,6 @@ files = [
name = "mslex"
version = "0.3.0"
description = "shlex for windows"
category = "main"
optional = false
python-versions = ">=3.5"
files = [
@ -379,7 +373,6 @@ files = [
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
category = "dev"
optional = false
python-versions = ">=3.5"
files = [
@ -391,7 +384,6 @@ files = [
name = "numpy"
version = "1.23.5"
description = "NumPy is the fundamental package for array computing with Python."
category = "main"
optional = false
python-versions = ">=3.8"
files = [
@ -429,7 +421,6 @@ files = [
name = "onnxruntime"
version = "1.15.1"
description = "ONNX Runtime is a runtime accelerator for Machine Learning models"
category = "main"
optional = false
python-versions = "*"
files = [
@ -471,7 +462,6 @@ sympy = "*"
name = "opencv-python"
version = "4.7.0.72"
description = "Wrapper package for OpenCV python bindings."
category = "main"
optional = false
python-versions = ">=3.6"
files = [
@ -485,19 +475,12 @@ files = [
]
[package.dependencies]
numpy = [
{version = ">=1.21.2", markers = "python_version >= \"3.10\""},
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""},
{version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""},
{version = ">=1.17.0", markers = "python_version >= \"3.7\""},
{version = ">=1.17.3", markers = "python_version >= \"3.8\""},
]
numpy = {version = ">=1.22.0", markers = "python_version >= \"3.11\""}
[[package]]
name = "packaging"
version = "23.1"
description = "Core utilities for Python packages"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@ -509,7 +492,6 @@ files = [
name = "pathspec"
version = "0.11.1"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -521,7 +503,6 @@ files = [
name = "pefile"
version = "2023.2.7"
description = "Python PE parsing module"
category = "dev"
optional = false
python-versions = ">=3.6.0"
files = [
@ -533,7 +514,6 @@ files = [
name = "platformdirs"
version = "3.8.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -549,7 +529,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-
name = "protobuf"
version = "4.23.3"
description = ""
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@ -572,7 +551,6 @@ files = [
name = "psutil"
version = "5.9.5"
description = "Cross-platform lib for process and system monitoring in Python."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
@ -599,7 +577,6 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
name = "pycodestyle"
version = "2.9.1"
description = "Python style guide checker"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@ -609,62 +586,145 @@ files = [
[[package]]
name = "pydantic"
version = "1.10.9"
description = "Data validation and settings management using python type hints"
category = "main"
version = "2.4.2"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.7"
files = [
{file = "pydantic-1.10.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e692dec4a40bfb40ca530e07805b1208c1de071a18d26af4a2a0d79015b352ca"},
{file = "pydantic-1.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c52eb595db83e189419bf337b59154bdcca642ee4b2a09e5d7797e41ace783f"},
{file = "pydantic-1.10.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:939328fd539b8d0edf244327398a667b6b140afd3bf7e347cf9813c736211896"},
{file = "pydantic-1.10.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b48d3d634bca23b172f47f2335c617d3fcb4b3ba18481c96b7943a4c634f5c8d"},
{file = "pydantic-1.10.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f0b7628fb8efe60fe66fd4adadd7ad2304014770cdc1f4934db41fe46cc8825f"},
{file = "pydantic-1.10.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e1aa5c2410769ca28aa9a7841b80d9d9a1c5f223928ca8bec7e7c9a34d26b1d4"},
{file = "pydantic-1.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:eec39224b2b2e861259d6f3c8b6290d4e0fbdce147adb797484a42278a1a486f"},
{file = "pydantic-1.10.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d111a21bbbfd85c17248130deac02bbd9b5e20b303338e0dbe0faa78330e37e0"},
{file = "pydantic-1.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e9aec8627a1a6823fc62fb96480abe3eb10168fd0d859ee3d3b395105ae19a7"},
{file = "pydantic-1.10.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07293ab08e7b4d3c9d7de4949a0ea571f11e4557d19ea24dd3ae0c524c0c334d"},
{file = "pydantic-1.10.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee829b86ce984261d99ff2fd6e88f2230068d96c2a582f29583ed602ef3fc2c"},
{file = "pydantic-1.10.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4b466a23009ff5cdd7076eb56aca537c745ca491293cc38e72bf1e0e00de5b91"},
{file = "pydantic-1.10.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7847ca62e581e6088d9000f3c497267868ca2fa89432714e21a4fb33a04d52e8"},
{file = "pydantic-1.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:7845b31959468bc5b78d7b95ec52fe5be32b55d0d09983a877cca6aedc51068f"},
{file = "pydantic-1.10.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:517a681919bf880ce1dac7e5bc0c3af1e58ba118fd774da2ffcd93c5f96eaece"},
{file = "pydantic-1.10.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67195274fd27780f15c4c372f4ba9a5c02dad6d50647b917b6a92bf00b3d301a"},
{file = "pydantic-1.10.9-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2196c06484da2b3fded1ab6dbe182bdabeb09f6318b7fdc412609ee2b564c49a"},
{file = "pydantic-1.10.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6257bb45ad78abacda13f15bde5886efd6bf549dd71085e64b8dcf9919c38b60"},
{file = "pydantic-1.10.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3283b574b01e8dbc982080d8287c968489d25329a463b29a90d4157de4f2baaf"},
{file = "pydantic-1.10.9-cp37-cp37m-win_amd64.whl", hash = "sha256:5f8bbaf4013b9a50e8100333cc4e3fa2f81214033e05ac5aa44fa24a98670a29"},
{file = "pydantic-1.10.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9cd67fb763248cbe38f0593cd8611bfe4b8ad82acb3bdf2b0898c23415a1f82"},
{file = "pydantic-1.10.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f50e1764ce9353be67267e7fd0da08349397c7db17a562ad036aa7c8f4adfdb6"},
{file = "pydantic-1.10.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73ef93e5e1d3c8e83f1ff2e7fdd026d9e063c7e089394869a6e2985696693766"},
{file = "pydantic-1.10.9-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:128d9453d92e6e81e881dd7e2484e08d8b164da5507f62d06ceecf84bf2e21d3"},
{file = "pydantic-1.10.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ad428e92ab68798d9326bb3e5515bc927444a3d71a93b4a2ca02a8a5d795c572"},
{file = "pydantic-1.10.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fab81a92f42d6d525dd47ced310b0c3e10c416bbfae5d59523e63ea22f82b31e"},
{file = "pydantic-1.10.9-cp38-cp38-win_amd64.whl", hash = "sha256:963671eda0b6ba6926d8fc759e3e10335e1dc1b71ff2a43ed2efd6996634dafb"},
{file = "pydantic-1.10.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:970b1bdc6243ef663ba5c7e36ac9ab1f2bfecb8ad297c9824b542d41a750b298"},
{file = "pydantic-1.10.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7e1d5290044f620f80cf1c969c542a5468f3656de47b41aa78100c5baa2b8276"},
{file = "pydantic-1.10.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83fcff3c7df7adff880622a98022626f4f6dbce6639a88a15a3ce0f96466cb60"},
{file = "pydantic-1.10.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0da48717dc9495d3a8f215e0d012599db6b8092db02acac5e0d58a65248ec5bc"},
{file = "pydantic-1.10.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0a2aabdc73c2a5960e87c3ffebca6ccde88665616d1fd6d3db3178ef427b267a"},
{file = "pydantic-1.10.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9863b9420d99dfa9c064042304868e8ba08e89081428a1c471858aa2af6f57c4"},
{file = "pydantic-1.10.9-cp39-cp39-win_amd64.whl", hash = "sha256:e7c9900b43ac14110efa977be3da28931ffc74c27e96ee89fbcaaf0b0fe338e1"},
{file = "pydantic-1.10.9-py3-none-any.whl", hash = "sha256:6cafde02f6699ce4ff643417d1a9223716ec25e228ddc3b436fe7e2d25a1f305"},
{file = "pydantic-1.10.9.tar.gz", hash = "sha256:95c70da2cd3b6ddf3b9645ecaa8d98f3d80c606624b6d245558d202cd23ea3be"},
{file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"},
{file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"},
]
[package.dependencies]
typing-extensions = ">=4.2.0"
annotated-types = ">=0.4.0"
pydantic-core = "2.10.1"
typing-extensions = ">=4.6.1"
[package.extras]
dotenv = ["python-dotenv (>=0.10.4)"]
email = ["email-validator (>=1.0.3)"]
email = ["email-validator (>=2.0.0)"]
[[package]]
name = "pydantic-core"
version = "2.10.1"
description = ""
optional = false
python-versions = ">=3.7"
files = [
{file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"},
{file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"},
{file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"},
{file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"},
{file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"},
{file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"},
{file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"},
{file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"},
{file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"},
{file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"},
{file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"},
{file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"},
{file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"},
{file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"},
{file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"},
{file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"},
{file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"},
{file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"},
{file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"},
{file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"},
{file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"},
{file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"},
{file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"},
{file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"},
{file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"},
{file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"},
{file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"},
{file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"},
{file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"},
{file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"},
{file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"},
{file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"},
{file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"},
{file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"},
{file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"},
{file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"},
{file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"},
{file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"},
{file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"},
{file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"},
{file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"},
{file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"},
{file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"},
{file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"},
{file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"},
{file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"},
{file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"},
{file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"},
{file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"},
{file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"},
{file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"},
{file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"},
{file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"},
{file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"},
{file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"},
{file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"},
{file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"},
{file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"},
{file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"},
{file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"},
{file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"},
{file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"},
{file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"},
{file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"},
{file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"},
{file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"},
{file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"},
{file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"},
{file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"},
{file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"},
{file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"},
{file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"},
{file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"},
{file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"},
{file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"},
{file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"},
{file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"},
{file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"},
{file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"},
{file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"},
{file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"},
{file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"},
{file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"},
{file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"},
{file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"},
{file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"},
{file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"},
{file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"},
{file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"},
{file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"},
{file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"},
{file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"},
{file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"},
{file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"},
{file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"},
{file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"},
{file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"},
{file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"},
{file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"},
{file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"},
{file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"},
{file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"},
{file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"},
{file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"},
{file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"},
{file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"},
]
[package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pye3d"
version = "0.3.2"
description = "3D eye model"
category = "main"
optional = false
python-versions = ">=3.6"
files = [
@ -705,7 +765,6 @@ with-opencv = ["opencv-python"]
name = "pyflakes"
version = "2.5.0"
description = "passive checker of Python programs"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@ -717,7 +776,6 @@ files = [
name = "pyinstaller"
version = "5.13.0"
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
category = "dev"
optional = false
python-versions = "<3.13,>=3.7"
files = [
@ -751,7 +809,6 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"]
name = "pyinstaller-hooks-contrib"
version = "2023.4"
description = "Community maintained hooks for PyInstaller"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -763,7 +820,6 @@ files = [
name = "pyreadline3"
version = "3.4.1"
description = "A python implementation of GNU readline."
category = "main"
optional = false
python-versions = "*"
files = [
@ -775,7 +831,6 @@ files = [
name = "pyserial"
version = "3.5"
description = "Python Serial Port Extension"
category = "main"
optional = false
python-versions = "*"
files = [
@ -790,7 +845,6 @@ cp2110 = ["hidapi"]
name = "pysimplegui"
version = "4.60.5"
description = "Python GUIs for Humans. Launched in 2018. It's 2022 & PySimpleGUI is an ACTIVE & supported project. Super-simple to create custom GUI's. 325+ Demo programs & Cookbook for rapid start. Extensive documentation. Main docs at www.PySimpleGUI.org. Fun & your success are the focus. Examples using Machine Learning (GUI, OpenCV Integration), Rainmeter Style Desktop Widgets, Matplotlib + Pyplot, PIL support, add GUI to command line scripts, PDF & Image Viewers. Great for beginners & advanced GUI programmers."
category = "main"
optional = false
python-versions = "*"
files = [
@ -802,7 +856,6 @@ files = [
name = "python-osc"
version = "1.8.1"
description = "Open Sound Control server and client implementations in pure Python"
category = "main"
optional = false
python-versions = "*"
files = [
@ -814,7 +867,6 @@ files = [
name = "pywin32-ctypes"
version = "0.2.2"
description = "A (partial) reimplementation of pywin32 using ctypes/cffi"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@ -826,7 +878,6 @@ files = [
name = "requests"
version = "2.31.0"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@ -848,7 +899,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
name = "setuptools"
version = "68.0.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -865,7 +915,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (
name = "sortedcontainers"
version = "2.4.0"
description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
category = "main"
optional = false
python-versions = "*"
files = [
@ -877,7 +926,6 @@ files = [
name = "sympy"
version = "1.12"
description = "Computer algebra system (CAS) in Python"
category = "main"
optional = false
python-versions = ">=3.8"
files = [
@ -892,7 +940,6 @@ mpmath = ">=0.19"
name = "taskipy"
version = "1.11.0"
description = "tasks runner for python projects"
category = "main"
optional = false
python-versions = ">=3.6,<4.0"
files = [
@ -910,7 +957,6 @@ tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version >= \"3.7\" and py
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@ -920,21 +966,19 @@ files = [
[[package]]
name = "typing-extensions"
version = "4.7.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main"
version = "4.8.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.7.0-py3-none-any.whl", hash = "sha256:5d8c9dac95c27d20df12fb1d97b9793ab8b2af8a3a525e68c80e21060c161771"},
{file = "typing_extensions-4.7.0.tar.gz", hash = "sha256:935ccf31549830cda708b42289d44b6f74084d616a00be651601a4f968e77c82"},
{file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"},
{file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"},
]
[[package]]
name = "urllib3"
version = "2.0.3"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@ -952,7 +996,6 @@ zstd = ["zstandard (>=0.18.0)"]
name = "winotify"
version = "1.1.0"
description = "Show notification toast on Windows 10"
category = "main"
optional = false
python-versions = "*"
files = [
@ -962,5 +1005,5 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "~3.10.0"
content-hash = "e7ba1f268906e96f3e40127b34c43e8eaa2a2367bc3a2b471d7f6917a1edcc76"
python-versions = "~3.11.0"
content-hash = "0ba077e8a19c2fbfe87ee251c1b1ab26ff3a49e0072abd16f122c46c2db181a3"

View File

@ -14,7 +14,7 @@ opencv-python = "^4.6.0.66"
numpy = "~1.23.5"
pye3d = "^0.3.1.post1"
pysimplegui = "^4.60.4"
pydantic = "^1.10.2"
pydantic = "^2.4.2"
pyserial = "^3.5"
winotify = [
{ version = "^1.1.0", platform = 'win32' }