Merge pull request #103 from lorow/fix/fix-v2-params

Fix: fix v2 params behaving not correctly
This commit is contained in:
Prohurtz 2024-02-12 09:30:58 -06:00 committed by GitHub
commit f5354ff1e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 625 additions and 137 deletions

6
.coveragerc Normal file
View File

@ -0,0 +1,6 @@
[run]
omit=
tests/*
conftest.py
source =
EyeTrackApp

0
EyeTrackApp/__init__.py Normal file
View File

View File

@ -1,12 +1,10 @@
import json
import os.path
import shutil
from eye import EyeId
from EyeTrackApp.eye import EyeId
from pydantic import BaseModel
from typing import Union, List
import psutil, os
import sys
import os
CONFIG_FILE_NAME: str = "eyetrack_settings.json"
@ -30,7 +28,6 @@ class EyeTrackCameraConfig(BaseModel):
calibration_points: List[List[Union[float, None]]] = []
class EyeTrackSettingsConfig(BaseModel):
gui_flip_x_axis_left: bool = False
gui_flip_x_axis_right: bool = False

View File

@ -1,13 +1,12 @@
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
from EyeTrackApp.config import EyeTrackConfig
from EyeTrackApp.utils.misc_utils import PlaySound, SND_FILENAME, SND_ASYNC
from enum import IntEnum
import queue
import threading
from enum import IntEnum
import time
import numpy as np
class EyeId(IntEnum):
@ -30,13 +29,8 @@ falloff = False
def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity, self):
# print(pupil_dilation)
global se, falloff
# self.config.gui_osc_vrcft_v2
# self.config.gui_osc_vrcft_v1
# self.config.gui_vrc_native
if self.config.gui_osc_vrcft_v1:
if self.main_config.eye_display_id in [
@ -48,9 +42,6 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
self.client.send_message(self.config.osc_left_eye_x_address, eye_x)
self.client.send_message(self.config.osc_right_eye_x_address, eye_x)
self.client.send_message(self.config.osc_eyes_y_address, eye_y)
self.config.osc_left_eye_close_address
self.client.send_message(
self.config.osc_right_eye_close_address,
eyelid_transformer(self, eye_blink),
@ -68,9 +59,7 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
self.l_eye_velocity = avg_velocity
if self.l_eye_blink == 0.0:
if (
last_blink > 0.15
): # when binary blink is on, blinks may be too fast for OSC so we repeat them.
if last_blink > 0.15: # when binary blink is on, blinks may be too fast for OSC so we repeat them.
for i in range(4):
self.client.send_message(
self.config.osc_left_eye_close_address,
@ -94,9 +83,7 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
self.l_eye_velocity = avg_velocity
if self.r_eye_blink == 0.0:
if (
last_blink > 0.15
): # when binary blink is on, blinks may be too fast for OSC so we repeat them.
if last_blink > 0.15: # when binary blink is on, blinks may be too fast for OSC so we repeat them.
# print("REPEATING R BLINK")
for i in range(4):
self.client.send_message(
@ -105,9 +92,7 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
)
last_blink = time.time() - last_blink
if self.config.gui_outer_side_falloff:
if (
self.l_eye_blink == 0.0
): # if both eyes closed and DEF is enables, blink
if self.l_eye_blink == 0.0: # if both eyes closed and DEF is enables, blink
self.client.send_message(
self.config.osc_left_eye_close_address,
eyelid_transformer(self, self.r_eye_blink),
@ -127,11 +112,7 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
eyelid_transformer(self, self.r_eye_blink),
)
if (
self.main_config.eye_display_id in [EyeId.BOTH]
and self.right_y != 621
and self.left_y != 621
):
if self.main_config.eye_display_id in [EyeId.BOTH] and self.right_y != 621 and self.left_y != 621:
y = (self.right_y + self.left_y) / 2
self.client.send_message(self.config.osc_eyes_y_address, y)
@ -144,10 +125,7 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
se = True
self.client.send_message("/avatar/parameters/v2/EyeX", eye_x)
self.client.send_message("/avatar/parameters/v2/EyeLeftX", eye_x)
self.client.send_message("/avatar/parameters/v2/EyeRightX", eye_x)
self.client.send_message("/avatar/parameters/v2/EyeLeftY", eye_y)
self.client.send_message("/avatar/parameters/v2/EyeRightY", eye_y)
self.client.send_message("/avatar/parameters/v2/EyeY", eye_y)
self.client.send_message(
"/avatar/parameters/v2/EyeLid",
eyelid_transformer(self, eye_blink),
@ -161,9 +139,7 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
self.r_eye_velocity = avg_velocity
if self.l_eye_blink == 0.0:
if (
last_blink > 0.15
): # when binary blink is on, blinks may be too fast for OSC so we repeat them.
if last_blink > 0.15: # when binary blink is on, blinks may be too fast for OSC so we repeat them.
for i in range(4):
self.client.send_message(
"/avatar/parameters/v2/EyeLidLeft",
@ -171,9 +147,7 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
)
last_blink = time.time() - last_blink
if self.config.gui_outer_side_falloff:
if (
self.r_eye_blink == 0.0
): # if both eyes closed and DEF is enables, blink
if self.r_eye_blink == 0.0: # if both eyes closed and DEF is enables, blink
self.client.send_message(
"/avatar/parameters/v2/EyeLidLeft",
eyelid_transformer(self, self.l_eye_blink),
@ -188,12 +162,10 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
self.left_y = eye_y
if self.left_y != 621:
self.client.send_message(
"/avatar/parameters/FT/v2/EyeLeftY", self.left_y
)
self.client.send_message("/avatar/parameters/v2/EyeLeftY", self.left_y)
self.client.send_message(
"/avatar/parameters/FT/v2/EyeLidLeft",
"/avatar/parameters/v2/EyeLidLeft",
eyelid_transformer(self, self.l_eye_blink),
)
@ -203,10 +175,7 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
self.r_eye_velocity = avg_velocity
if self.r_eye_blink == 0.0:
if (
last_blink > 0.15
): # when binary blink is on, blinks may be too fast for OSC so we repeat them.
# print("REPEATING R BLINK")
if last_blink > 0.15: # when binary blink is on, blinks may be too fast for OSC so we repeat them.
for i in range(4):
self.client.send_message(
"/avatar/parameters/v2/EyeLidRight",
@ -214,9 +183,7 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
)
last_blink = time.time() - last_blink
if self.config.gui_outer_side_falloff:
if (
self.l_eye_blink == 0.0
): # if both eyes closed and DEF is enables, blink
if self.l_eye_blink == 0.0: # if both eyes closed and DEF is enables, blink
self.client.send_message(
"/avatar/parameters/v2/EyeLidLeft",
eyelid_transformer(self, self.r_eye_blink),
@ -232,23 +199,13 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
self.right_y = eye_y
if self.right_y != 621:
self.client.send_message(
"/avatar/parameters/v2/EyeRightY", self.right_y
)
self.client.send_message("/avatar/parameters/v2/EyeRightY", self.right_y)
self.client.send_message(
"/avatar/parameters/v2/EyeLidRight",
eyelid_transformer(self, self.r_eye_blink),
)
if (
self.main_config.eye_display_id in [EyeId.BOTH]
and self.right_y != 621
and self.left_y != 621
):
y = (self.right_y + self.left_y) / 2
self.client.send_message("/avatar/parameters/v2/EyeY", y)
if self.config.gui_vrc_native: # VRC NATIVE
if self.main_config.eye_display_id in [
@ -257,20 +214,14 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
]: # we are in single eye mode
se = True
if eye_blink == 0.0:
if (
last_blink > 0.2
): # when binary blink is on, blinks may be too fast for OSC so we repeat them.
if last_blink > 0.2: # when binary blink is on, blinks may be too fast for OSC so we repeat them.
for i in range(5):
self.client.send_message(
"/tracking/eye/EyesClosedAmount", float(1 - eye_blink)
)
self.client.send_message("/tracking/eye/EyesClosedAmount", float(1 - eye_blink))
eye_blink += 0.02 # TODO finish tuning value
last_blink = time.time() - last_blink
else:
self.client.send_message(
"/tracking/eye/EyesClosedAmount", float(1 - eye_blink)
)
self.client.send_message("/tracking/eye/EyesClosedAmount", float(1 - eye_blink))
self.client.send_message(
"/tracking/eye/LeftRightVec",
[float(eye_x), float(eye_y), 1.0, float(eye_x), float(eye_y), 1.0],
@ -290,21 +241,13 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
)
if self.l_eye_blink == 0.0:
if (
last_blink > 0.2
): # when binary blink is on, blinks may be too fast for OSC so we repeat them.
if last_blink > 0.2: # when binary blink is on, blinks may be too fast for OSC so we repeat them.
for i in range(5):
self.client.send_message(
"/tracking/eye/EyesClosedAmount", float(1 - eye_blink)
)
self.client.send_message("/tracking/eye/EyesClosedAmount", float(1 - eye_blink))
last_blink = time.time() - last_blink
if self.config.gui_outer_side_falloff:
if (
self.r_eye_blink == 0.0
): # if both eyes closed and DEF is enables, blink
self.client.send_message(
"/tracking/eye/EyesClosedAmount", float(1 - eye_blink)
)
if self.r_eye_blink == 0.0: # if both eyes closed and DEF is enables, blink
self.client.send_message("/tracking/eye/EyesClosedAmount", float(1 - eye_blink))
self.l_eye_x = self.r_eye_x
elif self.eye_id in [EyeId.RIGHT] and not se: # Right eye, send data to right
@ -318,48 +261,26 @@ def output_osc(eye_x, eye_y, eye_blink, last_blink, pupil_dilation, avg_velocity
)
if self.r_eye_blink == 0.0:
if (
last_blink > 0.2
): # when binary blink is on, blinks may be too fast for OSC so we repeat them.
if last_blink > 0.2: # when binary blink is on, blinks may be too fast for OSC so we repeat them.
for i in range(5):
self.client.send_message(
"/tracking/eye/EyesClosedAmount", float(1 - eye_blink)
)
self.client.send_message("/tracking/eye/EyesClosedAmount", float(1 - eye_blink))
last_blink = time.time() - last_blink
if self.config.gui_outer_side_falloff:
if (
self.l_eye_blink == 0.0
): # if both eyes closed and DEF is enables, blink
self.client.send_message(
"/tracking/eye/EyesClosedAmount", float(0)
)
if self.l_eye_blink == 0.0: # if both eyes closed and DEF is enables, blink
self.client.send_message("/tracking/eye/EyesClosedAmount", float(0))
self.r_eye_x = self.l_eye_x
if (
self.main_config.eye_display_id in [EyeId.BOTH]
and self.r_eye_blink != 621
and self.r_eye_blink != 621
):
if self.main_config.eye_display_id in [EyeId.BOTH] and self.r_eye_blink != 621 and self.r_eye_blink != 621:
if self.r_eye_blink == 0.0 or self.l_eye_blink == 0.0:
if (
last_blink > 0.2
): # when binary blink is on, blinks may be too fast for OSC so we repeat them.
if last_blink > 0.2: # when binary blink is on, blinks may be too fast for OSC so we repeat them.
for i in range(5):
self.client.send_message(
"/tracking/eye/EyesClosedAmount", float(1)
)
self.client.send_message("/tracking/eye/EyesClosedAmount", float(1))
last_blink = time.time() - last_blink
eye_blink = (self.r_eye_blink + self.l_eye_blink) / 2
self.client.send_message(
"/tracking/eye/EyesClosedAmount", float(1 - eye_blink)
)
self.client.send_message("/tracking/eye/EyesClosedAmount", float(1 - eye_blink))
if (
self.main_config.eye_display_id in [EyeId.BOTH]
and self.right_y != 621
and self.left_y != 621
):
if self.main_config.eye_display_id in [EyeId.BOTH] and self.right_y != 621 and self.left_y != 621:
eye_y = (self.right_y + self.left_y) / 2
if not se:
@ -426,9 +347,7 @@ class VRChatOSC:
class VRChatOSCReceiver:
def __init__(
self, cancellation_event: threading.Event, main_config: EyeTrackConfig, eyes: []
):
def __init__(self, cancellation_event: threading.Event, main_config: EyeTrackConfig, eyes: []):
self.config = main_config.settings
self.cancellation_event = cancellation_event
self.dispatcher = dispatcher.Dispatcher()
@ -439,9 +358,7 @@ class VRChatOSCReceiver:
self.dispatcher,
)
except:
print(
f"\033[91m[ERROR] OSC Receive port: {self.config.gui_osc_receiver_port} occupied.\033[0m"
)
print(f"\033[91m[ERROR] OSC Receive port: {self.config.gui_osc_receiver_port} occupied.\033[0m")
def shutdown(self):
print("\033[94m[INFO] Exiting OSC Receiver\033[0m")
@ -467,24 +384,13 @@ class VRChatOSCReceiver:
PlaySound("Audio/start.wav", SND_FILENAME | SND_ASYNC)
def run(self):
# bind what function to run when specified OSC message is received
try:
self.dispatcher.map(
self.config.gui_osc_recalibrate_address, self.recalibrate_eyes
)
self.dispatcher.map(
self.config.gui_osc_recenter_address, self.recenter_eyes
)
self.dispatcher.map(self.config.gui_osc_recalibrate_address, self.recalibrate_eyes)
self.dispatcher.map(self.config.gui_osc_recenter_address, self.recenter_eyes)
# start the server
print(
"\033[92m[INFO] VRChatOSCReceiver serving on {}\033[0m".format(
self.server.server_address
)
)
print("\033[92m[INFO] VRChatOSCReceiver serving on {}\033[0m".format(self.server.server_address))
self.server.serve_forever()
except:
print(
f"\033[91m[ERROR] OSC Receive port: {self.config.gui_osc_receiver_port} occupied.\033[0m"
)
print(f"\033[91m[ERROR] OSC Receive port: {self.config.gui_osc_receiver_port} occupied.\033[0m")

125
conftest.py Normal file
View File

@ -0,0 +1,125 @@
import pytest
from EyeTrackApp.config import (
EyeTrackConfig,
EyeTrackCameraConfig,
EyeTrackSettingsConfig,
)
@pytest.fixture()
def eyetrack_settings_config():
return EyeTrackSettingsConfig(
gui_flip_x_axis_left=False,
gui_flip_x_axis_right=False,
gui_flip_y_axis=False,
gui_RANSAC3D=False,
gui_HSF=False,
gui_BLOB=False,
gui_BLINK=False,
gui_HSRAC=False,
gui_AHSFRAC=False,
gui_AHSF=False,
gui_DADDY=False,
gui_LEAP=True,
gui_HSF_radius=15,
gui_HSF_radius_left=10,
gui_HSF_radius_right=10,
gui_min_cutoff="0.0004",
gui_speed_coefficient="0.9",
gui_osc_address="127.0.0.1",
gui_osc_port=8889,
gui_osc_receiver_port=9001,
gui_osc_recenter_address="/avatar/parameters/etvr_recenter",
gui_osc_recalibrate_address="/avatar/parameters/etvr_recalibrate",
gui_blob_maxsize=25.0,
gui_blob_minsize=10.0,
gui_recenter_eyes=False,
tracker_single_eye=2,
gui_threshold=65,
gui_AHSFRACP=1,
gui_AHSFP=2,
gui_HSRACP=3,
gui_HSFP=4,
gui_DADDYP=5,
gui_RANSAC3DP=6,
gui_BLOBP=7,
gui_LEAPP=8,
gui_IBO=True,
gui_skip_autoradius=False,
gui_thresh_add=11,
gui_update_check=False,
gui_ROSC=False,
gui_circular_crop_right=False,
gui_circular_crop_left=False,
ibo_filter_samples=400,
ibo_average_output_samples=0,
ibo_fully_close_eye_threshold=0.3,
calibration_samples=600,
osc_right_eye_close_address="/avatar/parameters/RightEyeLidExpandedSqueeze",
osc_left_eye_close_address="/avatar/parameters/LeftEyeLidExpandedSqueeze",
osc_left_eye_x_address="/avatar/parameters/LeftEyeX",
osc_right_eye_x_address="/avatar/parameters/RightEyeX",
osc_eyes_y_address="/avatar/parameters/EyesY",
osc_invert_eye_close=False,
gui_RANSACBLINK=False,
gui_right_eye_dominant=False,
gui_left_eye_dominant=False,
gui_outer_side_falloff=False,
gui_eye_dominant_diff_thresh=0.3,
gui_legacy_ransac=False,
gui_legacy_ransac_thresh_right=80,
gui_legacy_ransac_thresh_left=80,
gui_LEAP_lid=False,
gui_osc_vrcft_v1=False,
gui_osc_vrcft_v2=False,
gui_vrc_native=False,
gui_pupil_dilation=True,
)
@pytest.fixture()
def eyetrack_camera_config():
return EyeTrackCameraConfig(
rotation_angle=250,
roi_window_x=67,
roi_window_y=27,
roi_window_w=96,
roi_window_h=117,
focal_length=30,
capture_source="http://192.168.0.31/",
calib_XMAX=122.5,
calib_XMIN=38.0,
calib_YMAX=118.0,
calib_YMIN=6.0,
calib_XOFF=40.0,
calib_YOFF=63.0,
calibration_points=[],
)
@pytest.fixture()
def main_config(eyetrack_camera_config, eyetrack_settings_config):
return EyeTrackConfig(
right_eye=eyetrack_camera_config,
left_eye=eyetrack_camera_config,
settings=eyetrack_settings_config,
eye_display_id=0,
)
@pytest.fixture()
def main_config_v1_params(main_config):
main_config.settings.gui_osc_vrcft_v1 = True
return main_config
@pytest.fixture()
def main_config_v2_params(main_config):
main_config.settings.gui_osc_vrcft_v2 = True
return main_config
@pytest.fixture()
def main_config_native_params(main_config):
main_config.settings.gui_vrc_native = True
return main_config

130
poetry.lock generated
View File

@ -207,6 +207,70 @@ humanfriendly = ">=9.1"
[package.extras]
cron = ["capturer (>=2.4)"]
[[package]]
name = "coverage"
version = "7.4.1"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"},
{file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"},
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"},
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"},
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"},
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"},
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"},
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"},
{file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"},
{file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"},
{file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"},
{file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"},
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"},
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"},
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"},
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"},
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"},
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"},
{file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"},
{file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"},
{file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"},
{file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"},
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"},
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"},
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"},
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"},
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"},
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"},
{file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"},
{file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"},
{file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"},
{file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"},
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"},
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"},
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"},
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"},
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"},
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"},
{file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"},
{file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"},
{file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"},
{file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"},
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"},
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"},
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"},
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"},
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"},
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"},
{file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"},
{file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"},
{file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"},
{file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"},
]
[package.extras]
toml = ["tomli"]
[[package]]
name = "flake8"
version = "5.0.4"
@ -291,6 +355,17 @@ pyav = ["av"]
test = ["fsspec[github]", "pytest", "pytest-cov"]
tifffile = ["tifffile"]
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "lazy-loader"
version = "0.3"
@ -665,6 +740,21 @@ files = [
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
[[package]]
name = "pluggy"
version = "1.4.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
{file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "protobuf"
version = "4.25.1"
@ -991,6 +1081,44 @@ files = [
{file = "PySimpleGUI-4.60.5.tar.gz", hash = "sha256:31014d1cc5eef1373d7e93564ff2604662645cc774a939b1f01aa253e7f9d78b"},
]
[[package]]
name = "pytest"
version = "8.0.0"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-8.0.0-py3-none-any.whl", hash = "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6"},
{file = "pytest-8.0.0.tar.gz", hash = "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.3.0,<2.0"
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-cov"
version = "4.1.0"
description = "Pytest plugin for measuring coverage."
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
{file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
]
[package.dependencies]
coverage = {version = ">=5.2.1", extras = ["toml"]}
pytest = ">=4.6"
[package.extras]
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
[[package]]
name = "python-osc"
version = "1.8.3"
@ -1251,4 +1379,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "~3.11.0"
content-hash = "01b325e85a4f1b92329d326db3871acd7051a0756b27b7dd230daf4ffa9c18bd"
content-hash = "51c416718d7c51b683ad74bee318c227ced271bc97307643b7c8072921cc4928"

View File

@ -23,6 +23,8 @@ winotify = [
onnxruntime = "^1.13.1"
colorama = "^0.4.6"
taskipy = "^1.10.4"
pytest = "^8.0.0"
pytest-cov = "^4.1.0"
[tool.poetry.group.dev.dependencies]
black = "^22.10.0"
pyinstaller = "^5.6.2"
@ -31,6 +33,16 @@ flake8 = "^5.0.4"
[tool.taskipy.tasks]
dev = "python eyetrackapp.py"
[tool.black]
line-length = 120
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.pytest.ini_options]
addopts = "-ra -q --cov=. --cov-report html --cov-report term"
pythonpath = "."
python_files = [
"test_*.py"
]

0
tests/__init__.py Normal file
View File

314
tests/test_osc.py Normal file
View File

@ -0,0 +1,314 @@
import dataclasses
import threading
from queue import Queue
from time import sleep
from unittest import mock
import pytest
from EyeTrackApp.osc import VRChatOSC
@dataclasses.dataclass
class EyeInfoMock:
x: int
y: int
blink: float
pupil_dilation: float
avg_velocity: float
class SimpleUDPClientMock:
def __init__(self, osc_address, port):
self.osc_address = osc_address
self.port = port
self.messages = []
def send_message(self, address, value):
self.messages.append((address, value))
@pytest.mark.parametrize(
"eye_id,messages,expected_outcome",
[
(
0,
[
EyeInfoMock(
x=0,
y=0,
blink=1,
pupil_dilation=0,
avg_velocity=0,
),
],
[
("/avatar/parameters/v2/EyeX", 0),
("/avatar/parameters/v2/EyeY", 0),
("/avatar/parameters/v2/EyeLid", 1.0),
],
),
(
1,
[
EyeInfoMock(
x=0,
y=0,
blink=1,
pupil_dilation=0,
avg_velocity=0,
),
],
[
("/avatar/parameters/v2/EyeX", 0),
("/avatar/parameters/v2/EyeY", 0),
("/avatar/parameters/v2/EyeLid", 1.0),
],
),
],
)
def test_send_command_v2_params_single_eye(main_config_v2_params, eye_id, messages, expected_outcome):
with mock.patch("EyeTrackApp.osc.udp_client.SimpleUDPClient", SimpleUDPClientMock):
cancellation_event = threading.Event()
msg_queue = Queue()
client = VRChatOSC(
main_config=main_config_v2_params,
msg_queue=msg_queue,
cancellation_event=cancellation_event,
)
osc_thread = threading.Thread(target=client.run)
osc_thread.start()
for message in messages:
sleep(0.01)
msg_queue.put((eye_id, message))
cancellation_event.set()
osc_thread.join()
assert msg_queue.empty()
assert client.client.messages == expected_outcome
@pytest.mark.parametrize(
"eye_data,expected_outcome",
[
(
[
(
0,
EyeInfoMock(
x=0,
y=0,
blink=1,
pupil_dilation=1,
avg_velocity=0,
),
),
(
1,
EyeInfoMock(
x=10,
y=5,
blink=0.5,
pupil_dilation=1,
avg_velocity=0,
),
),
],
[
("/avatar/parameters/v2/EyeRightX", 0),
("/avatar/parameters/v2/EyeRightY", 0),
("/avatar/parameters/v2/EyeLidRight", 1.0),
("/avatar/parameters/v2/EyeLeftX", 10),
("/avatar/parameters/v2/EyeLeftY", 5),
("/avatar/parameters/v2/EyeLidLeft", 0.5),
],
),
# binary blink
(
[
(
0,
EyeInfoMock(
x=0,
y=0,
blink=0,
pupil_dilation=1,
avg_velocity=0,
),
),
(
1,
EyeInfoMock(
x=10,
y=5,
blink=0,
pupil_dilation=1,
avg_velocity=0,
),
),
],
[
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeRightX", 0),
("/avatar/parameters/v2/EyeRightY", 0),
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
("/avatar/parameters/v2/EyeLeftX", 0),
("/avatar/parameters/v2/EyeLeftY", 5),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
],
),
],
)
def test_send_command_v2_params_dual_eye(main_config_v2_params, eye_data, expected_outcome):
main_config_v2_params.eye_display_id = 2
with mock.patch("EyeTrackApp.osc.udp_client.SimpleUDPClient", SimpleUDPClientMock):
cancellation_event = threading.Event()
msg_queue = Queue()
client = VRChatOSC(
main_config=main_config_v2_params,
msg_queue=msg_queue,
cancellation_event=cancellation_event,
)
osc_thread = threading.Thread(target=client.run)
osc_thread.start()
for eye_id, message in eye_data:
sleep(0.01)
msg_queue.put((eye_id, message))
cancellation_event.set()
osc_thread.join()
assert msg_queue.empty()
assert client.client.messages == expected_outcome
@pytest.mark.parametrize(
"eye_data,expected_outcome",
[
(
[
(
0,
EyeInfoMock(
x=0,
y=0,
blink=0,
pupil_dilation=1,
avg_velocity=0,
),
),
(
1,
EyeInfoMock(
x=10,
y=5,
blink=0,
pupil_dilation=1,
avg_velocity=0,
),
),
],
[
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeRightX", 0),
("/avatar/parameters/v2/EyeRightY", 0),
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLeftX", 0),
("/avatar/parameters/v2/EyeLeftY", 5),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
],
),
(
[
(
0,
EyeInfoMock(
x=0,
y=0,
blink=0,
pupil_dilation=1,
avg_velocity=0,
),
),
(
1,
EyeInfoMock(
x=10,
y=5,
blink=0,
pupil_dilation=1,
avg_velocity=0,
),
),
],
[
# binary blink
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeRightX", 0),
("/avatar/parameters/v2/EyeRightY", 0),
# side falloff
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
# binary blink again
("/avatar/parameters/v2/EyeLidLeft", 0.0),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
("/avatar/parameters/v2/EyeLidRight", 0.0),
("/avatar/parameters/v2/EyeLeftX", 0),
("/avatar/parameters/v2/EyeLeftY", 5),
("/avatar/parameters/v2/EyeLidLeft", 0.0),
],
),
],
)
def test_send_command_v2_params_eye_outer_side_falloff(main_config_v2_params, eye_data, expected_outcome):
main_config_v2_params.eye_display_id = 2
main_config_v2_params.settings.gui_outer_side_falloff = True
with mock.patch("EyeTrackApp.osc.udp_client.SimpleUDPClient", SimpleUDPClientMock):
cancellation_event = threading.Event()
msg_queue = Queue()
client = VRChatOSC(
main_config=main_config_v2_params,
msg_queue=msg_queue,
cancellation_event=cancellation_event,
)
osc_thread = threading.Thread(target=client.run)
osc_thread.start()
for eye_id, message in eye_data:
sleep(0.01)
msg_queue.put((eye_id, message))
cancellation_event.set()
osc_thread.join()
assert msg_queue.empty()
assert client.client.messages == expected_outcome