EyeTrackVR/EyeTrackApp/utils/misc_utils.py
Lorow 7e41a65aac
Feature/etvr module support (#106)
* initial changes

* Mostly clean up, refactor registering listeners to make sense, backport tests

* Add initial implementation of VRCFTModuleSender

* Add basic GUI for the modules settings

* Fix tooltip descriptions

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* Fix type validation bugs, fix typos

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* Add checkbox to switch to ETVR Module

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* Black stuff

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* Remove coverage by default

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* Fix timeout in tests

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* HEAVY WIP: Refactor native output,

NOTE:

I brought back the entire old OSC implementation as a live reference, this will be removed once I'm done.

This also lays ground for other modes as they're pretty similar

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* HEAVY WIP: Refactor v1 params output,

# TODO:
# - min/maxing will require field combinators in the modules lmao

* HEAVY WIP: Refactor v2 params output

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Finish refactoring v2 and v1, fixup tests, refactor native

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Add tests for v1 params

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Add tests for native params

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Fix OSC not getting up after config reset. Remove reset command, config sends everything changed anyway, sunset the idea of using single client and thus simplify the code a bit

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Rename gui_PortNumber to gui_VRCFTModulePort for readability

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Cleanup EyeID usage

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Cleanup osc after rebase

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Make VRChatOSCSender a bit more readable

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Remove unsued VRChatOSCReceiver, this is taken care of by generic OSCReceiver

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Commit crimes with try_convert_to_float to make osc, pysimplegui and pydantic happy

* Cleanup after merge

* Disable emulation by default

* Fix OSCReceiver crashing on unknown addresses

* Adjust VRCFT Module settings to look better in game

* Fix recalibrate and recenter for OSC only working for the right eye

* Fix save and restart button not restarting the tracking

* Fix broken tracking on v1 params for eye_x, clean up implementation

* Fix regular value being passed to OSC listeners instead of OSCMessage

* Add a TODO, probably to be ignored

* Add support for custom ETVR VRCFT Module listening address
2024-07-04 20:51:19 +02:00

83 lines
2.5 KiB
Python

import os
import typing
import sys
from pathlib import Path
from typing import Union
is_nt = True if os.name == "nt" else False
def PlaySound(*args, **kwargs):
pass
SND_FILENAME = SND_ASYNC = 1
if is_nt:
import winsound
PlaySound = winsound.PlaySound
SND_FILENAME = winsound.SND_FILENAME
SND_ASYNC = winsound.SND_ASYNC
def clamp(x, low, high):
return max(low, min(x, high))
def lst_median(lst, ordered=False):
# https://github.com/emilianavt/OpenSeeFace/blob/6f24efc4f58eb7cca47ec2146d934eabcc207e46/remedian.py
assert lst, "median needs a non-empty list"
n = len(lst)
p = q = n // 2
if n < 3:
p, q = 0, n - 1
else:
lst = lst if ordered else sorted(lst)
if not n % 2: # for even-length lists, use mean of mid 2 nums
q = p - 1
return lst[p] if p == q else (lst[p] + lst[q]) / 2
class FastMedian:
# https://github.com/emilianavt/OpenSeeFace/blob/6f24efc4f58eb7cca47ec2146d934eabcc207e46/remedian.py
# Initialization
def __init__(self, inits: typing.Optional[typing.Sequence] = [], k=64): # after some experimentation, 64 works ok
self.all, self.k = [], k
self.more, self.__median = None, None
if inits is not None:
[self + x for x in inits]
# When full, push the median of current values to next list, then reset.
def __add__(self, x):
self.__median = None
self.all.append(x) # It would be faster to pre-allocate an array and assign it by index.
if len(self.all) == self.k:
self.more = self.more or FastMedian(k=self.k)
self.more + self.__medianPrim(self.all)
# It's going to be slower because of the re-allocation.
self.all = [] # reset
# If there is a next list, ask its median. Else, work it out locally.
def median(self):
return self.more.median() if self.more else self.__medianPrim(self.all)
# Only recompute median if we do not know it already.
def __medianPrim(self, all):
if self.__median is None:
self.__median = lst_median(all, ordered=False)
return self.__median
def resource_path(relative_path: Union[str, Path]) -> str:
"""
Get absolute path to resource, works for dev and for PyInstaller
"""
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = Path(sys._MEIPASS)
except AttributeError:
base_path = Path(".")
return str(base_path / relative_path)