mirror of
https://github.com/EyeTrackVR/EyeTrackVR.git
synced 2025-11-04 14:39:42 +08:00
Merge pull request #44 from ShyAssassin/python-3.11
Update to python 3.11
This commit is contained in:
commit
40c4033477
@ -1,15 +1,8 @@
|
||||
from time import sleep
|
||||
|
||||
import numpy
|
||||
|
||||
from config import EyeTrackConfig
|
||||
import requests
|
||||
from enum import Enum
|
||||
import threading
|
||||
import queue
|
||||
import runpy
|
||||
import cv2
|
||||
import time
|
||||
|
||||
WAIT_TIME = 0.1
|
||||
|
||||
|
||||
@ -1,57 +1,48 @@
|
||||
from pickle import NONE
|
||||
from dataclasses import dataclass
|
||||
from typing import Union, Dict
|
||||
from dacite import from_dict, Config
|
||||
from osc import EyeId
|
||||
import os.path
|
||||
import json
|
||||
from pydantic import BaseModel
|
||||
CONFIG_FILE_NAME: str = "eyetrack_settings.json"
|
||||
|
||||
|
||||
# TODO Who even needs synchronization? (We do.)
|
||||
|
||||
@dataclass
|
||||
class EyeTrackCameraConfig:
|
||||
threshold: "int" = 0
|
||||
rotation_angle: "int" = 50
|
||||
roi_window_x: "int" = 0
|
||||
roi_window_y: "int" = 0
|
||||
roi_window_w: "int" = 0
|
||||
roi_window_h: "int" = 0
|
||||
focal_length: "int" = 30
|
||||
capture_source: "Union[int, str, None]" = None
|
||||
gui_circular_crop: "bool" = False
|
||||
class EyeTrackCameraConfig(BaseModel):
|
||||
threshold: int = 0
|
||||
rotation_angle: int = 50
|
||||
roi_window_x: int = 0
|
||||
roi_window_y: int = 0
|
||||
roi_window_w: int = 0
|
||||
roi_window_h: int = 0
|
||||
focal_length: int = 30
|
||||
capture_source: Union[int, str, None] = None
|
||||
gui_circular_crop: bool = False
|
||||
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class EyeTrackSettingsConfig:
|
||||
gui_flip_x_axis_left: "bool" = False
|
||||
gui_flip_x_axis_right: "bool" = False
|
||||
gui_flip_y_axis: "bool" = False
|
||||
gui_blob_fallback: "bool" = True
|
||||
gui_min_cutoff: "str" = "0.0004"
|
||||
gui_speed_coefficient: "str" = "0.9"
|
||||
gui_osc_address: "str" = "127.0.0.1"
|
||||
gui_osc_port: "str" = "9000"
|
||||
gui_osc_receiver_port: "str" = "9001"
|
||||
gui_osc_recenter_address: "str" = "/avatar/parameters/etvr_recenter"
|
||||
gui_osc_recalibrate_address: "str" = "/avatar/parameters/etvr_recalibrate"
|
||||
gui_blob_maxsize: "float" = 25
|
||||
gui_blob_minsize: "float" = 10
|
||||
gui_recenter_eyes: "bool" = False
|
||||
gui_eye_falloff: "bool" = False
|
||||
tracker_single_eye: "float" = 0
|
||||
CONFIG_FILE_NAME = "eyetrack_settings.json"
|
||||
class EyeTrackSettingsConfig(BaseModel):
|
||||
gui_flip_x_axis_left: bool = False
|
||||
gui_flip_x_axis_right: bool = False
|
||||
gui_flip_y_axis: bool = False
|
||||
gui_blob_fallback: bool = True
|
||||
gui_min_cutoff: str = "0.0004"
|
||||
gui_speed_coefficient: str = "0.9"
|
||||
gui_osc_address: str = "127.0.0.1"
|
||||
gui_osc_port: str = "9000"
|
||||
gui_osc_receiver_port: str = "9001"
|
||||
gui_osc_recenter_address: str = "/avatar/parameters/etvr_recenter"
|
||||
gui_osc_recalibrate_address: str = "/avatar/parameters/etvr_recalibrate"
|
||||
gui_blob_maxsize: float = 25
|
||||
gui_blob_minsize: float = 10
|
||||
gui_recenter_eyes: bool = False
|
||||
gui_eye_falloff: bool = False
|
||||
tracker_single_eye: float = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class EyeTrackConfig:
|
||||
version: "int" = 1
|
||||
class EyeTrackConfig(BaseModel):
|
||||
version: int = 1
|
||||
right_eye: EyeTrackCameraConfig = EyeTrackCameraConfig()
|
||||
left_eye: EyeTrackCameraConfig = EyeTrackCameraConfig()
|
||||
settings: EyeTrackSettingsConfig = EyeTrackSettingsConfig()
|
||||
eye_display_id: "EyeId" = EyeId.RIGHT
|
||||
eye_display_id: EyeId = EyeId.RIGHT
|
||||
|
||||
@staticmethod
|
||||
def load():
|
||||
@ -59,19 +50,8 @@ class EyeTrackConfig:
|
||||
print("No settings file, using base settings")
|
||||
return EyeTrackConfig()
|
||||
with open(CONFIG_FILE_NAME, "r") as settings_file:
|
||||
# try:
|
||||
config: EyeTrackConfig = from_dict(
|
||||
data_class=EyeTrackConfig, data=json.load(settings_file), config=Config(type_hooks={EyeId: EyeId})
|
||||
)
|
||||
if config.version != EyeTrackConfig().version:
|
||||
raise RuntimeError(
|
||||
"Configuration does not contain version number, consider invalid"
|
||||
)
|
||||
return config
|
||||
# except:
|
||||
# print("[INFO] Configuration invalid, creating new config")
|
||||
# return EyeTrackConfig()
|
||||
return EyeTrackConfig(**json.load(settings_file))
|
||||
|
||||
def save(self):
|
||||
with open(CONFIG_FILE_NAME, "w+") as settings_file:
|
||||
json.dump(obj=self.__dict__, fp=settings_file, default=lambda x: x.__dict__)
|
||||
json.dump(obj=self.dict(), fp=settings_file)
|
||||
|
||||
@ -6,7 +6,8 @@ import asyncio
|
||||
sys.path.append(".")
|
||||
from config import EyeTrackCameraConfig
|
||||
from config import EyeTrackSettingsConfig
|
||||
from pye3dcustom.detector_3d import CameraModel, Detector3D, DetectorMode
|
||||
from pye3d.camera import CameraModel
|
||||
from pye3d.detector_3d import Detector3D, DetectorMode
|
||||
import queue
|
||||
import threading
|
||||
import numpy as np
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
# Random environment variable to speed up webcam opening on the MSMF backend.
|
||||
# https://github.com/opencv/opencv/issues/17687
|
||||
import os
|
||||
|
||||
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
|
||||
from osc import VRChatOSCReceiver, VRChatOSC, EyeId
|
||||
from config import EyeTrackConfig
|
||||
from camera_widget import CameraWidget
|
||||
@ -10,6 +6,9 @@ from settings_widget import SettingsWidget
|
||||
import queue
|
||||
import threading
|
||||
import PySimpleGUI as sg
|
||||
# Random environment variable to speed up webcam opening on the MSMF backend.
|
||||
# https://github.com/opencv/opencv/issues/17687
|
||||
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
|
||||
|
||||
WINDOW_NAME = "EyeTrackApp"
|
||||
RIGHT_EYE_NAME = "-RIGHTEYEWIDGET-"
|
||||
@ -35,7 +34,7 @@ def main():
|
||||
# Check to see if we have an ROI. If not, bring up ROI finder GUI.
|
||||
|
||||
# Spawn worker threads
|
||||
osc_queue: "queue.Queue[tuple[bool, int, int]]" = queue.Queue()
|
||||
osc_queue: queue.Queue[tuple[bool, int, int]] = queue.Queue()
|
||||
osc = VRChatOSC(cancellation_event, osc_queue, config)
|
||||
osc_thread = threading.Thread(target=osc.run)
|
||||
# start worker threads
|
||||
@ -108,7 +107,6 @@ def main():
|
||||
|
||||
if config.eye_display_id in [EyeId.LEFT, EyeId.BOTH]:
|
||||
eyes[1].start()
|
||||
eyes[0].ransac.calibration_frame_counter
|
||||
if config.eye_display_id in [EyeId.RIGHT, EyeId.BOTH]:
|
||||
eyes[0].start()
|
||||
|
||||
@ -130,7 +128,6 @@ def main():
|
||||
|
||||
# If we're in either mode and someone hits q, quit immediately
|
||||
if event == "Exit" or event == sg.WIN_CLOSED:
|
||||
# eyes[2].stop()
|
||||
for eye in eyes:
|
||||
eye.stop()
|
||||
cancellation_event.set()
|
||||
@ -138,7 +135,7 @@ def main():
|
||||
osc_thread.join()
|
||||
# TODO: find a way to have this function run on join maybe??
|
||||
# threading.Event() wont work because pythonosc spawns its own thread.
|
||||
# only way i can see to get around this is an ugly while loop that only checks if a threading event is trigggered
|
||||
# only way i can see to get around this is an ugly while loop that only checks if a threading event is triggered
|
||||
# and then call the pythonosc shutdown function
|
||||
osc_receiver.shutdown()
|
||||
osc_receiver_thread.join()
|
||||
|
||||
@ -5,8 +5,8 @@ block_cipher = None
|
||||
|
||||
a = Analysis(['eyetrackapp.py'],
|
||||
pathex=[],
|
||||
binaries=[("pye3d.libs/*", "pye3d.libs"), ("pye3d.libs/.*", "pye3d.libs")],
|
||||
datas=[("pye3dcustom/refraction_models/*", "pye3dcustom/refraction_models"), ("Audio/*", "Audio"), ("Images/*", "Images/")],
|
||||
binaries=[],
|
||||
datas=[("Audio/*", "Audio"), ("Images/*", "Images/")],
|
||||
hiddenimports=['cv2', 'numpy', 'PySimpleGui'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
|
||||
@ -17,7 +17,7 @@ from config import EyeTrackConfig
|
||||
class VRChatOSC:
|
||||
# Use a tuple of blink (true, blinking, false, not), x, y for now. Probably clearer as a class but
|
||||
# we're stuck in python 3.6 so still no dataclasses. God I hate python.
|
||||
def __init__(self, cancellation_event: "threading.Event", msg_queue: "queue.Queue[tuple[bool, int, int, int]]", main_config: EyeTrackConfig,):
|
||||
def __init__(self, cancellation_event: threading.Event, msg_queue: queue.Queue[tuple[bool, int, int]], main_config: EyeTrackConfig,):
|
||||
self.main_config = main_config
|
||||
self.config = main_config.settings
|
||||
self.client = udp_client.SimpleUDPClient(self.config.gui_osc_address, int(self.config.gui_osc_port)) # use OSC port and address that was set in the config
|
||||
@ -63,9 +63,7 @@ class VRChatOSC:
|
||||
self.client.send_message("/avatar/parameters/RightEyeX", eye_info.x)
|
||||
self.client.send_message("/avatar/parameters/RightEyeLid", float(0))# old param open right
|
||||
self.client.send_message("/avatar/parameters/RightEyeLidExpandedSqueeze", float(0.8)) # open right eye
|
||||
# self.client.send_message(
|
||||
# "/avatar/parameters/EyesDilation", eye_info.pupil_dialation
|
||||
#)
|
||||
|
||||
if eye_id in [EyeId.LEFT]:
|
||||
yl = eye_info.y
|
||||
sx = eye_info.x
|
||||
@ -79,7 +77,7 @@ class VRChatOSC:
|
||||
y = (yr + yl) / 2
|
||||
self.client.send_message("/avatar/parameters/EyesY", y)
|
||||
else:
|
||||
if self.config.gui_eye_falloff == True:
|
||||
if self.config.gui_eye_falloff:
|
||||
if eye_id in [EyeId.LEFT]:
|
||||
lb = True
|
||||
if eye_id in [EyeId.RIGHT]:
|
||||
@ -102,7 +100,6 @@ class VRChatOSC:
|
||||
last_blink = time.time()
|
||||
else:
|
||||
if self.config.tracker_single_eye == 1 or self.config.tracker_single_eye == 2:
|
||||
|
||||
if last_blink > 0.5:
|
||||
for i in range(4):
|
||||
self.client.send_message("/avatar/parameters/RightEyeLid", float(1)) #close eye
|
||||
@ -127,12 +124,13 @@ class VRChatOSC:
|
||||
self.client.send_message("/avatar/parameters/RightEyeLidExpandedSqueeze", float(0)) # close eye
|
||||
last_blink = time.time()
|
||||
|
||||
|
||||
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()
|
||||
self.eyes = eyes # we cant import CameraWidget so any type it is
|
||||
self.eyes = eyes # we cant import CameraWidget so any type it is
|
||||
self.server = osc_server.OSCUDPServer((self.config.gui_osc_address, int(self.config.gui_osc_receiver_port)), self.dispatcher)
|
||||
|
||||
def shutdown(self):
|
||||
@ -140,13 +138,13 @@ class VRChatOSCReceiver:
|
||||
self.server.shutdown()
|
||||
|
||||
def recenter_eyes(self, address, osc_value):
|
||||
if type(osc_value) != bool: return # just incase we get anything other than bool
|
||||
if type(osc_value) != bool: return # just incase we get anything other than bool
|
||||
if osc_value:
|
||||
for eye in self.eyes:
|
||||
eye.settings.gui_recenter_eyes = True
|
||||
|
||||
def recalibrate_eyes(self, address, osc_value):
|
||||
if type(osc_value) != bool: return # just incase we get anything other than bool
|
||||
if type(osc_value) != bool: return # just incase we get anything other than bool
|
||||
if osc_value:
|
||||
for eye in self.eyes:
|
||||
eye.ransac.calibration_frame_counter = 300
|
||||
|
||||
@ -1 +0,0 @@
|
||||
vcruntime140_1.dll
|
||||
Binary file not shown.
@ -1,34 +0,0 @@
|
||||
|
||||
|
||||
"""""" # start delvewheel patch
|
||||
def _delvewheel_init_patch_0_0_15():
|
||||
import os
|
||||
import sys
|
||||
libs_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'pye3d.libs'))
|
||||
if sys.version_info[:2] >= (3, 8):
|
||||
if os.path.exists(os.path.join(sys.base_prefix, 'conda-meta')):
|
||||
# backup the state of the environment variable CONDA_DLL_SEARCH_MODIFICATION_ENABLE
|
||||
conda_dll_search_modification_enable = os.environ.get("CONDA_DLL_SEARCH_MODIFICATION_ENABLE")
|
||||
os.environ['CONDA_DLL_SEARCH_MODIFICATION_ENABLE']='1'
|
||||
|
||||
os.add_dll_directory(libs_dir)
|
||||
|
||||
if os.path.exists(os.path.join(sys.base_prefix, 'conda-meta')):
|
||||
# restore the state of the environment variable CONDA_DLL_SEARCH_MODIFICATION_ENABLE
|
||||
if conda_dll_search_modification_enable is None:
|
||||
os.environ.pop("CONDA_DLL_SEARCH_MODIFICATION_ENABLE", None)
|
||||
else:
|
||||
os.environ["CONDA_DLL_SEARCH_MODIFICATION_ENABLE"] = conda_dll_search_modification_enable
|
||||
else:
|
||||
from ctypes import WinDLL
|
||||
with open(os.path.join(libs_dir, '.load-order-pye3d-0.3.0')) as file:
|
||||
load_order = file.read().split()
|
||||
for lib in load_order:
|
||||
WinDLL(os.path.join(libs_dir, lib))
|
||||
|
||||
|
||||
_delvewheel_init_patch_0_0_15()
|
||||
del _delvewheel_init_patch_0_0_15
|
||||
# end delvewheel patch
|
||||
|
||||
__version__ = "0.3.0"
|
||||
@ -1,6 +0,0 @@
|
||||
from typing import Tuple, NamedTuple
|
||||
|
||||
|
||||
class CameraModel(NamedTuple):
|
||||
focal_length: float
|
||||
resolution: Tuple[float, float]
|
||||
@ -1,4 +0,0 @@
|
||||
import typing as T
|
||||
|
||||
_EYE_RADIUS_DEFAULT: float = 10.392304845413264
|
||||
DEFAULT_SPHERE_CENTER: T.Tuple[float, float, float] = (0.0, 0.0, 35.0)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,725 +0,0 @@
|
||||
"""
|
||||
(*)~---------------------------------------------------------------------------
|
||||
Pupil - eye tracking platform
|
||||
Copyright (C) 2012-2019 Pupil Labs
|
||||
|
||||
Distributed under the terms of the GNU
|
||||
Lesser General Public License (LGPL v3.0).
|
||||
See COPYING and COPYING.LESSER for license details.
|
||||
---------------------------------------------------------------------------~(*)
|
||||
"""
|
||||
import enum
|
||||
import logging
|
||||
import traceback
|
||||
from typing import Dict, NamedTuple, Type
|
||||
|
||||
import numpy as np
|
||||
import cv2 # Todo: DELETE
|
||||
from .geometry.projections import (
|
||||
unproject_edges_to_sphere,
|
||||
project_point_into_image_plane,
|
||||
) # Todo: DELETE
|
||||
|
||||
from .camera import CameraModel
|
||||
from .constants import _EYE_RADIUS_DEFAULT
|
||||
from .cpp.pupil_detection_3d import get_edges
|
||||
from .cpp.pupil_detection_3d import search_on_sphere as search_on_sphere
|
||||
from .geometry.primitives import Circle, Ellipse, Sphere
|
||||
from .geometry.projections import (
|
||||
project_circle_into_image_plane,
|
||||
project_sphere_into_image_plane,
|
||||
)
|
||||
from .geometry.utilities import cart2sph, sph2cart
|
||||
from .kalman import KalmanFilter
|
||||
from .observation import (
|
||||
BinBufferedObservationStorage,
|
||||
BufferedObservationStorage,
|
||||
Observation,
|
||||
)
|
||||
from .eye_model import (
|
||||
SphereCenterEstimates,
|
||||
TwoSphereModelAbstract,
|
||||
TwoSphereModel,
|
||||
TwoSphereModelAsync,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DetectorMode(enum.Enum):
|
||||
blocking = TwoSphereModel
|
||||
asynchronous = TwoSphereModelAsync
|
||||
|
||||
@classmethod
|
||||
def from_name(cls, mode_name: str):
|
||||
return {mode.name: mode for mode in cls}[mode_name]
|
||||
|
||||
|
||||
def ellipse2dict(ellipse: Ellipse) -> Dict:
|
||||
return {
|
||||
"center": (
|
||||
ellipse.center[0],
|
||||
ellipse.center[1],
|
||||
),
|
||||
"axes": (
|
||||
ellipse.minor_radius,
|
||||
ellipse.major_radius,
|
||||
),
|
||||
"angle": ellipse.angle,
|
||||
}
|
||||
|
||||
|
||||
def circle2dict(circle: Circle) -> Dict:
|
||||
return {
|
||||
"center": (
|
||||
circle.center[0],
|
||||
circle.center[1],
|
||||
circle.center[2],
|
||||
),
|
||||
"normal": (
|
||||
circle.normal[0],
|
||||
circle.normal[1],
|
||||
circle.normal[2],
|
||||
),
|
||||
"radius": float(circle.radius),
|
||||
}
|
||||
|
||||
|
||||
class Prediction(NamedTuple):
|
||||
sphere_center: np.ndarray
|
||||
pupil_circle: Circle
|
||||
|
||||
|
||||
class Search3DResult(NamedTuple):
|
||||
circle: Circle
|
||||
confidence: float
|
||||
|
||||
|
||||
def sigmoid(x, baseline=0.1, amplitude=500.0, center=0.99, width=0.02):
|
||||
return baseline + amplitude * 1.0 / (1.0 + np.exp(-(x - center) / width))
|
||||
|
||||
|
||||
class Detector3D(object):
|
||||
def __init__(
|
||||
self,
|
||||
camera: CameraModel,
|
||||
threshold_swirski=0.7,
|
||||
threshold_kalman=0.98,
|
||||
threshold_short_term=0.8,
|
||||
threshold_long_term=0.98,
|
||||
long_term_buffer_size=30,
|
||||
long_term_forget_time=5,
|
||||
long_term_forget_observations=300,
|
||||
long_term_mode: DetectorMode = DetectorMode.blocking,
|
||||
model_update_interval_long_term=1.0,
|
||||
model_update_interval_ult_long_term=10.0,
|
||||
model_warmup_duration=5.0,
|
||||
calculate_rms_residual=False,
|
||||
):
|
||||
self._camera = camera
|
||||
self._long_term_mode = long_term_mode
|
||||
self._calculate_rms_residual = calculate_rms_residual
|
||||
# NOTE: changing settings after intialization can lead to inconsistent behavior
|
||||
# if .reset() is not called.
|
||||
self._settings = {
|
||||
"threshold_swirski": threshold_swirski,
|
||||
"threshold_kalman": threshold_kalman,
|
||||
"threshold_short_term": threshold_short_term,
|
||||
"threshold_long_term": threshold_long_term,
|
||||
"long_term_buffer_size": long_term_buffer_size,
|
||||
"long_term_forget_time": long_term_forget_time,
|
||||
"long_term_forget_observations": long_term_forget_observations,
|
||||
"model_update_interval_long_term": model_update_interval_long_term,
|
||||
"model_update_interval_ult_long_term": model_update_interval_ult_long_term,
|
||||
"model_warmup_duration": model_warmup_duration,
|
||||
}
|
||||
self.reset()
|
||||
logger.debug(
|
||||
f"{type(self)} initialized with "
|
||||
f"long_term_mode={long_term_mode} "
|
||||
f"calculate_rms_residual={calculate_rms_residual} "
|
||||
f"settings={self._settings}"
|
||||
)
|
||||
|
||||
@property
|
||||
def camera(self) -> CameraModel:
|
||||
return self._camera
|
||||
|
||||
@property
|
||||
def long_term_mode(self) -> DetectorMode:
|
||||
return self._long_term_mode
|
||||
|
||||
@long_term_mode.setter
|
||||
def long_term_mode(self, mode: DetectorMode):
|
||||
needs_reset = mode != self._long_term_mode
|
||||
self._long_term_mode = mode
|
||||
if needs_reset:
|
||||
self.reset()
|
||||
|
||||
@property
|
||||
def is_long_term_model_frozen(self) -> bool:
|
||||
# If _ult_long_term_schedule is paused or not does not actually matter. The
|
||||
# _ult_long_term_model is only used for fitting the _long_term_model. If the
|
||||
# _long_term_schedule is paused, the _long_term_model is not being fitted and
|
||||
# therefore the state of _ult_long_term_model will be ignored.
|
||||
return self._long_term_schedule.is_paused
|
||||
|
||||
@is_long_term_model_frozen.setter
|
||||
def is_long_term_model_frozen(self, should_be_frozen: bool) -> None:
|
||||
# We pause/resume _ult_long_term_schedule here as well to save CPU resources
|
||||
# while the _long_term_model is frozen.
|
||||
if should_be_frozen:
|
||||
self._long_term_schedule.pause()
|
||||
self._ult_long_term_schedule.pause()
|
||||
else:
|
||||
self._long_term_schedule.resume()
|
||||
self._ult_long_term_schedule.resume()
|
||||
|
||||
def reset_camera(self, camera: CameraModel):
|
||||
"""Change camera model and reset detector state."""
|
||||
self._camera = camera
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self._cleanup_models()
|
||||
self._initialize_models(
|
||||
long_term_model_cls=self._long_term_mode.value,
|
||||
ultra_long_term_model_cls=self._long_term_mode.value,
|
||||
)
|
||||
self._long_term_schedule = _ModelUpdateSchedule(
|
||||
update_interval=self._settings["model_update_interval_long_term"],
|
||||
warmup_duration=self._settings["model_warmup_duration"],
|
||||
)
|
||||
self._ult_long_term_schedule = _ModelUpdateSchedule(
|
||||
update_interval=self._settings["model_update_interval_ult_long_term"],
|
||||
warmup_duration=self._settings["model_warmup_duration"],
|
||||
)
|
||||
|
||||
self.kalman_filter = KalmanFilter()
|
||||
|
||||
def _initialize_models(
|
||||
self,
|
||||
short_term_model_cls: Type[TwoSphereModelAbstract] = TwoSphereModel,
|
||||
long_term_model_cls: Type[TwoSphereModelAbstract] = TwoSphereModel,
|
||||
ultra_long_term_model_cls: Type[TwoSphereModelAbstract] = TwoSphereModel,
|
||||
):
|
||||
# Recreate all models. This is required in case any of the settings (incl
|
||||
# camera) changed in the meantime.
|
||||
self.short_term_model = short_term_model_cls(
|
||||
camera=self.camera,
|
||||
storage_cls=BufferedObservationStorage,
|
||||
storage_kwargs=dict(
|
||||
confidence_threshold=self._settings["threshold_short_term"],
|
||||
buffer_length=10,
|
||||
),
|
||||
)
|
||||
self.long_term_model = long_term_model_cls(
|
||||
camera=self.camera,
|
||||
storage_cls=BinBufferedObservationStorage,
|
||||
storage_kwargs=dict(
|
||||
camera=self.camera,
|
||||
confidence_threshold=self._settings["threshold_long_term"],
|
||||
n_bins_horizontal=10,
|
||||
bin_buffer_length=self._settings["long_term_buffer_size"],
|
||||
forget_min_observations=self._settings["long_term_forget_observations"],
|
||||
forget_min_time=self._settings["long_term_forget_time"],
|
||||
),
|
||||
)
|
||||
self.ultra_long_term_model = ultra_long_term_model_cls(
|
||||
camera=self.camera,
|
||||
storage_cls=BinBufferedObservationStorage,
|
||||
storage_kwargs=dict(
|
||||
camera=self.camera,
|
||||
confidence_threshold=self._settings["threshold_long_term"],
|
||||
n_bins_horizontal=10,
|
||||
bin_buffer_length=self._settings["long_term_buffer_size"],
|
||||
forget_min_observations=(
|
||||
2 * self._settings["long_term_forget_observations"]
|
||||
),
|
||||
forget_min_time=60,
|
||||
),
|
||||
)
|
||||
|
||||
def _cleanup_models(self):
|
||||
try:
|
||||
self.short_term_model.cleanup()
|
||||
self.long_term_model.cleanup()
|
||||
self.ultra_long_term_model.cleanup()
|
||||
except AttributeError:
|
||||
pass # models have not been initialized yet
|
||||
|
||||
def update_and_detect(
|
||||
self,
|
||||
pupil_datum: Dict,
|
||||
frame: np.ndarray,
|
||||
apply_refraction_correction: bool = True,
|
||||
debug: bool = False,
|
||||
):
|
||||
# update models
|
||||
observation = self._extract_observation(pupil_datum)
|
||||
self.update_models(observation)
|
||||
|
||||
# predict target variables
|
||||
sphere_center = self.long_term_model.sphere_center
|
||||
pupil_circle = self._predict_pupil_circle(observation, frame)
|
||||
prediction_uncorrected = Prediction(sphere_center, pupil_circle)
|
||||
|
||||
# apply refraction correction
|
||||
if apply_refraction_correction:
|
||||
pupil_circle = self.long_term_model.apply_refraction_correction(
|
||||
pupil_circle
|
||||
)
|
||||
sphere_center = self.long_term_model.corrected_sphere_center
|
||||
# Falls back to uncorrected version if correction is disabled
|
||||
prediction_corrected = Prediction(sphere_center, pupil_circle)
|
||||
|
||||
result = self._prepare_result(
|
||||
observation,
|
||||
prediction_uncorrected=prediction_uncorrected,
|
||||
prediction_corrected=prediction_corrected,
|
||||
)
|
||||
|
||||
if debug:
|
||||
result["debug_info"] = self._collect_debug_info()
|
||||
|
||||
return result
|
||||
|
||||
def update_models(self, observation: Observation):
|
||||
self.short_term_model.add_observation(observation)
|
||||
self.long_term_model.add_observation(observation)
|
||||
self.ultra_long_term_model.add_observation(observation)
|
||||
|
||||
if (
|
||||
self.short_term_model.n_observations <= 0
|
||||
or self.long_term_model.n_observations <= 0
|
||||
or self.ultra_long_term_model.n_observations <= 0
|
||||
):
|
||||
return
|
||||
|
||||
try:
|
||||
if self._ult_long_term_schedule.is_update_due(observation.timestamp):
|
||||
self.ultra_long_term_model.estimate_sphere_center(
|
||||
calculate_rms_residual=self._calculate_rms_residual
|
||||
)
|
||||
|
||||
if self._long_term_schedule.is_update_due(observation.timestamp):
|
||||
# update long term model with ultra long term bias
|
||||
long_term_estimate = self.long_term_model.estimate_sphere_center(
|
||||
prior_3d=self.ultra_long_term_model.sphere_center,
|
||||
prior_strength=0.1,
|
||||
calculate_rms_residual=self._calculate_rms_residual,
|
||||
)
|
||||
else:
|
||||
# use existing sphere center estimates
|
||||
long_term_estimate = SphereCenterEstimates(
|
||||
projected=self.long_term_model.projected_sphere_center,
|
||||
three_dim=self.long_term_model.sphere_center,
|
||||
rms_residual=self.long_term_model.rms_residual,
|
||||
)
|
||||
|
||||
# update short term model with help of long-term model
|
||||
# using 2d center for disambiguation and 3d center as prior bias
|
||||
# prior strength is set as a funcition of circularity of the 2D pupil
|
||||
# when frozen: do not update
|
||||
if not self.is_long_term_model_frozen:
|
||||
circularity_mean = self.short_term_model.mean_observation_circularity()
|
||||
self.short_term_model.estimate_sphere_center(
|
||||
from_2d=long_term_estimate.projected,
|
||||
prior_3d=long_term_estimate.three_dim,
|
||||
prior_strength=sigmoid(circularity_mean),
|
||||
calculate_rms_residual=self._calculate_rms_residual,
|
||||
)
|
||||
except Exception:
|
||||
# Known issues:
|
||||
# - Can raise numpy.linalg.LinAlgError: SVD did not converge
|
||||
logger.error("Error updating models:")
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
def _extract_observation(self, pupil_datum: Dict) -> Observation:
|
||||
width, height = self.camera.resolution
|
||||
center = (
|
||||
pupil_datum["ellipse"]["center"][0] - width / 2,
|
||||
pupil_datum["ellipse"]["center"][1] - height / 2,
|
||||
)
|
||||
minor_radius = pupil_datum["ellipse"]["axes"][0] / 2.0
|
||||
major_radius = pupil_datum["ellipse"]["axes"][1] / 2.0
|
||||
angle = (pupil_datum["ellipse"]["angle"] - 90.0) * np.pi / 180.0
|
||||
ellipse = Ellipse(center, minor_radius, major_radius, angle)
|
||||
|
||||
return Observation(
|
||||
ellipse,
|
||||
pupil_datum["confidence"],
|
||||
pupil_datum["timestamp"],
|
||||
self.camera.focal_length,
|
||||
)
|
||||
|
||||
def _predict_pupil_circle(
|
||||
self, observation: Observation, frame: np.ndarray
|
||||
) -> Circle:
|
||||
# NOTE: General idea: predict pupil circle from long and short term models based
|
||||
# on current observation. Filter results with a kalman filter.
|
||||
|
||||
# Kalman filter needs to be queried every timestamp to update it internally.
|
||||
pupil_circle_kalman = self._predict_from_kalman_filter(observation.timestamp)
|
||||
|
||||
if observation.confidence > self._settings["threshold_swirski"]:
|
||||
# high-confidence observation, use to construct pupil circle from models
|
||||
|
||||
# short-term-model is best for estimating gaze direction (circle normal) if
|
||||
# one needs to assume slippage. long-term-model ist more stable for
|
||||
# positions (center and radius)
|
||||
long_term = self.long_term_model.predict_pupil_circle(observation)
|
||||
if self.is_long_term_model_frozen:
|
||||
normal = long_term.normal
|
||||
else:
|
||||
short_term = self.short_term_model.predict_pupil_circle(observation)
|
||||
normal = short_term.normal
|
||||
pupil_circle = Circle(
|
||||
normal=normal,
|
||||
center=long_term.center,
|
||||
radius=long_term.radius,
|
||||
)
|
||||
|
||||
else:
|
||||
# low confidence: use kalman prediction to search for circles in image
|
||||
pupil_circle, confidence_3d_search = self._predict_from_3d_search(
|
||||
frame, best_guess=pupil_circle_kalman
|
||||
)
|
||||
observation.confidence = confidence_3d_search
|
||||
|
||||
if observation.confidence > self._settings["threshold_kalman"]:
|
||||
# very-high-confidence: correct kalman filter
|
||||
self._correct_kalman_filter(pupil_circle)
|
||||
|
||||
return pupil_circle
|
||||
|
||||
def _predict_from_kalman_filter(self, timestamp):
|
||||
phi, theta, pupil_radius_kalman = self.kalman_filter.predict(timestamp)
|
||||
gaze_vector_kalman = sph2cart(phi, theta)
|
||||
pupil_center_kalman = (
|
||||
self.short_term_model.sphere_center
|
||||
+ _EYE_RADIUS_DEFAULT * gaze_vector_kalman
|
||||
)
|
||||
pupil_circle_kalman = Circle(
|
||||
pupil_center_kalman, gaze_vector_kalman, pupil_radius_kalman
|
||||
)
|
||||
return pupil_circle_kalman
|
||||
|
||||
def _correct_kalman_filter(self, observed_pupil_circle: Circle):
|
||||
if observed_pupil_circle.is_null():
|
||||
return
|
||||
|
||||
phi, theta, r = observed_pupil_circle.spherical_representation()
|
||||
self.kalman_filter.correct(phi, theta, r)
|
||||
|
||||
def _predict_from_3d_search(
|
||||
# TODO: Remove debug code
|
||||
self,
|
||||
frame: np.ndarray,
|
||||
best_guess: Circle,
|
||||
debug=False,
|
||||
) -> Search3DResult:
|
||||
no_result = Search3DResult(Circle.null(), 0.0)
|
||||
|
||||
if best_guess.is_null():
|
||||
return no_result
|
||||
|
||||
frame, frame_roi, edge_frame, edges, roi = get_edges(
|
||||
frame,
|
||||
best_guess.normal,
|
||||
best_guess.radius,
|
||||
self.long_term_model.sphere_center,
|
||||
_EYE_RADIUS_DEFAULT,
|
||||
self.camera.focal_length,
|
||||
self.camera.resolution,
|
||||
major_axis_factor=2.5,
|
||||
)
|
||||
|
||||
if len(edges) <= 0:
|
||||
return no_result
|
||||
|
||||
(gaze_vector, pupil_radius, final_edges, edges_on_sphere) = search_on_sphere(
|
||||
edges,
|
||||
best_guess.normal,
|
||||
best_guess.radius,
|
||||
self.long_term_model.sphere_center,
|
||||
_EYE_RADIUS_DEFAULT,
|
||||
self.camera.focal_length,
|
||||
self.camera.resolution,
|
||||
)
|
||||
|
||||
if debug:
|
||||
frame_ = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
|
||||
try:
|
||||
for edge in edges_on_sphere:
|
||||
edge = project_point_into_image_plane(
|
||||
edge, self.camera.focal_length
|
||||
).astype(np.int)
|
||||
edge[0] += self.camera.resolution[0] / 2
|
||||
edge[1] += self.camera.resolution[1] / 2
|
||||
cv2.rectangle(
|
||||
frame_,
|
||||
(edge[0] - roi[2], edge[1] - roi[0]),
|
||||
(edge[0] + 1 - roi[2], edge[1] + 1 - roi[0]),
|
||||
(255, 0, 0),
|
||||
2,
|
||||
)
|
||||
|
||||
for edge in final_edges:
|
||||
edge = project_point_into_image_plane(
|
||||
edge, self.camera.focal_length
|
||||
).astype(np.int)
|
||||
edge[0] += self.camera.resolution[0] / 2
|
||||
edge[1] += self.camera.resolution[1] / 2
|
||||
cv2.rectangle(
|
||||
frame_,
|
||||
(edge[0] - roi[2], edge[1] - roi[0]),
|
||||
(edge[0] + 1 - roi[2], edge[1] + 1 - roi[0]),
|
||||
(255, 255, 255),
|
||||
1,
|
||||
)
|
||||
|
||||
cv2.imshow("", frame_)
|
||||
cv2.waitKey(1)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
pupil_center = (
|
||||
self.long_term_model.sphere_center + _EYE_RADIUS_DEFAULT * gaze_vector
|
||||
)
|
||||
pupil_circle = Circle(pupil_center, gaze_vector, pupil_radius)
|
||||
|
||||
if pupil_circle.is_null():
|
||||
confidence_3d_search = 0.0
|
||||
else:
|
||||
ellipse_2d = project_circle_into_image_plane(
|
||||
pupil_circle,
|
||||
focal_length=self.camera.focal_length,
|
||||
transform=False,
|
||||
width=self.camera.resolution[0],
|
||||
height=self.camera.resolution[1],
|
||||
)
|
||||
if ellipse_2d:
|
||||
circumference = ellipse_2d.circumference()
|
||||
confidence_3d_search = np.clip(
|
||||
len(final_edges) / circumference, 0.0, 1.0
|
||||
)
|
||||
else:
|
||||
confidence_3d_search = 0.0
|
||||
|
||||
return Search3DResult(pupil_circle, confidence_3d_search * 0.6)
|
||||
|
||||
def _prepare_result(
|
||||
self,
|
||||
observation: Observation,
|
||||
prediction_uncorrected: Prediction,
|
||||
prediction_corrected: Prediction,
|
||||
) -> Dict:
|
||||
"""[summary]
|
||||
|
||||
Args:
|
||||
observation (Observation): [description]
|
||||
prediction_uncorrected (Prediction): Used for 2d projections
|
||||
prediction_corrected (Prediction): Used for 3d data
|
||||
|
||||
Returns:
|
||||
Dict: pye3d pupil detection result
|
||||
"""
|
||||
|
||||
result = {
|
||||
"timestamp": observation.timestamp,
|
||||
"sphere": {
|
||||
"center": (
|
||||
prediction_corrected.sphere_center[0],
|
||||
prediction_corrected.sphere_center[1],
|
||||
prediction_corrected.sphere_center[2],
|
||||
),
|
||||
"radius": _EYE_RADIUS_DEFAULT,
|
||||
},
|
||||
}
|
||||
|
||||
eye_sphere_projected = project_sphere_into_image_plane(
|
||||
Sphere(prediction_uncorrected.sphere_center, _EYE_RADIUS_DEFAULT),
|
||||
transform=True,
|
||||
focal_length=self.camera.focal_length,
|
||||
width=self.camera.resolution[0],
|
||||
height=self.camera.resolution[1],
|
||||
)
|
||||
result["projected_sphere"] = ellipse2dict(eye_sphere_projected)
|
||||
|
||||
result["circle_3d"] = circle2dict(prediction_corrected.pupil_circle)
|
||||
|
||||
result["diameter_3d"] = prediction_corrected.pupil_circle.radius * 2
|
||||
|
||||
projected_pupil_circle = project_circle_into_image_plane(
|
||||
prediction_uncorrected.pupil_circle,
|
||||
focal_length=self.camera.focal_length,
|
||||
transform=True,
|
||||
width=self.camera.resolution[0],
|
||||
height=self.camera.resolution[1],
|
||||
)
|
||||
if not projected_pupil_circle:
|
||||
projected_pupil_circle = Ellipse(np.asarray([0.0, 0.0]), 0.0, 0.0, 0.0)
|
||||
|
||||
result["ellipse"] = ellipse2dict(projected_pupil_circle)
|
||||
result["location"] = result["ellipse"]["center"] # pupil center in pixels
|
||||
|
||||
# projected_pupil_circle is an OpenCV ellipse, i.e. major_radius is major diameter
|
||||
result["diameter"] = projected_pupil_circle.major_radius
|
||||
|
||||
result["confidence"] = observation.confidence
|
||||
|
||||
# Model confidence:
|
||||
# - Prior to version 0.1.0, model_confidence was fixed to 1.0 as there was no
|
||||
# way to estimate it
|
||||
# - Starting with version 0.1.0, model_confidence is 1.0 by default but set to
|
||||
# 0.1 if at least one model output exceeds its physiologically reasonable
|
||||
# range. These ranges also inform the input range for the refraction
|
||||
# correction function.
|
||||
# If the ranges are exceeded, it is likely that the model is either not fit
|
||||
# well or the 2d input ellipse was a false detection.
|
||||
model_confidence_default = 1.0
|
||||
model_confidence_out_of_range = 0.1
|
||||
model_confidence_phi_theta_nan = 0.0
|
||||
|
||||
result["model_confidence"] = model_confidence_default
|
||||
|
||||
phi, theta = cart2sph(prediction_corrected.pupil_circle.normal)
|
||||
if not np.any(np.isnan([phi, theta])):
|
||||
result["theta"] = theta
|
||||
result["phi"] = phi
|
||||
|
||||
is_phi_in_range = -80 <= np.rad2deg(phi) + 90.0 <= 80
|
||||
is_theta_in_range = -80 <= np.rad2deg(theta) - 90.0 <= 80
|
||||
if not is_phi_in_range or not is_theta_in_range:
|
||||
result["model_confidence"] = model_confidence_out_of_range
|
||||
else:
|
||||
result["theta"] = 0.0
|
||||
result["phi"] = 0.0
|
||||
result["model_confidence"] = model_confidence_phi_theta_nan
|
||||
|
||||
is_center_x_in_range = -10 <= prediction_corrected.sphere_center[0] <= 10
|
||||
is_center_y_in_range = -10 <= prediction_corrected.sphere_center[1] <= 10
|
||||
is_center_z_in_range = 20 <= prediction_corrected.sphere_center[2] <= 75
|
||||
is_diameter_in_range = 1.0 <= result["diameter_3d"] <= 9.0
|
||||
parameters_in_range = (
|
||||
is_center_x_in_range,
|
||||
is_center_y_in_range,
|
||||
is_center_z_in_range,
|
||||
is_diameter_in_range,
|
||||
)
|
||||
if not all(parameters_in_range):
|
||||
result["model_confidence"] = model_confidence_out_of_range
|
||||
|
||||
return result
|
||||
|
||||
def _collect_debug_info(self):
|
||||
debug_info = {}
|
||||
|
||||
projected_short_term = project_sphere_into_image_plane(
|
||||
Sphere(self.short_term_model.sphere_center, _EYE_RADIUS_DEFAULT),
|
||||
transform=True,
|
||||
focal_length=self.camera.focal_length,
|
||||
width=self.camera.resolution[0],
|
||||
height=self.camera.resolution[1],
|
||||
)
|
||||
projected_long_term = project_sphere_into_image_plane(
|
||||
Sphere(self.long_term_model.sphere_center, _EYE_RADIUS_DEFAULT),
|
||||
transform=True,
|
||||
focal_length=self.camera.focal_length,
|
||||
width=self.camera.resolution[0],
|
||||
height=self.camera.resolution[1],
|
||||
)
|
||||
projected_ultra_long_term = project_sphere_into_image_plane(
|
||||
Sphere(self.ultra_long_term_model.sphere_center, _EYE_RADIUS_DEFAULT),
|
||||
transform=True,
|
||||
focal_length=self.camera.focal_length,
|
||||
width=self.camera.resolution[0],
|
||||
height=self.camera.resolution[1],
|
||||
)
|
||||
debug_info["projected_short_term"] = ellipse2dict(projected_short_term)
|
||||
debug_info["projected_long_term"] = ellipse2dict(projected_long_term)
|
||||
debug_info["projected_ultra_long_term"] = ellipse2dict(
|
||||
projected_ultra_long_term
|
||||
)
|
||||
|
||||
try:
|
||||
bin_data = self.long_term_model.storage.get_bin_counts()
|
||||
max_bin_level = np.max(bin_data)
|
||||
if max_bin_level >= 0:
|
||||
bin_data = bin_data / max_bin_level
|
||||
bin_data = np.flip(bin_data, axis=0)
|
||||
debug_info["bin_data"] = bin_data.tolist()
|
||||
except AttributeError:
|
||||
debug_info["bin_data"] = []
|
||||
|
||||
# TODO: Pupil visualizer_pye3d.py attempts to draw Dierkes lines. Currently we
|
||||
# don't calculate them here, we could probably do that again. Based on which
|
||||
# model? Might be hard to do when things run in the background. We might have to
|
||||
# remove this from the visualizer_pye3d.py
|
||||
debug_info["Dierkes_lines"] = []
|
||||
|
||||
return debug_info
|
||||
|
||||
# pupil-detector interface: See base class implementation as reference:
|
||||
# https://github.com/pupil-labs/pupil-detectors/blob/master/src/pupil_detectors/detector_base.pyx
|
||||
|
||||
PUBLIC_PROPERTY_NAMES = ("is_long_term_model_frozen",)
|
||||
|
||||
def get_properties(self):
|
||||
return {
|
||||
property_name: getattr(self, property_name)
|
||||
for property_name in self.PUBLIC_PROPERTY_NAMES
|
||||
if hasattr(self, property_name)
|
||||
}
|
||||
|
||||
def update_properties(self, properties):
|
||||
keys_to_update = set(self.PUBLIC_PROPERTY_NAMES)
|
||||
keys_to_update.intersection_update(properties.keys())
|
||||
for key in keys_to_update:
|
||||
expected_type = type(getattr(self, key))
|
||||
value = properties[key]
|
||||
try:
|
||||
value = expected_type(value)
|
||||
except ValueError as e:
|
||||
raise ValueError(
|
||||
f"Value `{repr(value)}` for key `{key}` could not be converted to"
|
||||
f" expected type: {expected_type}"
|
||||
) from e
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
class _ModelUpdateSchedule:
|
||||
def __init__(self, update_interval: float, warmup_duration: float = 5.0) -> None:
|
||||
self._update_interval = update_interval
|
||||
self._warmup_duration = warmup_duration
|
||||
self._warmup_start = None
|
||||
self._paused = False
|
||||
self._last_update = None
|
||||
|
||||
@property
|
||||
def is_paused(self) -> bool:
|
||||
return self._paused
|
||||
|
||||
def pause(self) -> None:
|
||||
self._paused = True
|
||||
|
||||
def resume(self) -> None:
|
||||
self._paused = False
|
||||
self._last_update = None
|
||||
|
||||
def is_update_due(self, current_time: float):
|
||||
if self._paused:
|
||||
return False
|
||||
if self._warmup_start is None:
|
||||
self._warmup_start = current_time
|
||||
return True
|
||||
if current_time - self._warmup_start < self._warmup_duration:
|
||||
return True
|
||||
if self._last_update is None:
|
||||
self._last_update = current_time
|
||||
return True
|
||||
if current_time - self._last_update > self._update_interval:
|
||||
self._last_update = current_time
|
||||
return True
|
||||
return False
|
||||
@ -1,22 +0,0 @@
|
||||
"""
|
||||
(*)~---------------------------------------------------------------------------
|
||||
Pupil - eye tracking platform
|
||||
Copyright (C) 2012-2019 Pupil Labs
|
||||
|
||||
Distributed under the terms of the GNU
|
||||
Lesser General Public License (LGPL v3.0).
|
||||
See COPYING and COPYING.LESSER for license details.
|
||||
---------------------------------------------------------------------------~(*)
|
||||
"""
|
||||
|
||||
from .abstract import TwoSphereModelAbstract, SphereCenterEstimates
|
||||
from .base import TwoSphereModel
|
||||
from .asynchronous import TwoSphereModelAsync
|
||||
|
||||
|
||||
__all__ = [
|
||||
"TwoSphereModelAbstract",
|
||||
"TwoSphereModel",
|
||||
"TwoSphereModelAsync",
|
||||
"SphereCenterEstimates",
|
||||
]
|
||||
@ -1,116 +0,0 @@
|
||||
"""
|
||||
(*)~---------------------------------------------------------------------------
|
||||
Pupil - eye tracking platform
|
||||
Copyright (C) 2012-2019 Pupil Labs
|
||||
|
||||
Distributed under the terms of the GNU
|
||||
Lesser General Public License (LGPL v3.0).
|
||||
See COPYING and COPYING.LESSER for license details.
|
||||
---------------------------------------------------------------------------~(*)
|
||||
"""
|
||||
import abc
|
||||
import typing as T
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..geometry.primitives import Circle
|
||||
from ..observation import Observation, ObservationStorage
|
||||
from ..camera import CameraModel
|
||||
|
||||
|
||||
class SphereCenterEstimates(T.NamedTuple):
|
||||
projected: np.ndarray
|
||||
three_dim: np.ndarray
|
||||
rms_residual: T.Optional[float] = None
|
||||
|
||||
|
||||
class TwoSphereModelAbstract(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
def __init__(
|
||||
self,
|
||||
camera: CameraModel,
|
||||
storage_cls: T.Type[ObservationStorage] = None,
|
||||
storage_kwargs: T.Dict = None,
|
||||
):
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def add_observation(self, observation: Observation):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def n_observations(self) -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def sphere_center(self) -> np.ndarray:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def corrected_sphere_center(self) -> np.ndarray:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def projected_sphere_center(self) -> np.ndarray:
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_sphere_center(self, new_sphere_center: np.ndarray):
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def estimate_sphere_center(
|
||||
self,
|
||||
from_2d: T.Optional[np.ndarray] = None,
|
||||
prior_3d: T.Optional[np.ndarray] = None,
|
||||
prior_strength: float = 0.0,
|
||||
calculate_rms_residual: bool = False,
|
||||
) -> SphereCenterEstimates:
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def estimate_sphere_center_2d(self) -> np.ndarray:
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def estimate_sphere_center_3d(
|
||||
self,
|
||||
sphere_center_2d: np.ndarray,
|
||||
prior_3d: T.Optional[np.ndarray] = None,
|
||||
prior_strength: float = 0.0,
|
||||
calculate_rms_residual: bool = False,
|
||||
) -> T.Tuple[np.array, T.Optional[float]]:
|
||||
raise NotImplementedError
|
||||
|
||||
# GAZE PREDICTION
|
||||
@abc.abstractmethod
|
||||
def _extract_unproject_disambiguate(self, pupil_datum: T.Dict) -> Circle:
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def _disambiguate_circle_3d_pair(
|
||||
self, circle_3d_pair: T.Tuple[Circle, Circle]
|
||||
) -> Circle:
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def predict_pupil_circle(
|
||||
self, observation: Observation, use_unprojection: bool = False
|
||||
) -> Circle:
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def apply_refraction_correction(self, pupil_circle: Circle) -> Circle:
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def mean_observation_circularity(self) -> float:
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def cleanup(self):
|
||||
raise NotImplementedError
|
||||
@ -1,320 +0,0 @@
|
||||
"""
|
||||
(*)~---------------------------------------------------------------------------
|
||||
Pupil - eye tracking platform
|
||||
Copyright (C) 2012-2019 Pupil Labs
|
||||
|
||||
Distributed under the terms of the GNU
|
||||
Lesser General Public License (LGPL v3.0).
|
||||
See COPYING and COPYING.LESSER for license details.
|
||||
---------------------------------------------------------------------------~(*)
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import logging
|
||||
import typing as T
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..constants import DEFAULT_SPHERE_CENTER
|
||||
from .abstract import (
|
||||
TwoSphereModelAbstract,
|
||||
CameraModel,
|
||||
Circle,
|
||||
Observation,
|
||||
ObservationStorage,
|
||||
SphereCenterEstimates,
|
||||
)
|
||||
from .background_helper import BackgroundProcess, mp
|
||||
from .base import TwoSphereModel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TwoSphereModelAsync(TwoSphereModelAbstract):
|
||||
def __init__(
|
||||
self,
|
||||
camera: CameraModel,
|
||||
storage_cls: T.Type[ObservationStorage] = None,
|
||||
storage_kwargs: T.Dict = None,
|
||||
):
|
||||
synced_sphere_center = mp.Array(ctypes.c_double, 3)
|
||||
synced_corrected_sphere_center = mp.Array(ctypes.c_double, 3)
|
||||
synced_projected_sphere_center = mp.Array(ctypes.c_double, 2)
|
||||
synced_observation_count = mp.Value(ctypes.c_long)
|
||||
synced_rms_residual = mp.Value(ctypes.c_double)
|
||||
is_estimation_ongoing_flag = mp.Event()
|
||||
|
||||
self._frontend = _TwoSphereModelSyncedFrontend(
|
||||
synced_sphere_center,
|
||||
synced_corrected_sphere_center,
|
||||
synced_projected_sphere_center,
|
||||
synced_observation_count,
|
||||
synced_rms_residual,
|
||||
is_estimation_ongoing_flag,
|
||||
camera=camera,
|
||||
)
|
||||
self._backend_process = BackgroundProcess(
|
||||
function=self._process_relayed_commands,
|
||||
setup=self._setup_backend,
|
||||
setup_args=(
|
||||
synced_sphere_center,
|
||||
synced_corrected_sphere_center,
|
||||
synced_projected_sphere_center,
|
||||
synced_observation_count,
|
||||
synced_rms_residual,
|
||||
is_estimation_ongoing_flag,
|
||||
),
|
||||
setup_kwargs=dict(
|
||||
camera=camera,
|
||||
storage_cls=storage_cls,
|
||||
storage_kwargs=storage_kwargs,
|
||||
),
|
||||
cleanup=self._cleanup_backend,
|
||||
log_handlers=logging.getLogger().handlers,
|
||||
)
|
||||
|
||||
@property
|
||||
def sphere_center(self) -> np.ndarray:
|
||||
return self._frontend.sphere_center
|
||||
|
||||
@property
|
||||
def corrected_sphere_center(self) -> np.ndarray:
|
||||
return self._frontend.corrected_sphere_center
|
||||
|
||||
@property
|
||||
def projected_sphere_center(self) -> np.ndarray:
|
||||
return self._frontend.projected_sphere_center
|
||||
|
||||
@property
|
||||
def rms_residual(self) -> float:
|
||||
return self._frontend.rms_residual
|
||||
|
||||
def relay_command(self, function_name: str, *args, **kwargs):
|
||||
self._backend_process.send(function_name, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _process_relayed_commands(
|
||||
backend: "_TwoSphereModelSyncedBackend", function_name: str, *args, **kwargs
|
||||
):
|
||||
function = getattr(backend, function_name)
|
||||
return function(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _setup_backend(*args, **kwargs) -> "_TwoSphereModelSyncedBackend":
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.debug(f"Setting up backend: {args}, {kwargs}")
|
||||
return _TwoSphereModelSyncedBackend(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _cleanup_backend(backend: "_TwoSphereModelSyncedBackend"):
|
||||
backend.cleanup()
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.debug(f"Backend cleaned")
|
||||
|
||||
def add_observation(self, observation: Observation):
|
||||
self.relay_command("add_observation", observation)
|
||||
|
||||
@property
|
||||
def n_observations(self) -> int:
|
||||
return self._frontend.n_observations
|
||||
|
||||
def set_sphere_center(self, new_sphere_center: np.ndarray):
|
||||
raise NotImplementedError
|
||||
|
||||
def estimate_sphere_center(
|
||||
self,
|
||||
from_2d: T.Optional[np.ndarray] = None,
|
||||
prior_3d: T.Optional[np.ndarray] = None,
|
||||
prior_strength: float = 0.0,
|
||||
calculate_rms_residual=False,
|
||||
) -> SphereCenterEstimates:
|
||||
if not self._frontend._is_estimation_ongoing_flag.is_set():
|
||||
self.relay_command(
|
||||
"estimate_sphere_center",
|
||||
from_2d,
|
||||
prior_3d,
|
||||
prior_strength,
|
||||
calculate_rms_residual,
|
||||
)
|
||||
self._frontend._is_estimation_ongoing_flag.set()
|
||||
projected_sphere_center = self._frontend.projected_sphere_center
|
||||
sphere_center = self._frontend.sphere_center
|
||||
rms_residual = self._frontend.rms_residual
|
||||
return SphereCenterEstimates(
|
||||
projected_sphere_center, sphere_center, rms_residual
|
||||
)
|
||||
|
||||
def estimate_sphere_center_2d(self) -> np.ndarray:
|
||||
raise NotImplementedError
|
||||
|
||||
def estimate_sphere_center_3d(
|
||||
self,
|
||||
sphere_center_2d: np.ndarray,
|
||||
prior_3d: T.Optional[np.ndarray] = None,
|
||||
prior_strength: float = 0.0,
|
||||
calculate_rms_residual: bool = False,
|
||||
) -> T.Tuple[np.array, T.Optional[float]]:
|
||||
raise NotImplementedError
|
||||
|
||||
# GAZE PREDICTION
|
||||
def _extract_unproject_disambiguate(self, pupil_datum: T.Dict) -> Circle:
|
||||
return self._frontend._extract_unproject_disambiguate(pupil_datum)
|
||||
|
||||
def _disambiguate_circle_3d_pair(
|
||||
self, circle_3d_pair: T.Tuple[Circle, Circle]
|
||||
) -> Circle:
|
||||
return self._frontend._disambiguate_circle_3d_pair(circle_3d_pair)
|
||||
|
||||
def predict_pupil_circle(
|
||||
self, observation: Observation, use_unprojection: bool = False
|
||||
) -> Circle:
|
||||
return self._frontend.predict_pupil_circle(observation, use_unprojection)
|
||||
|
||||
def apply_refraction_correction(self, pupil_circle: Circle) -> Circle:
|
||||
return self._frontend.apply_refraction_correction(pupil_circle)
|
||||
|
||||
def cleanup(self):
|
||||
logger.debug("Cancelling backend process")
|
||||
self._backend_process.cancel()
|
||||
self._frontend.cleanup()
|
||||
|
||||
def mean_observation_circularity(self) -> float:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class _TwoSphereModelSyncedAbstract(TwoSphereModel):
|
||||
def __init__(
|
||||
self,
|
||||
synced_sphere_center: mp.Array, # c_double_Array_3
|
||||
synced_corrected_sphere_center: mp.Array, # c_double_Array_3
|
||||
synced_projected_sphere_center: mp.Array, # c_double_Array_2
|
||||
synced_observation_count: mp.Value, # c_long
|
||||
synced_rms_residual: mp.Value, # c_double
|
||||
flag_is_estimation_ongoing: mp.Event,
|
||||
**kwargs,
|
||||
):
|
||||
self._synced_sphere_center = synced_sphere_center
|
||||
self._synced_corrected_sphere_center = synced_corrected_sphere_center
|
||||
self._synced_projected_sphere_center = synced_projected_sphere_center
|
||||
self._synced_observation_count = synced_observation_count
|
||||
self._synced_rms_residual = synced_rms_residual
|
||||
self._is_estimation_ongoing_flag = flag_is_estimation_ongoing
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@property
|
||||
def sphere_center(self):
|
||||
with self._synced_sphere_center:
|
||||
return np.array(self._synced_sphere_center.get_obj())
|
||||
|
||||
@sphere_center.setter
|
||||
def sphere_center(self, coordinates: np.array):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def corrected_sphere_center(self):
|
||||
with self._synced_corrected_sphere_center:
|
||||
return np.array(self._synced_corrected_sphere_center.get_obj())
|
||||
|
||||
@corrected_sphere_center.setter
|
||||
def corrected_sphere_center(self, coordinates: np.array):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def projected_sphere_center(self):
|
||||
with self._synced_projected_sphere_center:
|
||||
return np.array(self._synced_projected_sphere_center.get_obj())
|
||||
|
||||
@projected_sphere_center.setter
|
||||
def projected_sphere_center(self, coordinates: np.array):
|
||||
raise NotImplementedError
|
||||
|
||||
def mean_observation_circularity(self) -> float:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def rms_residual(self) -> float:
|
||||
with self._synced_rms_residual:
|
||||
return self._synced_rms_residual.value
|
||||
|
||||
@rms_residual.setter
|
||||
def rms_residual(self, residual: float):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class _TwoSphereModelSyncedFrontend(_TwoSphereModelSyncedAbstract):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
del self.storage # There is no storage in the frontend
|
||||
|
||||
def _set_default_model_params(self):
|
||||
with self._synced_sphere_center:
|
||||
self._synced_sphere_center[:] = DEFAULT_SPHERE_CENTER
|
||||
|
||||
corrected_sphere_center = self.refractionizer.correct_sphere_center(
|
||||
np.asarray([[*self.sphere_center]])
|
||||
)[0]
|
||||
with self._synced_corrected_sphere_center:
|
||||
self._synced_corrected_sphere_center[:] = corrected_sphere_center
|
||||
|
||||
@property
|
||||
def n_observations(self) -> int:
|
||||
return self._synced_observation_count.value
|
||||
|
||||
|
||||
class _TwoSphereModelSyncedBackend(_TwoSphereModelSyncedAbstract):
|
||||
@property
|
||||
def sphere_center(self):
|
||||
return super().sphere_center
|
||||
|
||||
@sphere_center.setter
|
||||
def sphere_center(self, coordinates: np.array):
|
||||
with self._synced_sphere_center:
|
||||
self._synced_sphere_center[:] = coordinates
|
||||
|
||||
@property
|
||||
def corrected_sphere_center(self):
|
||||
return super().corrected_sphere_center
|
||||
|
||||
@corrected_sphere_center.setter
|
||||
def corrected_sphere_center(self, coordinates: np.array):
|
||||
with self._synced_corrected_sphere_center:
|
||||
self._synced_corrected_sphere_center[:] = coordinates
|
||||
|
||||
@property
|
||||
def projected_sphere_center(self):
|
||||
return super().projected_sphere_center
|
||||
|
||||
@projected_sphere_center.setter
|
||||
def projected_sphere_center(self, coordinates: np.array):
|
||||
with self._synced_projected_sphere_center:
|
||||
self._synced_projected_sphere_center[:] = coordinates
|
||||
|
||||
def add_observation(self, observation: Observation):
|
||||
super().add_observation(observation=observation)
|
||||
n_observations = super().n_observations
|
||||
with self._synced_observation_count:
|
||||
self._synced_observation_count.value = n_observations
|
||||
|
||||
@property
|
||||
def n_observations(self) -> int:
|
||||
return self._synced_observation_count.value
|
||||
|
||||
def estimate_sphere_center(self, *args, **kwargs):
|
||||
result = super().estimate_sphere_center(*args, **kwargs)
|
||||
self._is_estimation_ongoing_flag.clear()
|
||||
return result
|
||||
|
||||
def estimate_sphere_center_2d(self) -> np.ndarray:
|
||||
estimated: np.ndarray = super().estimate_sphere_center_2d()
|
||||
self.projected_sphere_center = estimated
|
||||
return estimated
|
||||
|
||||
@property
|
||||
def rms_residual(self) -> float:
|
||||
with self._synced_rms_residual:
|
||||
return self._synced_rms_residual.value
|
||||
|
||||
@rms_residual.setter
|
||||
def rms_residual(self, residual: float):
|
||||
with self._synced_rms_residual:
|
||||
self._synced_rms_residual.value = residual
|
||||
@ -1,164 +0,0 @@
|
||||
"""
|
||||
(*)~---------------------------------------------------------------------------
|
||||
Pupil - eye tracking platform
|
||||
Copyright (C) 2012-2020 Pupil Labs
|
||||
|
||||
Distributed under the terms of the GNU
|
||||
Lesser General Public License (LGPL v3.0).
|
||||
See COPYING and COPYING.LESSER for license details.
|
||||
---------------------------------------------------------------------------~(*)
|
||||
"""
|
||||
|
||||
import logging
|
||||
import multiprocessing as mp
|
||||
import queue
|
||||
import signal
|
||||
import time
|
||||
from ctypes import c_bool
|
||||
from logging import Handler
|
||||
from logging.handlers import QueueHandler, QueueListener
|
||||
import traceback
|
||||
from typing import Any, Callable, Dict, Iterable, Optional, Tuple, TypeVar
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
WorkerSetupResult = TypeVar("WorkerSetupResult")
|
||||
WorkerFunctionResult = TypeVar("WorkerFunctionResult")
|
||||
|
||||
|
||||
class BackgroundProcess:
|
||||
class StoppedError(Exception):
|
||||
"""Interaction with a BackgroundProcess that was stopped."""
|
||||
|
||||
class NothingToReceiveError(Exception):
|
||||
"""Trying to receive data from BackgroundProcess without sending input first."""
|
||||
|
||||
class MultipleSendError(Exception):
|
||||
"""Trying to send data without first receiving previous output."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
setup: Callable[..., WorkerSetupResult],
|
||||
function: Callable[[WorkerSetupResult], WorkerFunctionResult],
|
||||
cleanup: Callable[[WorkerSetupResult], None],
|
||||
setup_args: Optional[Tuple] = None,
|
||||
setup_kwargs: Optional[Dict] = None,
|
||||
log_handlers: Iterable[Handler] = (),
|
||||
):
|
||||
self._running = True
|
||||
|
||||
self._task_queue = mp.Queue(maxsize=500) # TODO: figure out good value
|
||||
|
||||
logging_queue = mp.Queue()
|
||||
self._log_listener = QueueListener(logging_queue, *log_handlers)
|
||||
self._log_listener.start()
|
||||
|
||||
self._should_terminate_flag = mp.Value(c_bool, 0)
|
||||
|
||||
self._process = mp.Process(
|
||||
name="Pye3D Background Process",
|
||||
daemon=True,
|
||||
target=BackgroundProcess._worker,
|
||||
kwargs=dict(
|
||||
setup=setup,
|
||||
function=function,
|
||||
cleanup=cleanup,
|
||||
task_queue=self._task_queue,
|
||||
should_terminate_flag=self._should_terminate_flag,
|
||||
logging_queue=logging_queue,
|
||||
setup_args=setup_args if setup_args else (),
|
||||
setup_kwargs=setup_kwargs if setup_kwargs else {},
|
||||
),
|
||||
)
|
||||
self._process.start()
|
||||
|
||||
@property
|
||||
def running(self) -> bool:
|
||||
"""Whether background task is running (not necessarily doing work)."""
|
||||
return self._running and self._process.is_alive()
|
||||
|
||||
def send(self, *args: Tuple[Any], **kwargs: Dict[Any, Any]):
|
||||
"""Send data to background process for processing.
|
||||
Raises StoppedError when called on a stopped process.
|
||||
"""
|
||||
|
||||
if not self.running:
|
||||
logger.error("Background process was closed previously!")
|
||||
raise BackgroundProcess.StoppedError()
|
||||
|
||||
try:
|
||||
self._task_queue.put_nowait({"args": args, "kwargs": kwargs})
|
||||
except queue.Full:
|
||||
logger.debug(f"Dropping task! args: {args}, kwargs: {kwargs}")
|
||||
|
||||
def cancel(self, timeout=-1):
|
||||
"""Stop process as soon as current task is finished."""
|
||||
|
||||
self._should_terminate_flag.value = 1
|
||||
if self.running:
|
||||
self._task_queue.close()
|
||||
self._task_queue.cancel_join_thread()
|
||||
self._task_queue.join_thread()
|
||||
self._process.join(timeout)
|
||||
self._running = False
|
||||
self._log_listener.stop()
|
||||
|
||||
@staticmethod
|
||||
def _install_sigint_interception():
|
||||
def interrupt_handler(sig, frame):
|
||||
import traceback
|
||||
|
||||
trace = traceback.format_stack(f=frame)
|
||||
logger.debug(f"Caught (and dropping) signal {sig} in:\n" + "".join(trace))
|
||||
|
||||
signal.signal(signal.SIGINT, interrupt_handler)
|
||||
|
||||
@staticmethod
|
||||
def _worker(
|
||||
setup: Callable[..., WorkerSetupResult],
|
||||
function: Callable[[WorkerSetupResult], Any],
|
||||
cleanup: Callable[[WorkerSetupResult], None],
|
||||
task_queue: mp.Queue,
|
||||
should_terminate_flag: mp.Value,
|
||||
logging_queue: mp.Queue,
|
||||
setup_args: Tuple,
|
||||
setup_kwargs: Dict,
|
||||
):
|
||||
log_queue_handler = QueueHandler(logging_queue)
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.NOTSET)
|
||||
logger.addHandler(log_queue_handler)
|
||||
|
||||
# Intercept SIGINT (ctrl-c), do required cleanup in foreground process!
|
||||
BackgroundProcess._install_sigint_interception()
|
||||
|
||||
setup_result: WorkerSetupResult = setup(*setup_args, **setup_kwargs)
|
||||
|
||||
while not should_terminate_flag.value:
|
||||
try:
|
||||
params = task_queue.get(block=True, timeout=0.1)
|
||||
args = params["args"]
|
||||
kwargs = params["kwargs"]
|
||||
except queue.Empty:
|
||||
continue
|
||||
# except EOFError:
|
||||
# logger.info("Pipe was closed from foreground process .")
|
||||
# break
|
||||
|
||||
try:
|
||||
t0 = time.perf_counter()
|
||||
function(setup_result, *args, **kwargs)
|
||||
t1 = time.perf_counter()
|
||||
# logger.debug(f"Finished background calculation in {(t1 - t0):.2}s")
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error executing background process with parameters {params}:\n{e}"
|
||||
)
|
||||
logger.debug(traceback.format_exc())
|
||||
break
|
||||
else:
|
||||
logger.info("Background process received termination signal.")
|
||||
|
||||
cleanup(setup_result)
|
||||
|
||||
logger.info("Stopping background process.")
|
||||
logger.removeHandler(log_queue_handler)
|
||||
@ -1,297 +0,0 @@
|
||||
"""
|
||||
(*)~---------------------------------------------------------------------------
|
||||
Pupil - eye tracking platform
|
||||
Copyright (C) 2012-2019 Pupil Labs
|
||||
|
||||
Distributed under the terms of the GNU
|
||||
Lesser General Public License (LGPL v3.0).
|
||||
See COPYING and COPYING.LESSER for license details.
|
||||
---------------------------------------------------------------------------~(*)
|
||||
"""
|
||||
import logging
|
||||
import typing as T
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .abstract import TwoSphereModelAbstract, SphereCenterEstimates
|
||||
from ..camera import CameraModel
|
||||
from ..constants import _EYE_RADIUS_DEFAULT, DEFAULT_SPHERE_CENTER
|
||||
from ..geometry.intersections import nearest_point_on_sphere_to_line
|
||||
from ..geometry.primitives import Circle, Line
|
||||
from ..geometry.projections import (
|
||||
project_line_into_image_plane,
|
||||
project_point_into_image_plane,
|
||||
unproject_ellipse,
|
||||
)
|
||||
from ..geometry.utilities import normalize
|
||||
from ..observation import BasicStorage, Observation, ObservationStorage
|
||||
from ..refraction import Refractionizer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TwoSphereModel(TwoSphereModelAbstract):
|
||||
def __init__(
|
||||
self,
|
||||
camera: CameraModel,
|
||||
storage_cls: T.Type[ObservationStorage] = None,
|
||||
storage_kwargs: T.Dict = None,
|
||||
):
|
||||
if storage_cls:
|
||||
kwargs = storage_kwargs if storage_kwargs is not None else {}
|
||||
self.storage = storage_cls(**kwargs)
|
||||
else:
|
||||
self.storage = BasicStorage()
|
||||
self.camera = camera
|
||||
|
||||
self.refractionizer = Refractionizer()
|
||||
self._set_default_model_params()
|
||||
|
||||
@property
|
||||
def sphere_center(self) -> np.ndarray:
|
||||
return self._sphere_center
|
||||
|
||||
@sphere_center.setter
|
||||
def sphere_center(self, coordinates: np.ndarray):
|
||||
self._sphere_center = coordinates
|
||||
|
||||
@property
|
||||
def corrected_sphere_center(self) -> np.ndarray:
|
||||
return self._corrected_sphere_center
|
||||
|
||||
@corrected_sphere_center.setter
|
||||
def corrected_sphere_center(self, coordinates: np.ndarray):
|
||||
self._corrected_sphere_center = coordinates
|
||||
|
||||
@property
|
||||
def projected_sphere_center(self) -> np.ndarray:
|
||||
return self._projected_sphere_center
|
||||
|
||||
@projected_sphere_center.setter
|
||||
def projected_sphere_center(self, projected_sphere_center: np.ndarray):
|
||||
self._projected_sphere_center = projected_sphere_center
|
||||
|
||||
def _set_default_model_params(self):
|
||||
# Overwrite in subclasses that do not allow setting these attributes
|
||||
self._sphere_center = np.asarray(DEFAULT_SPHERE_CENTER)
|
||||
self._corrected_sphere_center = self.refractionizer.correct_sphere_center(
|
||||
np.asarray([[*self.sphere_center]])
|
||||
)[0]
|
||||
self.rms_residual = np.nan
|
||||
|
||||
def add_observation(self, observation: Observation):
|
||||
self.storage.add(observation)
|
||||
|
||||
@property
|
||||
def n_observations(self) -> int:
|
||||
return self.storage.count()
|
||||
|
||||
def set_sphere_center(self, new_sphere_center):
|
||||
self.sphere_center = new_sphere_center
|
||||
self.corrected_sphere_center = self.refractionizer.correct_sphere_center(
|
||||
np.asarray([[*self.sphere_center]])
|
||||
)[0]
|
||||
|
||||
def estimate_sphere_center(
|
||||
self,
|
||||
from_2d=None,
|
||||
prior_3d=None,
|
||||
prior_strength=0.0,
|
||||
calculate_rms_residual=False,
|
||||
):
|
||||
self.projected_sphere_center = (
|
||||
from_2d if from_2d is not None else self.estimate_sphere_center_2d()
|
||||
)
|
||||
sphere_center, rms_residual = self.estimate_sphere_center_3d(
|
||||
self.projected_sphere_center,
|
||||
prior_3d,
|
||||
prior_strength,
|
||||
calculate_rms_residual=calculate_rms_residual,
|
||||
)
|
||||
self.set_sphere_center(sphere_center)
|
||||
self.rms_residual = rms_residual if rms_residual is not None else float("nan")
|
||||
return SphereCenterEstimates(
|
||||
self.projected_sphere_center, sphere_center, rms_residual
|
||||
)
|
||||
|
||||
def estimate_sphere_center_2d(self):
|
||||
observations = self.storage.observations
|
||||
|
||||
# slightly faster than np.array
|
||||
aux_2d = np.concatenate([obs.aux_2d for obs in observations])
|
||||
aux_2d.shape = -1, 2, 3
|
||||
|
||||
# Estimate projected sphere center by nearest intersection of 2d gaze lines
|
||||
sum_aux_2d = aux_2d.sum(axis=0)
|
||||
projected_sphere_center = np.linalg.pinv(sum_aux_2d[:2, :2]) @ sum_aux_2d[:2, 2]
|
||||
|
||||
return projected_sphere_center
|
||||
|
||||
def estimate_sphere_center_3d(
|
||||
self,
|
||||
sphere_center_2d,
|
||||
prior_3d=None,
|
||||
prior_strength=0.0,
|
||||
calculate_rms_residual=False,
|
||||
) -> T.Tuple[np.array, T.Optional[float]]:
|
||||
observations, aux_3d, gaze_2d = self._prep_data()
|
||||
sum_aux_3d, disamb_indices, aux_3d_disamb = self._disambiguate_dierkes_lines(
|
||||
aux_3d, gaze_2d, sphere_center_2d
|
||||
)
|
||||
sphere_center = self._calc_sphere_center(sum_aux_3d, prior_3d, prior_strength)
|
||||
|
||||
rms_residual = (
|
||||
self._calc_rms_residual(
|
||||
observations, disamb_indices, sphere_center, aux_3d_disamb
|
||||
)
|
||||
if calculate_rms_residual
|
||||
else None
|
||||
)
|
||||
|
||||
return sphere_center, rms_residual
|
||||
|
||||
def _prep_data(self):
|
||||
observations = self.storage.observations
|
||||
aux_3d = np.concatenate([obs.aux_3d for obs in observations])
|
||||
aux_3d.shape = -1, 2, 3, 4
|
||||
gaze_2d = np.concatenate([obs.gaze_2d_line for obs in observations])
|
||||
gaze_2d.shape = -1, 4
|
||||
return observations, aux_3d, gaze_2d
|
||||
|
||||
def _disambiguate_dierkes_lines(self, aux_3d, gaze_2d, sphere_center_2d):
|
||||
# Disambiguate Dierkes lines
|
||||
# We want gaze_2d to points towards the sphere center. gaze_2d was collected
|
||||
# from Dierkes[0]. If it points into the correct direction, we know that
|
||||
# Dierkes[0] is the correct one to use, otherwise we need to use Dierkes[1]. We
|
||||
# can check that with the sign of the dot product.
|
||||
gaze_2d_origins = gaze_2d[:, :2]
|
||||
gaze_2d_directions = gaze_2d[:, 2:]
|
||||
gaze_2d_towards_center = gaze_2d_origins - sphere_center_2d
|
||||
|
||||
dot_products = np.sum(gaze_2d_towards_center * gaze_2d_directions, axis=1)
|
||||
disambiguation_indices = np.where(dot_products < 0, 1, 0)
|
||||
|
||||
obs_idc = np.arange(disambiguation_indices.shape[0])
|
||||
aux_3d_disambiguated = aux_3d[obs_idc, disambiguation_indices, :, :]
|
||||
|
||||
# Estimate sphere center by nearest intersection of Dierkes lines
|
||||
sum_aux_3d = aux_3d_disambiguated.sum(axis=0)
|
||||
return sum_aux_3d, disambiguation_indices, aux_3d_disambiguated
|
||||
|
||||
def _calc_sphere_center(self, sum_aux_3d, prior_3d=None, prior_strength=0.0):
|
||||
matrix = sum_aux_3d[:3, :3]
|
||||
try:
|
||||
if prior_3d is None:
|
||||
return np.linalg.pinv(matrix) @ sum_aux_3d[:3, 3]
|
||||
else:
|
||||
return np.linalg.pinv(matrix + prior_strength * np.eye(3)) @ (
|
||||
sum_aux_3d[:3, 3] + prior_strength * prior_3d
|
||||
)
|
||||
except np.linalg.LinAlgError:
|
||||
# happens if lines are parallel, very rare
|
||||
return DEFAULT_SPHERE_CENTER
|
||||
|
||||
def _calc_rms_residual(
|
||||
self, observations, disamb_indices, sphere_center, aux_3d_disamb
|
||||
):
|
||||
# Here we use eq. (10) in https://docplayer.net/21072949-Least-squares-intersection-of-lines.html.
|
||||
origins_dierkes_lines = np.array(
|
||||
[
|
||||
obs.get_Dierkes_line(idx).origin
|
||||
for obs, idx in zip(observations, disamb_indices)
|
||||
]
|
||||
)
|
||||
origins_dierkes_lines.shape = -1, 3, 1
|
||||
deltas = origins_dierkes_lines - sphere_center[:, np.newaxis]
|
||||
tmp = np.einsum("ijk,ikl->ijl", aux_3d_disamb[:, :3, :3], deltas)
|
||||
squared_residuals = np.einsum(
|
||||
"ikj,ijk->i", np.transpose(deltas, (0, 2, 1)), tmp
|
||||
)
|
||||
rms_residual = np.clip(squared_residuals, 0.0, None)
|
||||
rms_residual = np.mean(np.sqrt(rms_residual))
|
||||
return rms_residual
|
||||
|
||||
# GAZE PREDICTION
|
||||
def _extract_unproject_disambiguate(self, pupil_datum):
|
||||
ellipse = self._extract_ellipse(pupil_datum)
|
||||
circle_3d_pair = unproject_ellipse(ellipse, self.camera.focal_length)
|
||||
if circle_3d_pair:
|
||||
circle_3d = self._disambiguate_circle_3d_pair(circle_3d_pair)
|
||||
else:
|
||||
circle_3d = Circle([0.0, 0.0, 0.0], [0.0, 0.0, -1.0], 0.0)
|
||||
return circle_3d
|
||||
|
||||
def _disambiguate_circle_3d_pair(self, circle_3d_pair):
|
||||
circle_center_2d = project_point_into_image_plane(
|
||||
circle_3d_pair[0].center, self.camera.focal_length
|
||||
)
|
||||
circle_normal_2d = normalize(
|
||||
project_line_into_image_plane(
|
||||
Line(circle_3d_pair[0].center, circle_3d_pair[0].normal),
|
||||
self.camera.focal_length,
|
||||
).direction
|
||||
)
|
||||
sphere_center_2d = project_point_into_image_plane(
|
||||
self.sphere_center, self.camera.focal_length
|
||||
)
|
||||
|
||||
if np.dot(circle_center_2d - sphere_center_2d, circle_normal_2d) >= 0:
|
||||
return circle_3d_pair[0]
|
||||
else:
|
||||
return circle_3d_pair[1]
|
||||
|
||||
def predict_pupil_circle(
|
||||
self, observation: Observation, use_unprojection: bool = False
|
||||
) -> Circle:
|
||||
if observation.invalid:
|
||||
return Circle.null()
|
||||
|
||||
circle_3d = self._disambiguate_circle_3d_pair(observation.circle_3d_pair)
|
||||
unprojection_depth = np.linalg.norm(circle_3d.center)
|
||||
direction = circle_3d.center / unprojection_depth
|
||||
|
||||
nearest_point_on_sphere = nearest_point_on_sphere_to_line(
|
||||
self.sphere_center, _EYE_RADIUS_DEFAULT, [0.0, 0.0, 0.0], direction
|
||||
)
|
||||
|
||||
if use_unprojection:
|
||||
gaze_vector = circle_3d.normal
|
||||
else:
|
||||
gaze_vector = normalize(nearest_point_on_sphere - self.sphere_center)
|
||||
|
||||
radius = np.linalg.norm(nearest_point_on_sphere) / unprojection_depth
|
||||
pupil_circle = Circle(nearest_point_on_sphere, gaze_vector, radius)
|
||||
return pupil_circle
|
||||
|
||||
def apply_refraction_correction(self, pupil_circle):
|
||||
input_features = np.asarray(
|
||||
[[*self.sphere_center, *pupil_circle.normal, pupil_circle.radius]]
|
||||
)
|
||||
refraction_corrected_params = self.refractionizer.correct_pupil_circle(
|
||||
input_features
|
||||
)[0]
|
||||
|
||||
refraction_corrected_gaze_vector = normalize(refraction_corrected_params[:3])
|
||||
refraction_corrected_radius = refraction_corrected_params[-1]
|
||||
refraction_corrected_pupil_center = (
|
||||
self.corrected_sphere_center
|
||||
+ _EYE_RADIUS_DEFAULT * refraction_corrected_gaze_vector
|
||||
)
|
||||
|
||||
refraction_corrected_pupil_circle = Circle(
|
||||
refraction_corrected_pupil_center,
|
||||
refraction_corrected_gaze_vector,
|
||||
refraction_corrected_radius,
|
||||
)
|
||||
|
||||
return refraction_corrected_pupil_circle
|
||||
|
||||
def mean_observation_circularity(self):
|
||||
observation_circularities = [
|
||||
observation.ellipse.circularity()
|
||||
for observation in self.storage.observations
|
||||
]
|
||||
return np.mean(observation_circularities)
|
||||
|
||||
def cleanup(self):
|
||||
pass
|
||||
@ -1,161 +0,0 @@
|
||||
"""
|
||||
(*)~---------------------------------------------------------------------------
|
||||
Pupil - eye tracking platform
|
||||
Copyright (C) 2012-2019 Pupil Labs
|
||||
|
||||
Distributed under the terms of the GNU
|
||||
Lesser General Public License (LGPL v3.0).
|
||||
See COPYING and COPYING.LESSER for license details.
|
||||
---------------------------------------------------------------------------~(*)
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
|
||||
def intersect_line_line(p11, p12, p21, p22, internal=False):
|
||||
x1, y1 = p11
|
||||
x2, y2 = p12
|
||||
x3, y3 = p21
|
||||
x4, y4 = p22
|
||||
|
||||
if ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)) != 0:
|
||||
Px = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / (
|
||||
(x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
|
||||
)
|
||||
Py = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / (
|
||||
(x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
|
||||
)
|
||||
if internal:
|
||||
if x1 != x2:
|
||||
lam = (Px - x2) / (x1 - x2)
|
||||
else:
|
||||
lam = (Py - y2) / (y1 - y2)
|
||||
if 0 <= lam <= 1:
|
||||
return [True, Px, Py]
|
||||
else:
|
||||
return [False]
|
||||
else:
|
||||
return [True, Px, Py]
|
||||
else:
|
||||
return [False]
|
||||
|
||||
|
||||
def intersect_sphere_multiple_lines(sphere_center, radius, points, directions):
|
||||
# Note: Directions need to be normalized!
|
||||
intermediate = np.einsum("ij,ij->i", directions, points - sphere_center)
|
||||
discriminant = (
|
||||
intermediate ** 2 - np.sum((points - sphere_center) ** 2, axis=1) + radius ** 2
|
||||
)
|
||||
idx = discriminant > 0
|
||||
sqr = np.sqrt(discriminant[idx])
|
||||
d1 = -intermediate[idx] + sqr
|
||||
d2 = -intermediate[idx] - sqr
|
||||
d_final = np.expand_dims(np.minimum(d1, d2), axis=1)
|
||||
intersections_on_sphere = points[idx] + d_final * directions[idx]
|
||||
|
||||
return intersections_on_sphere, idx
|
||||
|
||||
|
||||
def intersect_sphere_line(sphere_center, radius, point, direction):
|
||||
temp = np.dot(direction, point - sphere_center)
|
||||
discriminant = temp ** 2 - np.linalg.norm(point - sphere_center) ** 2 + radius ** 2
|
||||
if discriminant >= 0.0:
|
||||
sqr = np.sqrt(discriminant)
|
||||
d1 = -temp + sqr
|
||||
d2 = -temp - sqr
|
||||
return [True, d1, d2]
|
||||
else:
|
||||
return [False, 0.0, 0.0]
|
||||
|
||||
|
||||
def intersect_plane_line(p_plane, n_plane, p_line, l_line, radius=-1):
|
||||
if np.dot(n_plane, l_line) == 0 or np.dot(p_plane - p_line, n_plane) == 0:
|
||||
return [False]
|
||||
else:
|
||||
d = np.dot(p_plane - p_line, n_plane) / np.dot(l_line, n_plane)
|
||||
p_intersect = p_line + d * l_line
|
||||
if radius > 0:
|
||||
if np.linalg.norm(p_plane - p_intersect) <= radius[0]:
|
||||
return [True, p_intersect[0], p_intersect[1], p_intersect[2]]
|
||||
else:
|
||||
return [False, 0.0, 0.0, 0.0]
|
||||
else:
|
||||
return [True, p_intersect[0], p_intersect[1], p_intersect[2]]
|
||||
|
||||
|
||||
def nearest_point_on_sphere_to_line(center, radius, origin, direction):
|
||||
intersection = intersect_sphere_line(center, radius, origin, direction)
|
||||
if intersection[0]:
|
||||
d = np.min(intersection[1:])
|
||||
return origin + d * direction
|
||||
else:
|
||||
temp = np.dot(direction, center - origin)
|
||||
origin_prime = origin + temp * direction
|
||||
direction_prime = center - origin_prime
|
||||
direction_prime /= np.linalg.norm(direction_prime)
|
||||
success, d1, d2 = intersect_sphere_line(
|
||||
center, radius, origin_prime, direction_prime
|
||||
)
|
||||
if success:
|
||||
d = min(d1, d2)
|
||||
return origin_prime + d * direction_prime
|
||||
else:
|
||||
np.zeros(3)
|
||||
|
||||
|
||||
def nearest_intersection_points(p1, p2, p3, p4):
|
||||
"""Calculates the two nearest points, and their distance to each other on
|
||||
two lines defined by (p1,p2) respectively (p3,p4)
|
||||
"""
|
||||
|
||||
def mag(p):
|
||||
return np.sqrt(p.dot(p))
|
||||
|
||||
def normalise(p1, p2):
|
||||
p = p2 - p1
|
||||
m = mag(p)
|
||||
if m == 0:
|
||||
return [0.0, 0.0, 0.0]
|
||||
else:
|
||||
return p / m
|
||||
|
||||
d1 = normalise(p1, p2)
|
||||
d2 = normalise(p3, p4)
|
||||
|
||||
diff = p1 - p3
|
||||
a01 = -d1.dot(d2)
|
||||
b0 = diff.dot(d1)
|
||||
|
||||
if np.abs(a01) < 1.0:
|
||||
|
||||
# Lines are not parallel.
|
||||
det = 1.0 - a01 * a01
|
||||
b1 = -diff.dot(d2)
|
||||
s0 = (a01 * b1 - b0) / det
|
||||
s1 = (a01 * b0 - b1) / det
|
||||
|
||||
else:
|
||||
|
||||
# Lines are parallel, select any pair of closest points.
|
||||
s0 = -b0
|
||||
s1 = 0
|
||||
|
||||
closestPoint1 = p1 + s0 * d1
|
||||
closestPoint2 = p3 + s1 * d2
|
||||
dist = mag(closestPoint2 - closestPoint1)
|
||||
|
||||
return closestPoint1, closestPoint2, dist
|
||||
|
||||
|
||||
def nearest_intersection_lines(lines):
|
||||
dim = len(lines[0].origin)
|
||||
|
||||
R = np.zeros((dim, dim))
|
||||
q = np.zeros(dim)
|
||||
|
||||
for line in lines:
|
||||
v = np.reshape(line.direction, (dim, 1))
|
||||
A = np.eye(dim) - v @ v.T
|
||||
R += A
|
||||
q += A @ line.origin
|
||||
|
||||
return np.linalg.pinv(R) @ q
|
||||
@ -1,188 +0,0 @@
|
||||
"""
|
||||
(*)~---------------------------------------------------------------------------
|
||||
Pupil - eye tracking platform
|
||||
Copyright (C) 2012-2019 Pupil Labs
|
||||
|
||||
Distributed under the terms of the GNU
|
||||
Lesser General Public License (LGPL v3.0).
|
||||
See COPYING and COPYING.LESSER for license details.
|
||||
---------------------------------------------------------------------------~(*)
|
||||
"""
|
||||
import abc
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .utilities import cart2sph, normalize
|
||||
|
||||
|
||||
class Primitive(abc.ABC):
|
||||
__slots__ = ()
|
||||
|
||||
def __repr__(self):
|
||||
klass = "{}.{}".format(self.__class__.__module__, self.__class__.__name__)
|
||||
attributes = " ".join(
|
||||
"{}={}".format(k, v.__repr__()) for k, v in self.__dict__.items()
|
||||
)
|
||||
return "<{klass} at {id}: {attributes}>".format(
|
||||
klass=klass, id=id(self), attributes=attributes
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
def to_str(obj, float_fmt="{:f}") -> str:
|
||||
if isinstance(obj, float) or isinstance(obj, int):
|
||||
return float_fmt.format(obj)
|
||||
if isinstance(obj, np.ndarray):
|
||||
if obj.dtype != np.object:
|
||||
return ", ".join(float_fmt.format(x) for x in obj)
|
||||
return str(obj)
|
||||
|
||||
klass = self.__class__.__name__
|
||||
attributes = " - ".join(
|
||||
"{}: {}".format(k, to_str(v)) for k, v in self.__dict__.items()
|
||||
)
|
||||
return "{klass} -> {attributes}".format(klass=klass, attributes=attributes)
|
||||
|
||||
|
||||
class Line(Primitive):
|
||||
__slots__ = ("origin", "direction", "dim")
|
||||
|
||||
def __init__(self, origin, direction):
|
||||
self.origin = np.asarray(origin)
|
||||
self.direction = normalize(np.asarray(direction))
|
||||
self.dim = self.origin.shape[0]
|
||||
|
||||
|
||||
class Circle(Primitive):
|
||||
__slots__ = ("center", "normal", "radius")
|
||||
|
||||
def __init__(self, center=[0.0, 0.0, 0.0], normal=[0.0, 0.0, -1.0], radius=0.0):
|
||||
self.center = np.asarray(center, dtype=float)
|
||||
self.normal = np.asarray(normal, dtype=float)
|
||||
self.radius = radius
|
||||
|
||||
def spherical_representation(self):
|
||||
phi, theta = cart2sph(self.normal)
|
||||
return phi, theta, self.radius
|
||||
|
||||
def is_null(self):
|
||||
return self.radius <= 0.0
|
||||
|
||||
@staticmethod
|
||||
def null() -> "Circle":
|
||||
return Circle(radius=0.0)
|
||||
|
||||
|
||||
class Ellipse(Primitive):
|
||||
__slots__ = ("center", "major_radius", "minor_radius", "angle")
|
||||
|
||||
def __init__(self, center, minor_radius, major_radius, angle):
|
||||
self.center = center
|
||||
self.major_radius = major_radius
|
||||
self.minor_radius = minor_radius
|
||||
self.angle = angle
|
||||
|
||||
if self.minor_radius > self.major_radius:
|
||||
current_minor_radius = self.minor_radius
|
||||
self.minor_radius = self.major_radius
|
||||
self.major_radius = current_minor_radius
|
||||
self.angle = self.angle + np.pi / 2
|
||||
|
||||
def circumference(self):
|
||||
a = self.minor_radius
|
||||
b = self.major_radius
|
||||
return np.pi * (3.0 * (a + b) - np.sqrt((3.0 * a + b) * (a + 3.0 * b)))
|
||||
|
||||
def area(self):
|
||||
return np.pi * self.minor_radius * self.major_radius
|
||||
|
||||
def circularity(self):
|
||||
return self.minor_radius / self.major_radius
|
||||
|
||||
def parameters(self):
|
||||
return (
|
||||
self.center[0],
|
||||
self.center[1],
|
||||
self.minor_radius,
|
||||
self.major_radius,
|
||||
self.angle,
|
||||
)
|
||||
|
||||
|
||||
class Sphere(Primitive):
|
||||
__slots__ = ("center", "radius")
|
||||
|
||||
def __init__(self, center, radius):
|
||||
self.center = center
|
||||
self.radius = radius
|
||||
|
||||
def __bool__(self):
|
||||
return self.radius > 0
|
||||
|
||||
|
||||
class Conicoid(Primitive):
|
||||
"""
|
||||
Coefficients of the general equation (implicit form) of a cone, given its vertex and base (ellipse/conic).
|
||||
Formulae follow equations (1)-(3) of:
|
||||
Safaee-Rad, R. et al.: "Three-Dimensional Location Estimation of Circular Features for Machine Vision",
|
||||
IEEE Transactions on Robotics and Automation, Vol.8(5), 1992, pp624-640.
|
||||
"""
|
||||
|
||||
__slots__ = tuple("ABCFGHUVWD")
|
||||
|
||||
def __init__(self, conic, vertex):
|
||||
alpha = vertex[0]
|
||||
beta = vertex[1]
|
||||
gamma = vertex[2]
|
||||
self.A = (gamma ** 2) * conic.A
|
||||
self.B = (gamma ** 2) * conic.C
|
||||
self.C = (
|
||||
conic.A * (alpha ** 2)
|
||||
+ conic.B * alpha * beta
|
||||
+ conic.C * (beta ** 2)
|
||||
+ conic.D * alpha
|
||||
+ conic.E * beta
|
||||
+ conic.F
|
||||
)
|
||||
self.F = -gamma * (conic.C * beta + conic.B / 2 * alpha + conic.E / 2)
|
||||
self.G = -gamma * (conic.B / 2 * beta + conic.A * alpha + conic.D / 2)
|
||||
self.H = (gamma ** 2) * conic.B / 2
|
||||
self.U = (gamma ** 2) * conic.D / 2
|
||||
self.V = (gamma ** 2) * conic.E / 2
|
||||
self.W = -gamma * (conic.E / 2 * beta + conic.D / 2 * alpha + conic.F)
|
||||
self.D = (gamma ** 2) * conic.F
|
||||
|
||||
|
||||
class Conic(Primitive):
|
||||
"""
|
||||
Coefficients A-F of the general equation (implicit form) of a conic
|
||||
Ax² + Bxy + Cy² + Dx + Ey + F = 0
|
||||
calculated from 5 ellipse parameters, see https://en.wikipedia.org/wiki/Ellipse#General_ellipse
|
||||
"""
|
||||
|
||||
__slots__ = tuple("ABCDEF")
|
||||
|
||||
def __init__(self, *args):
|
||||
if len(args) == 1:
|
||||
ellipse = args[0]
|
||||
ax = np.cos(ellipse.angle)
|
||||
ay = np.sin(ellipse.angle)
|
||||
a2 = ellipse.major_radius ** 2
|
||||
b2 = ellipse.minor_radius ** 2
|
||||
|
||||
self.A = a2 * ay * ay + b2 * ax * ax
|
||||
self.B = 2.0 * (b2 - a2) * ax * ay
|
||||
self.C = a2 * ax * ax + b2 * ay * ay
|
||||
self.D = -2.0 * self.A * ellipse.center[0] - self.B * ellipse.center[1]
|
||||
self.E = -self.B * ellipse.center[0] - 2.0 * self.C * ellipse.center[1]
|
||||
self.F = (
|
||||
self.A * ellipse.center[0] * ellipse.center[0]
|
||||
+ self.B * ellipse.center[0] * ellipse.center[1]
|
||||
+ self.C * ellipse.center[1] * ellipse.center[1]
|
||||
- a2 * b2
|
||||
)
|
||||
|
||||
if len(args) == 6:
|
||||
self.A, self.B, self.C, self.D, self.E, self.F = args
|
||||
|
||||
def discriminant(self):
|
||||
return self.B ** 2 - 4 * self.A * self.C
|
||||
@ -1,123 +0,0 @@
|
||||
"""
|
||||
(*)~---------------------------------------------------------------------------
|
||||
Pupil - eye tracking platform
|
||||
Copyright (C) 2012-2019 Pupil Labs
|
||||
|
||||
Distributed under the terms of the GNU
|
||||
Lesser General Public License (LGPL v3.0).
|
||||
See COPYING and COPYING.LESSER for license details.
|
||||
---------------------------------------------------------------------------~(*)
|
||||
"""
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .intersections import intersect_sphere_multiple_lines
|
||||
from .primitives import Circle, Conic, Conicoid, Ellipse, Line
|
||||
from .utilities import normalize
|
||||
from ..cpp.projections import unproject_ellipse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def unproject_edges_to_sphere(
|
||||
edges, focal_length, sphere_center, sphere_radius, width=640, height=480
|
||||
):
|
||||
n_edges = edges.shape[0]
|
||||
|
||||
directions = edges - np.asarray([width / 2.0, height / 2.0])
|
||||
directions = np.hstack((directions, focal_length * np.ones((n_edges, 1))))
|
||||
directions = directions / np.linalg.norm(directions, axis=1, keepdims=1)
|
||||
|
||||
origins = np.zeros((n_edges, 3))
|
||||
|
||||
edges_on_sphere, idxs = intersect_sphere_multiple_lines(
|
||||
sphere_center, sphere_radius, origins, directions
|
||||
)
|
||||
|
||||
return edges_on_sphere, idxs
|
||||
|
||||
|
||||
def project_point_into_image_plane(point, focal_length):
|
||||
scale = focal_length / point[2]
|
||||
point_projected = scale * np.asarray(point)
|
||||
return point_projected[:2]
|
||||
|
||||
|
||||
def project_line_into_image_plane(line, focal_length):
|
||||
p1 = line.origin
|
||||
p2 = line.origin + line.direction
|
||||
|
||||
p1_projected = project_point_into_image_plane(p1, focal_length)
|
||||
p2_projected = project_point_into_image_plane(p2, focal_length)
|
||||
|
||||
return Line(p1_projected, p2_projected - p1_projected)
|
||||
|
||||
|
||||
def project_circle_into_image_plane(
|
||||
circle, focal_length, transform=True, width=0, height=0
|
||||
):
|
||||
c = circle.center
|
||||
n = circle.normal
|
||||
r = circle.radius
|
||||
f = focal_length
|
||||
|
||||
cn = np.dot(c, n)
|
||||
c2r2 = np.dot(c, c) - r ** 2
|
||||
ABC = cn ** 2 - 2.0 * cn * (c * n) + c2r2 * (n ** 2)
|
||||
F = 2.0 * (c2r2 * n[1] * n[2] - cn * (n[1] * c[2] + n[2] * c[1]))
|
||||
G = 2.0 * (c2r2 * n[2] * n[0] - cn * (n[2] * c[0] + n[0] * c[2]))
|
||||
H = 2.0 * (c2r2 * n[0] * n[1] - cn * (n[0] * c[1] + n[1] * c[0]))
|
||||
conic = Conic(ABC[0], H, ABC[1], G * f, F * f, ABC[2] * f ** 2)
|
||||
|
||||
disc_ = conic.discriminant()
|
||||
|
||||
if disc_ < 0:
|
||||
|
||||
A, B, C, D, E, F = conic.A, conic.B, conic.C, conic.D, conic.E, conic.F
|
||||
center_x = (2 * C * D - B * E) / disc_
|
||||
center_y = (2 * A * E - B * D) / disc_
|
||||
temp_ = 2 * (A * E ** 2 + C * D ** 2 - B * D * E + disc_ * F)
|
||||
minor_axis = (
|
||||
-np.sqrt(np.abs(temp_ * (A + C - np.sqrt((A - C) ** 2 + B ** 2)))) / disc_
|
||||
) # Todo: Absolute value???
|
||||
major_axis = (
|
||||
-np.sqrt(np.abs(temp_ * (A + C + np.sqrt((A - C) ** 2 + B ** 2)))) / disc_
|
||||
)
|
||||
|
||||
if B == 0 and A < C:
|
||||
angle = 0
|
||||
elif B == 0 and A >= C:
|
||||
angle = np.pi / 2.0
|
||||
else:
|
||||
angle = np.arctan((C - A - np.sqrt((A - C) ** 2 + B ** 2)) / B)
|
||||
|
||||
# TO BE CONSISTENT WITH PUPIL
|
||||
if transform:
|
||||
center_x = center_x + width / 2.0
|
||||
center_y = center_y + height / 2.0
|
||||
minor_axis, major_axis = 2.0 * minor_axis, 2.0 * major_axis
|
||||
angle = angle * 180.0 / np.pi + 90.0
|
||||
|
||||
return Ellipse(np.asarray([center_x, center_y]), minor_axis, major_axis, angle)
|
||||
|
||||
else:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def project_sphere_into_image_plane(
|
||||
sphere, focal_length, transform=True, width=0, height=0
|
||||
):
|
||||
scale = focal_length / sphere.center[2]
|
||||
|
||||
projected_sphere_center = scale * sphere.center
|
||||
projected_radius = scale * sphere.radius
|
||||
|
||||
if transform:
|
||||
projected_sphere_center[0] += width / 2.0
|
||||
projected_sphere_center[1] += height / 2
|
||||
projected_radius *= 2.0
|
||||
|
||||
return Ellipse(projected_sphere_center[:2], projected_radius, projected_radius, 0.0)
|
||||
@ -1,92 +0,0 @@
|
||||
"""
|
||||
(*)~---------------------------------------------------------------------------
|
||||
Pupil - eye tracking platform
|
||||
Copyright (C) 2012-2019 Pupil Labs
|
||||
|
||||
Distributed under the terms of the GNU
|
||||
Lesser General Public License (LGPL v3.0).
|
||||
See COPYING and COPYING.LESSER for license details.
|
||||
---------------------------------------------------------------------------~(*)
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
|
||||
def cart2sph(x):
|
||||
|
||||
phi = np.arctan2(x[2], x[0])
|
||||
theta = np.arccos(x[1] / np.linalg.norm(x))
|
||||
|
||||
return phi, theta
|
||||
|
||||
|
||||
def sph2cart(phi, theta):
|
||||
|
||||
result = np.empty(3)
|
||||
|
||||
result[0] = np.sin(theta) * np.cos(phi)
|
||||
result[1] = np.cos(theta)
|
||||
result[2] = np.sin(theta) * np.sin(phi)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def normalize(v, axis=-1):
|
||||
|
||||
return v / np.linalg.norm(v, axis=axis)
|
||||
|
||||
|
||||
def enclosed_angle(v1, v2, unit="deg", axis=-1):
|
||||
|
||||
v1 = normalize(v1, axis=axis)
|
||||
v2 = normalize(v2, axis=axis)
|
||||
|
||||
alpha = np.arccos(np.clip(np.dot(v1.T, v2), -1, 1))
|
||||
|
||||
if unit == "deg":
|
||||
return 180.0 / np.pi * alpha
|
||||
else:
|
||||
return alpha
|
||||
|
||||
|
||||
def make_homogeneous_vector(v):
|
||||
|
||||
return np.hstack((v, [0.0]))
|
||||
|
||||
|
||||
def make_homogeneous_point(p):
|
||||
return np.hstack((p, [1.0]))
|
||||
|
||||
|
||||
def transform_as_homogeneous_point(p, trafo):
|
||||
p = make_homogeneous_point(p)
|
||||
return (trafo @ p)[:3]
|
||||
|
||||
|
||||
def transform_as_homogeneous_vector(v, trafo):
|
||||
v = make_homogeneous_vector(v)
|
||||
return (trafo @ v)[:3]
|
||||
|
||||
|
||||
def rotate_v1_on_v2(v1, v2):
|
||||
|
||||
v1 = normalize(v1)
|
||||
v2 = normalize(v2)
|
||||
cos_angle = np.dot(v1, v2)
|
||||
|
||||
if not np.allclose(np.abs(cos_angle), 1):
|
||||
u = np.cross(v1, v2)
|
||||
s = np.linalg.norm(u)
|
||||
c = np.dot(v1, v2)
|
||||
|
||||
I = np.eye(3)
|
||||
ux = np.asarray([[0, -u[2], u[1]], [u[2], 0, -u[0]], [-u[1], u[0], 0]])
|
||||
|
||||
R = I + ux + np.dot(ux, ux) * (1 - c) / s ** 2
|
||||
|
||||
elif np.allclose(cos_angle, 1):
|
||||
R = np.eye(3)
|
||||
|
||||
elif np.allclose(cos_angle, -1):
|
||||
R = -np.eye(3)
|
||||
|
||||
return R
|
||||
@ -1,58 +0,0 @@
|
||||
"""
|
||||
(*)~---------------------------------------------------------------------------
|
||||
Pupil - eye tracking platform
|
||||
Copyright (C) 2012-2019 Pupil Labs
|
||||
|
||||
Distributed under the terms of the GNU
|
||||
Lesser General Public License (LGPL v3.0).
|
||||
See COPYING and COPYING.LESSER for license details.
|
||||
---------------------------------------------------------------------------~(*)
|
||||
"""
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
|
||||
class KalmanFilter(object):
|
||||
def __init__(self):
|
||||
self.filter = cv2.KalmanFilter(7, 3, 0, cv2.CV_32F)
|
||||
self.filter.measurementMatrix = np.asarray(
|
||||
[[1, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1]],
|
||||
dtype=np.float32,
|
||||
)
|
||||
self.filter.processNoiseCov = 1e-4 * np.eye(7, dtype=np.float32)
|
||||
self.filter.measurementNoiseCov = 1e-5 * np.eye(3, dtype=np.float32)
|
||||
self.filter.measurementNoiseCov[2][2] = 0.1
|
||||
self.filter.statePost = np.asarray([0, 0, 0, 0, 0, 0, 2.0], dtype=np.float32)
|
||||
self.filter.errorCovPost = np.eye(7, dtype=np.float32)
|
||||
self.last_call = -1
|
||||
|
||||
def predict(self, t):
|
||||
if self.last_call != -1 and t > self.last_call:
|
||||
dt = t - self.last_call
|
||||
self.filter.transitionMatrix = np.asarray(
|
||||
[
|
||||
[1, 0, dt, 0, 0.5 * dt * dt, 0, 0],
|
||||
[0, 1, 0, dt, 0, 0.5 * dt * dt, 0],
|
||||
[0, 0, 1, 0, dt, 0, 0],
|
||||
[0, 0, 0, 1, 0, dt, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1],
|
||||
],
|
||||
dtype=np.float32,
|
||||
)
|
||||
prediction = self.filter.predict()
|
||||
phi, theta, pupil_radius = (
|
||||
prediction[0][0],
|
||||
prediction[1][0],
|
||||
prediction[6][0],
|
||||
)
|
||||
else:
|
||||
phi, theta, pupil_radius = -np.pi / 2, np.pi / 2, 0
|
||||
|
||||
self.last_call = t
|
||||
|
||||
return phi, theta, pupil_radius
|
||||
|
||||
def correct(self, phi, theta, radius):
|
||||
self.filter.correct(np.asarray([phi, theta, radius], dtype=np.float32))
|
||||
@ -1,232 +0,0 @@
|
||||
"""
|
||||
(*)~---------------------------------------------------------------------------
|
||||
Pupil - eye tracking platform
|
||||
Copyright (C) 2012-2019 Pupil Labs
|
||||
|
||||
Distributed under the terms of the GNU
|
||||
Lesser General Public License (LGPL v3.0).
|
||||
See COPYING and COPYING.LESSER for license details.
|
||||
---------------------------------------------------------------------------~(*)
|
||||
"""
|
||||
from abc import abstractmethod, abstractproperty
|
||||
from collections import deque
|
||||
from math import floor
|
||||
from typing import Sequence, Optional
|
||||
|
||||
import numpy as np
|
||||
from sortedcontainers import SortedList
|
||||
|
||||
from .camera import CameraModel
|
||||
from .constants import _EYE_RADIUS_DEFAULT
|
||||
from .geometry.primitives import Ellipse, Line
|
||||
from .geometry.projections import project_line_into_image_plane, unproject_ellipse
|
||||
|
||||
|
||||
class Observation(object):
|
||||
def __init__(
|
||||
self, ellipse: Ellipse, confidence: float, timestamp: float, focal_length: float
|
||||
):
|
||||
self.ellipse = ellipse
|
||||
self.confidence_2d = confidence
|
||||
self.confidence = 0.0
|
||||
self.timestamp = timestamp
|
||||
|
||||
self.circle_3d_pair = None
|
||||
self.gaze_3d_pair = None
|
||||
self.gaze_2d = None
|
||||
self.aux_2d = None
|
||||
self.aux_3d = None
|
||||
self.invalid = True
|
||||
|
||||
circle_3d_pair = unproject_ellipse(ellipse, focal_length)
|
||||
if not circle_3d_pair:
|
||||
# unprojecting ellipse failed, invalid observation!
|
||||
return
|
||||
|
||||
self.invalid = False
|
||||
self.confidence = self.confidence_2d
|
||||
self.circle_3d_pair = circle_3d_pair
|
||||
|
||||
self.gaze_3d_pair = [
|
||||
Line(
|
||||
circle_3d_pair[i].center,
|
||||
circle_3d_pair[i].center + circle_3d_pair[i].normal,
|
||||
)
|
||||
for i in [0, 1]
|
||||
]
|
||||
self.gaze_2d = project_line_into_image_plane(self.gaze_3d_pair[0], focal_length)
|
||||
self.gaze_2d_line = np.array([*self.gaze_2d.origin, *self.gaze_2d.direction])
|
||||
|
||||
self.aux_2d = np.empty((2, 3))
|
||||
v = np.reshape(self.gaze_2d.direction, (2, 1))
|
||||
self.aux_2d[:, :2] = np.eye(2) - v @ v.T
|
||||
self.aux_2d[:, 2] = (np.eye(2) - v @ v.T) @ self.gaze_2d.origin
|
||||
|
||||
self.aux_3d = np.empty((2, 3, 4))
|
||||
for i in range(2):
|
||||
Dierkes_line = self.get_Dierkes_line(i)
|
||||
v = np.reshape(Dierkes_line.direction, (3, 1))
|
||||
self.aux_3d[i, :3, :3] = np.eye(3) - v @ v.T
|
||||
self.aux_3d[i, :3, 3] = (np.eye(3) - v @ v.T) @ Dierkes_line.origin
|
||||
|
||||
def get_Dierkes_line(self, i):
|
||||
origin = (
|
||||
self.circle_3d_pair[i].center
|
||||
- _EYE_RADIUS_DEFAULT * self.circle_3d_pair[i].normal
|
||||
)
|
||||
direction = self.circle_3d_pair[i].center
|
||||
return Line(origin, direction)
|
||||
|
||||
|
||||
class ObservationStorage:
|
||||
@abstractmethod
|
||||
def add(self, observation: Observation):
|
||||
pass
|
||||
|
||||
@abstractproperty
|
||||
def observations(self) -> Sequence[Observation]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def clear(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def count(self) -> int:
|
||||
pass
|
||||
|
||||
|
||||
class BasicStorage(ObservationStorage):
|
||||
def __init__(self):
|
||||
self._storage = []
|
||||
|
||||
def add(self, observation: Observation):
|
||||
if observation.invalid:
|
||||
return
|
||||
self._storage.append(observation)
|
||||
|
||||
@property
|
||||
def observations(self) -> Sequence[Observation]:
|
||||
return self._storage
|
||||
|
||||
def clear(self):
|
||||
self._storage.clear()
|
||||
|
||||
def count(self) -> int:
|
||||
return len(self._storage)
|
||||
|
||||
|
||||
class BufferedObservationStorage(ObservationStorage):
|
||||
def __init__(self, confidence_threshold: float, buffer_length: int):
|
||||
self.confidence_threshold = confidence_threshold
|
||||
self._storage = deque(maxlen=buffer_length)
|
||||
|
||||
def add(self, observation: Observation):
|
||||
if observation.invalid:
|
||||
return
|
||||
if observation.confidence < self.confidence_threshold:
|
||||
return
|
||||
|
||||
self._storage.append(observation)
|
||||
|
||||
@property
|
||||
def observations(self) -> Sequence[Observation]:
|
||||
return list(self._storage)
|
||||
|
||||
def clear(self):
|
||||
self._storage.clear()
|
||||
|
||||
def count(self) -> int:
|
||||
return len(self._storage)
|
||||
|
||||
|
||||
class BinBufferedObservationStorage(ObservationStorage):
|
||||
def __init__(
|
||||
self,
|
||||
camera: CameraModel,
|
||||
confidence_threshold: float,
|
||||
n_bins_horizontal: int,
|
||||
bin_buffer_length: int,
|
||||
forget_min_observations: Optional[int] = None,
|
||||
forget_min_time: Optional[float] = None,
|
||||
):
|
||||
self.camera = camera
|
||||
self.confidence_threshold = confidence_threshold
|
||||
self.bin_buffer_length = bin_buffer_length
|
||||
self.forget_min_observations = forget_min_observations
|
||||
self.forget_min_time = forget_min_time
|
||||
self.pixels_per_bin = self.camera.resolution[0] / n_bins_horizontal
|
||||
self.w = n_bins_horizontal
|
||||
self.h = int(round(self.camera.resolution[1] / self.pixels_per_bin))
|
||||
|
||||
self._by_time = SortedList(key=lambda obs: obs.timestamp)
|
||||
self._by_bin = dict()
|
||||
|
||||
def add(self, observation: Observation):
|
||||
if observation.invalid:
|
||||
return
|
||||
if observation.confidence < self.confidence_threshold:
|
||||
return
|
||||
|
||||
idx = self._get_bin(observation)
|
||||
if idx < 0 or idx >= self.w * self.h:
|
||||
print(f"INDEX OUT OF BOUNDS: {idx}")
|
||||
return
|
||||
|
||||
if idx not in self._by_bin:
|
||||
self._by_bin[idx] = SortedList(key=lambda obs: obs.timestamp)
|
||||
|
||||
# add to both lookup structures
|
||||
_bin: SortedList = self._by_bin[idx]
|
||||
_bin.add(observation)
|
||||
self._by_time.add(observation)
|
||||
|
||||
# manage within-bin forgetting
|
||||
while len(_bin) > self.bin_buffer_length:
|
||||
old = _bin.pop(0)
|
||||
self._by_time.remove(old)
|
||||
|
||||
# manage across-bin forgetting
|
||||
if self.forget_min_observations is None or self.forget_min_time is None:
|
||||
return
|
||||
|
||||
while self.count() > self.forget_min_observations:
|
||||
oldest_age = observation.timestamp - self._by_time[0].timestamp
|
||||
if oldest_age < self.forget_min_time:
|
||||
break
|
||||
|
||||
# forget oldest entry
|
||||
old = self._by_time.pop(0)
|
||||
idx = self._get_bin(old)
|
||||
_bin = self._by_bin[idx]
|
||||
_bin.remove(old)
|
||||
# make sure to remove bin if empty for bin-counting to work
|
||||
if len(_bin) == 0:
|
||||
self._by_bin.pop(idx)
|
||||
|
||||
@property
|
||||
def observations(self) -> Sequence[Observation]:
|
||||
return list(self._by_time)
|
||||
|
||||
def clear(self):
|
||||
self._by_time.clear()
|
||||
self._by_bin.clear()
|
||||
|
||||
def count(self) -> int:
|
||||
return len(self._by_time)
|
||||
|
||||
def get_bin_counts(self) -> np.ndarray:
|
||||
dense_1d = np.zeros((self.w * self.h,))
|
||||
for idx, _bin in self._by_bin.items():
|
||||
dense_1d[idx] = len(_bin)
|
||||
return np.reshape(dense_1d, (self.w, self.h))
|
||||
|
||||
def _get_bin(self, observation: Observation) -> int:
|
||||
x, y = (
|
||||
floor((ellipse_center + resolution / 2) / self.pixels_per_bin)
|
||||
for ellipse_center, resolution in zip(
|
||||
observation.ellipse.center, self.camera.resolution
|
||||
)
|
||||
)
|
||||
# convert to 1D bin index
|
||||
return x + y * self.h
|
||||
@ -1,141 +0,0 @@
|
||||
import itertools
|
||||
from pathlib import Path
|
||||
from .cpp.refraction_correction import apply_correction_pipeline
|
||||
|
||||
import numpy as np
|
||||
import msgpack
|
||||
|
||||
LOAD_DIR = Path(__file__).parent / "refraction_models"
|
||||
LOAD_VERSION = 1
|
||||
|
||||
|
||||
class ModelDeserializationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Refractionizer:
|
||||
def __init__(self, degree=3, type_="default", custom_load_dir=None):
|
||||
self.pipeline_radius_as_list = self.load_config_from_msgpack(
|
||||
"radius", type_, degree, custom_load_dir
|
||||
)
|
||||
|
||||
self.pipeline_gaze_vector_as_list = self.load_config_from_msgpack(
|
||||
"gaze_vector", type_, degree, custom_load_dir
|
||||
)
|
||||
|
||||
self.pipeline_sphere_center_as_list = self.load_config_from_msgpack(
|
||||
"sphere_center", type_, degree, custom_load_dir
|
||||
)
|
||||
|
||||
self.pipeline_pupil_circle_as_list = self.load_config_from_msgpack(
|
||||
"pupil_circle", type_, degree, custom_load_dir
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def load_config_from_msgpack(feature, type_, degree, custom_load_dir=None):
|
||||
load_dir = Path(custom_load_dir or LOAD_DIR).resolve()
|
||||
name = f"{type_}_refraction_model_{feature}_degree_{degree}.msgpack"
|
||||
path = load_dir / name
|
||||
with path.open("rb") as file:
|
||||
config_model = msgpack.unpack(file)
|
||||
Refractionizer._validate_loaded_model_config(config_model)
|
||||
try:
|
||||
return list(
|
||||
itertools.chain(
|
||||
Refractionizer._polynomial_features_from_config(config_model),
|
||||
Refractionizer._standard_scaler_from_config(config_model),
|
||||
Refractionizer._linear_regression_from_config(config_model),
|
||||
)
|
||||
)
|
||||
except KeyError as err:
|
||||
raise ModelDeserializationError from err
|
||||
|
||||
@staticmethod
|
||||
def _validate_loaded_model_config(config_model):
|
||||
if not isinstance(config_model, dict) or "version" not in config_model:
|
||||
raise ModelDeserializationError("Unrecognized format")
|
||||
if config_model["version"] != LOAD_VERSION:
|
||||
raise ModelDeserializationError(
|
||||
f"Unexpected version `{config_model['version']}` "
|
||||
f"(expected `{LOAD_VERSION}``)"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _polynomial_features_from_config(config_model):
|
||||
yield np.array(config_model["steps"]["PolynomialFeatures"]["powers"])
|
||||
|
||||
@staticmethod
|
||||
def _standard_scaler_from_config(config_model):
|
||||
config_scaler = config_model["steps"]["StandardScaler"]
|
||||
yield np.array(config_scaler["mean"])
|
||||
yield np.array(config_scaler["var"])
|
||||
|
||||
@staticmethod
|
||||
def _linear_regression_from_config(config_model):
|
||||
config_lin_reg = config_model["steps"]["LinearRegression"]
|
||||
yield np.array(config_lin_reg["coef"])
|
||||
yield np.array(config_lin_reg["intercept"])
|
||||
|
||||
@staticmethod
|
||||
def _apply_correction_pipeline(X, pipeline_arrays):
|
||||
return apply_correction_pipeline(np.asarray(X).T, *pipeline_arrays)
|
||||
|
||||
def correct_radius(self, X):
|
||||
return self._apply_correction_pipeline(X, self.pipeline_radius_as_list)
|
||||
|
||||
def correct_gaze_vector(self, X):
|
||||
return self._apply_correction_pipeline(X, self.pipeline_gaze_vector_as_list)
|
||||
|
||||
def correct_sphere_center(self, X):
|
||||
return self._apply_correction_pipeline(X, self.pipeline_sphere_center_as_list)
|
||||
|
||||
def correct_pupil_circle(self, X):
|
||||
return self._apply_correction_pipeline(X, self.pipeline_pupil_circle_as_list)
|
||||
|
||||
|
||||
class SklearnRefractionizer(Refractionizer):
|
||||
def __init__(self, degree=3, type_="default", custom_load_dir=None):
|
||||
self.correct_radius = self.load_predict_fn_from_joblib_pickle(
|
||||
"radius", type_, degree, custom_load_dir
|
||||
)
|
||||
|
||||
self.correct_gaze_vector = self.load_predict_fn_from_joblib_pickle(
|
||||
"gaze_vector", type_, degree, custom_load_dir
|
||||
)
|
||||
|
||||
self.correct_sphere_center = self.load_predict_fn_from_joblib_pickle(
|
||||
"sphere_center", type_, degree, custom_load_dir
|
||||
)
|
||||
|
||||
self.correct_pupil_circle = self.load_predict_fn_from_joblib_pickle(
|
||||
"pupil_circle", type_, degree, custom_load_dir
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def load_predict_fn_from_joblib_pickle(
|
||||
feature, type_, degree, custom_load_dir=None
|
||||
):
|
||||
import joblib
|
||||
|
||||
load_dir = Path(custom_load_dir or LOAD_DIR).resolve()
|
||||
name = f"{type_}_refraction_model_{feature}_degree_{degree}.save"
|
||||
path = load_dir / name
|
||||
try:
|
||||
pipeline = joblib.load(path)
|
||||
except FileNotFoundError as err:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise ModelDeserializationError(
|
||||
f"Failed to load pickled model from {path}"
|
||||
) from exc
|
||||
return pipeline.predict
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
refractionizer = Refractionizer()
|
||||
|
||||
print(refractionizer.correct_sphere_center([[0.0, 0.0, 35.0]]))
|
||||
print(refractionizer.correct_radius([[0.0, 0.0, 35.0, 0.0, 0.0, -1.0, 2.0]]))
|
||||
print(refractionizer.correct_gaze_vector([[0.0, 0.0, 35.0, 0.0, 0.0, -1.0, 2.0]]))
|
||||
print(refractionizer.correct_pupil_circle([[0.0, 0.0, 35.0, 0.0, 0.0, -1.0, 2.0]]))
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,33 +0,0 @@
|
||||
altgraph==0.17.2
|
||||
certifi==2022.6.15
|
||||
charset-normalizer==2.0.12
|
||||
comtypes==1.1.11
|
||||
dacite==1.6.0
|
||||
dataclasses==0.8
|
||||
docopt==0.6.2
|
||||
future==0.18.2
|
||||
idna==3.3
|
||||
importlib-metadata==4.8.3
|
||||
joblib==1.1.0
|
||||
msgpack==1.0.4
|
||||
numpy==1.19.5
|
||||
opencv-python==4.5.3.56
|
||||
pefile==2022.5.30
|
||||
pipreqs==0.4.11
|
||||
pyinstaller==4.10
|
||||
pyinstaller-hooks-contrib==2022.0
|
||||
pypiwin32==223
|
||||
PySimpleGUI==4.60.1
|
||||
python-osc==1.8.0
|
||||
pyttsx3==2.90
|
||||
pywin32==304
|
||||
pywin32-ctypes==0.2.0
|
||||
requests==2.27.1
|
||||
sortedcontainers==2.4.0
|
||||
typing_extensions==4.1.1
|
||||
urllib3==1.26.9
|
||||
yarg==0.1.9
|
||||
zipp==3.6.0
|
||||
python-osc==1.8.0
|
||||
sympy==1.2
|
||||
scipy==1.5.4
|
||||
575
poetry.lock
generated
575
poetry.lock
generated
@ -8,21 +8,17 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "22.8.0"
|
||||
version = "22.10.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""}
|
||||
mypy-extensions = ">=0.4.3"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
|
||||
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
|
||||
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
@ -40,56 +36,33 @@ python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "2.0.12"
|
||||
version = "2.1.1"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5.0"
|
||||
python-versions = ">=3.6.0"
|
||||
|
||||
[package.extras]
|
||||
unicode_backport = ["unicodedata2"]
|
||||
unicode-backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.0.4"
|
||||
version = "8.1.3"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "dacite"
|
||||
version = "1.6.0"
|
||||
description = "Simple creation of data classes from dictionaries."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
dataclasses = {version = "*", markers = "python_version < \"3.7\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["black", "coveralls", "mypy", "pylint", "pytest (>=5)", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "dataclasses"
|
||||
version = "0.8"
|
||||
description = "A backport of the dataclasses module for Python 3.6"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6, <3.7"
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "flake8"
|
||||
@ -100,7 +73,6 @@ optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
|
||||
[package.dependencies]
|
||||
importlib-metadata = {version = ">=1.1.0,<4.3", markers = "python_version < \"3.8\""}
|
||||
mccabe = ">=0.7.0,<0.8.0"
|
||||
pycodestyle = ">=2.9.0,<2.10.0"
|
||||
pyflakes = ">=2.5.0,<2.6.0"
|
||||
@ -121,22 +93,6 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "4.2.0"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"]
|
||||
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "macholib"
|
||||
version = "1.16.2"
|
||||
@ -186,34 +142,35 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "1.19.5"
|
||||
version = "1.23.4"
|
||||
description = "NumPy is the fundamental package for array computing with Python."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.8"
|
||||
|
||||
[[package]]
|
||||
name = "opencv-python"
|
||||
version = "4.5.3.56"
|
||||
version = "4.6.0.66"
|
||||
description = "Wrapper package for OpenCV python bindings."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.13.3"
|
||||
|
||||
[package.source]
|
||||
type = "url"
|
||||
url = "https://files.pythonhosted.org/packages/82/5e/f5df9ce92b7d25f43baf64327ea89f832f1eac7f250c1569a22f8b3fca3e/opencv_python-4.5.3.56-cp36-cp36m-win_amd64.whl"
|
||||
numpy = [
|
||||
{version = ">=1.21.2", markers = "python_version >= \"3.10\" or python_version >= \"3.6\" and platform_system == \"Darwin\" and platform_machine == \"arm64\""},
|
||||
{version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""},
|
||||
{version = ">=1.14.5", markers = "python_version >= \"3.7\""},
|
||||
{version = ">=1.17.3", markers = "python_version >= \"3.8\""},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.9.0"
|
||||
version = "0.10.1"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "pefile"
|
||||
@ -228,14 +185,14 @@ future = "*"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.4.0"
|
||||
version = "2.5.2"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
|
||||
|
||||
[[package]]
|
||||
@ -246,6 +203,41 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.10.2"
|
||||
description = "Data validation and settings management using python type hints"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = ">=4.1.0"
|
||||
|
||||
[package.extras]
|
||||
dotenv = ["python-dotenv (>=0.10.4)"]
|
||||
email = ["email-validator (>=1.0.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "pye3d"
|
||||
version = "0.3.1.post1"
|
||||
description = "3D eye model"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
msgpack = ">=1"
|
||||
numpy = "*"
|
||||
sortedcontainers = "*"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx (<4.4)"]
|
||||
examples = ["opencv-python", "pupil-detectors"]
|
||||
legacy-sklearn-models = ["joblib", "scikit-learn"]
|
||||
testing = ["matplotlib", "opencv-python-headless", "pandas", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "scikit-image"]
|
||||
with-opencv = ["opencv-python"]
|
||||
|
||||
[[package]]
|
||||
name = "pyflakes"
|
||||
version = "2.5.0"
|
||||
@ -256,31 +248,31 @@ python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "pyinstaller"
|
||||
version = "4.10"
|
||||
version = "5.6.2"
|
||||
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "<3.11,>=3.6"
|
||||
python-versions = "<3.12,>=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
altgraph = "*"
|
||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""}
|
||||
pefile = {version = ">=2017.8.1", markers = "sys_platform == \"win32\""}
|
||||
pyinstaller-hooks-contrib = ">=2020.6"
|
||||
pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""}
|
||||
pyinstaller-hooks-contrib = ">=2021.4"
|
||||
pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""}
|
||||
setuptools = "*"
|
||||
|
||||
[package.extras]
|
||||
encryption = ["tinyaes (>=1.0.0)"]
|
||||
hook_testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"]
|
||||
hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyinstaller-hooks-contrib"
|
||||
version = "2022.0"
|
||||
version = "2022.11"
|
||||
description = "Community maintained hooks for PyInstaller"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "pysimplegui"
|
||||
@ -298,14 +290,6 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pywin32"
|
||||
version = "304"
|
||||
description = "Python for Window Extensions"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pywin32-ctypes"
|
||||
version = "0.2.0"
|
||||
@ -316,32 +300,50 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.27.1"
|
||||
version = "2.28.1"
|
||||
description = "Python HTTP for Humans."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
python-versions = ">=3.7, <4"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2017.4.17"
|
||||
charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
|
||||
idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
|
||||
charset-normalizer = ">=2,<3"
|
||||
idna = ">=2.5,<4"
|
||||
urllib3 = ">=1.21.1,<1.27"
|
||||
|
||||
[package.extras]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
|
||||
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "scipy"
|
||||
version = "1.5.4"
|
||||
description = "SciPy: Scientific Library for Python"
|
||||
version = "1.9.3"
|
||||
description = "Fundamental algorithms for scientific computing in Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.8"
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.14.5"
|
||||
numpy = ">=1.18.5,<1.26.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["flake8", "mypy", "pycodestyle", "typing_extensions"]
|
||||
doc = ["matplotlib (>2)", "numpydoc", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-panels (>=0.5.2)", "sphinx-tabs"]
|
||||
test = ["asv", "gmpy2", "mpmath", "pytest", "pytest-cov", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "65.5.0"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||
|
||||
[[package]]
|
||||
name = "sortedcontainers"
|
||||
@ -353,38 +355,22 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "sympy"
|
||||
version = "1.9"
|
||||
version = "1.11.1"
|
||||
description = "Computer algebra system (CAS) in Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.8"
|
||||
|
||||
[package.dependencies]
|
||||
mpmath = ">=0.19"
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "1.2.3"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "typed-ast"
|
||||
version = "1.5.4"
|
||||
description = "a fork of Python 2 and 3 ast modules with type comment support"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.1.1"
|
||||
description = "Backported and Experimental Type Hints for Python 3.6+"
|
||||
category = "dev"
|
||||
version = "4.4.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
@ -399,22 +385,10 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
|
||||
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.6.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"]
|
||||
testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "~3.6.2"
|
||||
content-hash = "e09c41c346cca409c97844891b2c3c2294a6a36949efcdab558b7222ffe46619"
|
||||
python-versions = "~3.11.0"
|
||||
content-hash = "b59a104524f78b3dfc170751db515428495204063e9ebf448cd839eab4a3d8af"
|
||||
|
||||
[metadata.files]
|
||||
altgraph = [
|
||||
@ -422,53 +396,43 @@ altgraph = [
|
||||
{file = "altgraph-0.17.3.tar.gz", hash = "sha256:ad33358114df7c9416cdb8fa1eaa5852166c505118717021c6a8c7c7abbd03dd"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"},
|
||||
{file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"},
|
||||
{file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"},
|
||||
{file = "black-22.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd82842bb272297503cbec1a2600b6bfb338dae017186f8f215c8958f8acf869"},
|
||||
{file = "black-22.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d839150f61d09e7217f52917259831fe2b689f5c8e5e32611736351b89bb2a90"},
|
||||
{file = "black-22.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a05da0430bd5ced89176db098567973be52ce175a55677436a271102d7eaa3fe"},
|
||||
{file = "black-22.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a098a69a02596e1f2a58a2a1c8d5a05d5a74461af552b371e82f9fa4ada8342"},
|
||||
{file = "black-22.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5594efbdc35426e35a7defa1ea1a1cb97c7dbd34c0e49af7fb593a36bd45edab"},
|
||||
{file = "black-22.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983526af1bea1e4cf6768e649990f28ee4f4137266921c2c3cee8116ae42ec3"},
|
||||
{file = "black-22.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2c25f8dea5e8444bdc6788a2f543e1fb01494e144480bc17f806178378005e"},
|
||||
{file = "black-22.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:78dd85caaab7c3153054756b9fe8c611efa63d9e7aecfa33e533060cb14b6d16"},
|
||||
{file = "black-22.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cea1b2542d4e2c02c332e83150e41e3ca80dc0fb8de20df3c5e98e242156222c"},
|
||||
{file = "black-22.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b879eb439094751185d1cfdca43023bc6786bd3c60372462b6f051efa6281a5"},
|
||||
{file = "black-22.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a12e4e1353819af41df998b02c6742643cfef58282915f781d0e4dd7a200411"},
|
||||
{file = "black-22.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a73f66b6d5ba7288cd5d6dad9b4c9b43f4e8a4b789a94bf5abfb878c663eb3"},
|
||||
{file = "black-22.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:e981e20ec152dfb3e77418fb616077937378b322d7b26aa1ff87717fb18b4875"},
|
||||
{file = "black-22.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ce13ffed7e66dda0da3e0b2eb1bdfc83f5812f66e09aca2b0978593ed636b6c"},
|
||||
{file = "black-22.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32a4b17f644fc288c6ee2bafdf5e3b045f4eff84693ac069d87b1a347d861497"},
|
||||
{file = "black-22.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad827325a3a634bae88ae7747db1a395d5ee02cf05d9aa7a9bd77dfb10e940c"},
|
||||
{file = "black-22.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53198e28a1fb865e9fe97f88220da2e44df6da82b18833b588b1883b16bb5d41"},
|
||||
{file = "black-22.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bc4d4123830a2d190e9cc42a2e43570f82ace35c3aeb26a512a2102bce5af7ec"},
|
||||
{file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"},
|
||||
{file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"},
|
||||
{file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"},
|
||||
{file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"},
|
||||
{file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"},
|
||||
{file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"},
|
||||
{file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"},
|
||||
{file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"},
|
||||
{file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"},
|
||||
{file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"},
|
||||
{file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"},
|
||||
{file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"},
|
||||
{file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"},
|
||||
{file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"},
|
||||
{file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"},
|
||||
{file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"},
|
||||
{file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"},
|
||||
{file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"},
|
||||
{file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"},
|
||||
{file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"},
|
||||
{file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"},
|
||||
{file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"},
|
||||
{file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
|
||||
{file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
|
||||
]
|
||||
charset-normalizer = [
|
||||
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
|
||||
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
|
||||
{file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
|
||||
{file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
|
||||
{file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
|
||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
||||
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
||||
]
|
||||
dacite = [
|
||||
{file = "dacite-1.6.0-py3-none-any.whl", hash = "sha256:4331535f7aabb505c732fa4c3c094313fc0a1d5ea19907bf4726a7819a68b93f"},
|
||||
{file = "dacite-1.6.0.tar.gz", hash = "sha256:d48125ed0a0352d3de9f493bf980038088f45f3f9d7498f090b50a847daaa6df"},
|
||||
]
|
||||
dataclasses = [
|
||||
{file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"},
|
||||
{file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
flake8 = [
|
||||
{file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"},
|
||||
@ -481,10 +445,6 @@ idna = [
|
||||
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
|
||||
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
|
||||
]
|
||||
importlib-metadata = [
|
||||
{file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"},
|
||||
{file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"},
|
||||
]
|
||||
macholib = [
|
||||
{file = "macholib-1.16.2-py2.py3-none-any.whl", hash = "sha256:44c40f2cd7d6726af8fa6fe22549178d3a4dfecc35a9cd15ea916d9c83a688e0"},
|
||||
{file = "macholib-1.16.2.tar.gz", hash = "sha256:557bbfa1bb255c20e9abafe7ed6cd8046b48d9525db2f9b77d3122a63a2a8bf8"},
|
||||
@ -556,77 +516,138 @@ mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||
]
|
||||
numpy = [
|
||||
{file = "numpy-1.19.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc6bd4fd593cb261332568485e20a0712883cf631f6f5e8e86a52caa8b2b50ff"},
|
||||
{file = "numpy-1.19.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aeb9ed923be74e659984e321f609b9ba54a48354bfd168d21a2b072ed1e833ea"},
|
||||
{file = "numpy-1.19.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8b5e972b43c8fc27d56550b4120fe6257fdc15f9301914380b27f74856299fea"},
|
||||
{file = "numpy-1.19.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:43d4c81d5ffdff6bae58d66a3cd7f54a7acd9a0e7b18d97abb255defc09e3140"},
|
||||
{file = "numpy-1.19.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a4646724fba402aa7504cd48b4b50e783296b5e10a524c7a6da62e4a8ac9698d"},
|
||||
{file = "numpy-1.19.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:2e55195bc1c6b705bfd8ad6f288b38b11b1af32f3c8289d6c50d47f950c12e76"},
|
||||
{file = "numpy-1.19.5-cp36-cp36m-win32.whl", hash = "sha256:39b70c19ec771805081578cc936bbe95336798b7edf4732ed102e7a43ec5c07a"},
|
||||
{file = "numpy-1.19.5-cp36-cp36m-win_amd64.whl", hash = "sha256:dbd18bcf4889b720ba13a27ec2f2aac1981bd41203b3a3b27ba7a33f88ae4827"},
|
||||
{file = "numpy-1.19.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:603aa0706be710eea8884af807b1b3bc9fb2e49b9f4da439e76000f3b3c6ff0f"},
|
||||
{file = "numpy-1.19.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cae865b1cae1ec2663d8ea56ef6ff185bad091a5e33ebbadd98de2cfa3fa668f"},
|
||||
{file = "numpy-1.19.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:36674959eed6957e61f11c912f71e78857a8d0604171dfd9ce9ad5cbf41c511c"},
|
||||
{file = "numpy-1.19.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:06fab248a088e439402141ea04f0fffb203723148f6ee791e9c75b3e9e82f080"},
|
||||
{file = "numpy-1.19.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6149a185cece5ee78d1d196938b2a8f9d09f5a5ebfbba66969302a778d5ddd1d"},
|
||||
{file = "numpy-1.19.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:50a4a0ad0111cc1b71fa32dedd05fa239f7fb5a43a40663269bb5dc7877cfd28"},
|
||||
{file = "numpy-1.19.5-cp37-cp37m-win32.whl", hash = "sha256:d051ec1c64b85ecc69531e1137bb9751c6830772ee5c1c426dbcfe98ef5788d7"},
|
||||
{file = "numpy-1.19.5-cp37-cp37m-win_amd64.whl", hash = "sha256:a12ff4c8ddfee61f90a1633a4c4afd3f7bcb32b11c52026c92a12e1325922d0d"},
|
||||
{file = "numpy-1.19.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf2402002d3d9f91c8b01e66fbb436a4ed01c6498fffed0e4c7566da1d40ee1e"},
|
||||
{file = "numpy-1.19.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1ded4fce9cfaaf24e7a0ab51b7a87be9038ea1ace7f34b841fe3b6894c721d1c"},
|
||||
{file = "numpy-1.19.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:012426a41bc9ab63bb158635aecccc7610e3eff5d31d1eb43bc099debc979d94"},
|
||||
{file = "numpy-1.19.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:759e4095edc3c1b3ac031f34d9459fa781777a93ccc633a472a5468587a190ff"},
|
||||
{file = "numpy-1.19.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a9d17f2be3b427fbb2bce61e596cf555d6f8a56c222bd2ca148baeeb5e5c783c"},
|
||||
{file = "numpy-1.19.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:99abf4f353c3d1a0c7a5f27699482c987cf663b1eac20db59b8c7b061eabd7fc"},
|
||||
{file = "numpy-1.19.5-cp38-cp38-win32.whl", hash = "sha256:384ec0463d1c2671170901994aeb6dce126de0a95ccc3976c43b0038a37329c2"},
|
||||
{file = "numpy-1.19.5-cp38-cp38-win_amd64.whl", hash = "sha256:811daee36a58dc79cf3d8bdd4a490e4277d0e4b7d103a001a4e73ddb48e7e6aa"},
|
||||
{file = "numpy-1.19.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c843b3f50d1ab7361ca4f0b3639bf691569493a56808a0b0c54a051d260b7dbd"},
|
||||
{file = "numpy-1.19.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d6631f2e867676b13026e2846180e2c13c1e11289d67da08d71cacb2cd93d4aa"},
|
||||
{file = "numpy-1.19.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7fb43004bce0ca31d8f13a6eb5e943fa73371381e53f7074ed21a4cb786c32f8"},
|
||||
{file = "numpy-1.19.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2ea52bd92ab9f768cc64a4c3ef8f4b2580a17af0a5436f6126b08efbd1838371"},
|
||||
{file = "numpy-1.19.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:400580cbd3cff6ffa6293df2278c75aef2d58d8d93d3c5614cd67981dae68ceb"},
|
||||
{file = "numpy-1.19.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:df609c82f18c5b9f6cb97271f03315ff0dbe481a2a02e56aeb1b1a985ce38e60"},
|
||||
{file = "numpy-1.19.5-cp39-cp39-win32.whl", hash = "sha256:ab83f24d5c52d60dbc8cd0528759532736b56db58adaa7b5f1f76ad551416a1e"},
|
||||
{file = "numpy-1.19.5-cp39-cp39-win_amd64.whl", hash = "sha256:0eef32ca3132a48e43f6a0f5a82cb508f22ce5a3d6f67a8329c81c8e226d3f6e"},
|
||||
{file = "numpy-1.19.5-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a0d53e51a6cb6f0d9082decb7a4cb6dfb33055308c4c44f53103c073f649af73"},
|
||||
{file = "numpy-1.19.5.zip", hash = "sha256:a76f502430dd98d7546e1ea2250a7360c065a5fdea52b2dffe8ae7180909b6f4"},
|
||||
{file = "numpy-1.23.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:95d79ada05005f6f4f337d3bb9de8a7774f259341c70bc88047a1f7b96a4bcb2"},
|
||||
{file = "numpy-1.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:926db372bc4ac1edf81cfb6c59e2a881606b409ddc0d0920b988174b2e2a767f"},
|
||||
{file = "numpy-1.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c237129f0e732885c9a6076a537e974160482eab8f10db6292e92154d4c67d71"},
|
||||
{file = "numpy-1.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8365b942f9c1a7d0f0dc974747d99dd0a0cdfc5949a33119caf05cb314682d3"},
|
||||
{file = "numpy-1.23.4-cp310-cp310-win32.whl", hash = "sha256:2341f4ab6dba0834b685cce16dad5f9b6606ea8a00e6da154f5dbded70fdc4dd"},
|
||||
{file = "numpy-1.23.4-cp310-cp310-win_amd64.whl", hash = "sha256:d331afac87c92373826af83d2b2b435f57b17a5c74e6268b79355b970626e329"},
|
||||
{file = "numpy-1.23.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:488a66cb667359534bc70028d653ba1cf307bae88eab5929cd707c761ff037db"},
|
||||
{file = "numpy-1.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce03305dd694c4873b9429274fd41fc7eb4e0e4dea07e0af97a933b079a5814f"},
|
||||
{file = "numpy-1.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8981d9b5619569899666170c7c9748920f4a5005bf79c72c07d08c8a035757b0"},
|
||||
{file = "numpy-1.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a70a7d3ce4c0e9284e92285cba91a4a3f5214d87ee0e95928f3614a256a1488"},
|
||||
{file = "numpy-1.23.4-cp311-cp311-win32.whl", hash = "sha256:5e13030f8793e9ee42f9c7d5777465a560eb78fa7e11b1c053427f2ccab90c79"},
|
||||
{file = "numpy-1.23.4-cp311-cp311-win_amd64.whl", hash = "sha256:7607b598217745cc40f751da38ffd03512d33ec06f3523fb0b5f82e09f6f676d"},
|
||||
{file = "numpy-1.23.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ab46e4e7ec63c8a5e6dbf5c1b9e1c92ba23a7ebecc86c336cb7bf3bd2fb10e5"},
|
||||
{file = "numpy-1.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8aae2fb3180940011b4862b2dd3756616841c53db9734b27bb93813cd79fce6"},
|
||||
{file = "numpy-1.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c053d7557a8f022ec823196d242464b6955a7e7e5015b719e76003f63f82d0f"},
|
||||
{file = "numpy-1.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0882323e0ca4245eb0a3d0a74f88ce581cc33aedcfa396e415e5bba7bf05f68"},
|
||||
{file = "numpy-1.23.4-cp38-cp38-win32.whl", hash = "sha256:dada341ebb79619fe00a291185bba370c9803b1e1d7051610e01ed809ef3a4ba"},
|
||||
{file = "numpy-1.23.4-cp38-cp38-win_amd64.whl", hash = "sha256:0fe563fc8ed9dc4474cbf70742673fc4391d70f4363f917599a7fa99f042d5a8"},
|
||||
{file = "numpy-1.23.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c67b833dbccefe97cdd3f52798d430b9d3430396af7cdb2a0c32954c3ef73894"},
|
||||
{file = "numpy-1.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f76025acc8e2114bb664294a07ede0727aa75d63a06d2fae96bf29a81747e4a7"},
|
||||
{file = "numpy-1.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12ac457b63ec8ded85d85c1e17d85efd3c2b0967ca39560b307a35a6703a4735"},
|
||||
{file = "numpy-1.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95de7dc7dc47a312f6feddd3da2500826defdccbc41608d0031276a24181a2c0"},
|
||||
{file = "numpy-1.23.4-cp39-cp39-win32.whl", hash = "sha256:f2f390aa4da44454db40a1f0201401f9036e8d578a25f01a6e237cea238337ef"},
|
||||
{file = "numpy-1.23.4-cp39-cp39-win_amd64.whl", hash = "sha256:f260da502d7441a45695199b4e7fd8ca87db659ba1c78f2bbf31f934fe76ae0e"},
|
||||
{file = "numpy-1.23.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61be02e3bf810b60ab74e81d6d0d36246dbfb644a462458bb53b595791251911"},
|
||||
{file = "numpy-1.23.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296d17aed51161dbad3c67ed6d164e51fcd18dbcd5dd4f9d0a9c6055dce30810"},
|
||||
{file = "numpy-1.23.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4d52914c88b4930dafb6c48ba5115a96cbab40f45740239d9f4159c4ba779962"},
|
||||
{file = "numpy-1.23.4.tar.gz", hash = "sha256:ed2cc92af0efad20198638c69bb0fc2870a58dabfba6eb722c933b48556c686c"},
|
||||
]
|
||||
opencv-python = [
|
||||
{file = "opencv-python-4.6.0.66.tar.gz", hash = "sha256:c5bfae41ad4031e66bb10ec4a0a2ffd3e514d092652781e8b1ac98d1b59f1158"},
|
||||
{file = "opencv_python-4.6.0.66-cp36-abi3-macosx_10_15_x86_64.whl", hash = "sha256:e6e448b62afc95c5b58f97e87ef84699e6607fe5c58730a03301c52496005cae"},
|
||||
{file = "opencv_python-4.6.0.66-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5af8ba35a4fcb8913ffb86e92403e9a656a4bff4a645d196987468f0f8947875"},
|
||||
{file = "opencv_python-4.6.0.66-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbdc84a9b4ea2cbae33861652d25093944b9959279200b7ae0badd32439f74de"},
|
||||
{file = "opencv_python-4.6.0.66-cp36-abi3-win32.whl", hash = "sha256:f482e78de6e7b0b060ff994ffd859bddc3f7f382bb2019ef157b0ea8ca8712f5"},
|
||||
{file = "opencv_python-4.6.0.66-cp36-abi3-win_amd64.whl", hash = "sha256:0dc82a3d8630c099d2f3ac1b1aabee164e8188db54a786abb7a4e27eba309440"},
|
||||
{file = "opencv_python-4.6.0.66-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:6e32af22e3202748bd233ed8f538741876191863882eba44e332d1a34993165b"},
|
||||
]
|
||||
opencv-python = []
|
||||
pathspec = [
|
||||
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
|
||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
{file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"},
|
||||
{file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"},
|
||||
]
|
||||
pefile = [
|
||||
{file = "pefile-2022.5.30.tar.gz", hash = "sha256:a5488a3dd1fd021ce33f969780b88fe0f7eebb76eb20996d7318f307612a045b"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
|
||||
{file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"},
|
||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
||||
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
||||
]
|
||||
pycodestyle = [
|
||||
{file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
|
||||
{file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
|
||||
]
|
||||
pydantic = [
|
||||
{file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"},
|
||||
{file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"},
|
||||
{file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"},
|
||||
{file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"},
|
||||
{file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"},
|
||||
{file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"},
|
||||
{file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"},
|
||||
{file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"},
|
||||
{file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"},
|
||||
{file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"},
|
||||
{file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"},
|
||||
{file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"},
|
||||
{file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"},
|
||||
{file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"},
|
||||
{file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"},
|
||||
]
|
||||
pye3d = [
|
||||
{file = "pye3d-0.3.1.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae6f745f4f66e9fe74986cf41c13135e5e368f8987aeff73c60fa25c29c39264"},
|
||||
{file = "pye3d-0.3.1.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:491d78bb7cae56d6746fc5cdac7d4d8b67a098583182fb37442818b6afa1743f"},
|
||||
{file = "pye3d-0.3.1.post1-cp310-cp310-win_amd64.whl", hash = "sha256:9779822129b372102a89b9c275803f299dbe9a8323350c5f5b41a2fe251b1714"},
|
||||
{file = "pye3d-0.3.1.post1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bfddec99fc3fad36115ed271b810161a98677807acac315fe84713a0ab5cd1b4"},
|
||||
{file = "pye3d-0.3.1.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa9d5cb2e68c41d44faeaee3390b36c2011dcc27c6395a078ba68dace5ab06d"},
|
||||
{file = "pye3d-0.3.1.post1-cp311-cp311-win_amd64.whl", hash = "sha256:4ef8fd736c364a89dc86a30d76f74039ab5b7fe41e256284db94a464454d7127"},
|
||||
{file = "pye3d-0.3.1.post1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b4724cce86fb4dc2c763be460b51b6fd69eb6aa96650aca71e1b114a268fd24a"},
|
||||
{file = "pye3d-0.3.1.post1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7646fe9f2f0da0514a8169d12a403a91f3f309de219ea5b650a9d491dcf1a3f"},
|
||||
{file = "pye3d-0.3.1.post1-cp36-cp36m-win_amd64.whl", hash = "sha256:fc5b58b5d14ceb8717956c288cee1ed33fbe309b843af4e80428e236bf8813c4"},
|
||||
{file = "pye3d-0.3.1.post1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d2eb5ec4017b18921b5da8dafa311204c9b60a72d6cfbc758835d2b66dca47de"},
|
||||
{file = "pye3d-0.3.1.post1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2877b8c00d00e8e103dbe32d81f9c0f420bcc1ecfdd320df4e6e5ad2234782c"},
|
||||
{file = "pye3d-0.3.1.post1-cp37-cp37m-win_amd64.whl", hash = "sha256:eabf6341a48bd754106e827d984294cf10298d4f634ea2b63f70ede8011c1463"},
|
||||
{file = "pye3d-0.3.1.post1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:74230323b2f88a5bd60d57c4837863ccb4c0fe3d0a7305c130da038f13a65e9d"},
|
||||
{file = "pye3d-0.3.1.post1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96aadb0043b8c7561a8ed4e475d86021ae3c2725fe06c040f03d26180836562d"},
|
||||
{file = "pye3d-0.3.1.post1-cp38-cp38-win_amd64.whl", hash = "sha256:2e0df8810205347c5b9136803010691fb7f4b90b1860a2535a742feadd804fc6"},
|
||||
{file = "pye3d-0.3.1.post1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17e2b093fd567a448f3daa8ebe6b5aaf863738b34557b27a6f01e2070da8c66f"},
|
||||
{file = "pye3d-0.3.1.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36f473cf7ab3ca94bf91ba6aa2f1c44305164850e754adc35f43f7c4d61639ce"},
|
||||
{file = "pye3d-0.3.1.post1-cp39-cp39-win_amd64.whl", hash = "sha256:b7953b879f7e2c3c132b047ddb7653deb07ee00573a47adc9f703feb02e606b0"},
|
||||
{file = "pye3d-0.3.1.post1.tar.gz", hash = "sha256:3258b2f545712927af7c47a5d27639dfeb7d5936cf1604c54d6cced526581c91"},
|
||||
]
|
||||
pyflakes = [
|
||||
{file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"},
|
||||
{file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"},
|
||||
]
|
||||
pyinstaller = [
|
||||
{file = "pyinstaller-4.10-py3-none-macosx_10_13_universal2.whl", hash = "sha256:15557cd1a79d182967f0a5040750e6902e13ebd6cab41e3ed84d7b28a306357b"},
|
||||
{file = "pyinstaller-4.10-py3-none-manylinux2014_aarch64.whl", hash = "sha256:f2166ff2cd95eefb0d377ae8d1071f186fa25edd410ede65b376162d5ec41909"},
|
||||
{file = "pyinstaller-4.10-py3-none-manylinux2014_i686.whl", hash = "sha256:7d94518ba1f8e9a8577345312276891ad7d6cd9785e453e9951b35647e2c7078"},
|
||||
{file = "pyinstaller-4.10-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:70c71e827f4b34602cbc7a0947a067b662c1cbdc4db51832e13b97cca3c54dd7"},
|
||||
{file = "pyinstaller-4.10-py3-none-manylinux2014_s390x.whl", hash = "sha256:05c21117b84199272ebd355b556af4714f6e79245e1c435d6f16653786d7d17e"},
|
||||
{file = "pyinstaller-4.10-py3-none-manylinux2014_x86_64.whl", hash = "sha256:714c4dcc319a41416744d1e30c6317405dfaed80d2adc45f8bfa70dc7367e664"},
|
||||
{file = "pyinstaller-4.10-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:581620bdcd32f01e89b13231256b807bb090e7eadf40c81c864ec402afa4758a"},
|
||||
{file = "pyinstaller-4.10-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d4f79c0a774451f12baca4e476376418f011fa3039dde8fd172ea2aa8ff67bad"},
|
||||
{file = "pyinstaller-4.10-py3-none-win32.whl", hash = "sha256:cfed0b3a43e73550a43a094610328109564710b9514afa093ef7199d072cae87"},
|
||||
{file = "pyinstaller-4.10-py3-none-win_amd64.whl", hash = "sha256:0dcaf6557cdb2da763c46e06e95a94a7634ab03fb09d91bc77988b01ee05c907"},
|
||||
{file = "pyinstaller-4.10.tar.gz", hash = "sha256:7749c868d2e2dc84df7d6f65437226183c8a366f3a99bb2737785625c3a3cca1"},
|
||||
{file = "pyinstaller-5.6.2-py3-none-macosx_10_13_universal2.whl", hash = "sha256:1b1e3b37a22fb36555d917f0c3dfb998159ff4af6d8fa7cc0074d630c6fe81ad"},
|
||||
{file = "pyinstaller-5.6.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:05df5d2b9ca645cc6ef61d8a85451d2aabe5501997f1f50cd94306fd6bc0485d"},
|
||||
{file = "pyinstaller-5.6.2-py3-none-manylinux2014_i686.whl", hash = "sha256:eb083c25f711769af0898852ea30dcb727ba43990bbdf9ffbaa9c77a7bd0d720"},
|
||||
{file = "pyinstaller-5.6.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:0d167d57036219914188f1400427dd297b975707e78c32a5511191e607be920a"},
|
||||
{file = "pyinstaller-5.6.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:32727232f446aa96e394f01b0c35b3de0dc3513c6ba3e26d1ef64c57edb1e9e5"},
|
||||
{file = "pyinstaller-5.6.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:181856ade585b090379ae26b7017dc2c30620e36e3a804b381417a6dc3b2a82b"},
|
||||
{file = "pyinstaller-5.6.2-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:77888f52b61089caa0bee70809bbce9e9b1c613c88b6cb0742ff2a45f1511cbb"},
|
||||
{file = "pyinstaller-5.6.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d888db9afedff290d362ee296d30eb339abeba707ca1565916ce1cd5947131c3"},
|
||||
{file = "pyinstaller-5.6.2-py3-none-win32.whl", hash = "sha256:e026adc92c60158741d0bfca27eefaa2414801f61328cb84d0c88241fe8c2087"},
|
||||
{file = "pyinstaller-5.6.2-py3-none-win_amd64.whl", hash = "sha256:04ecf805bde2ef25b8e3642410871e6747c22fa7254107f155b8cd179c2a13b6"},
|
||||
{file = "pyinstaller-5.6.2.tar.gz", hash = "sha256:865025b6809d777bb0f66d8f8ab50cc97dc3dbe0ff09a1ef1f2fd646432714fc"},
|
||||
]
|
||||
pyinstaller-hooks-contrib = [
|
||||
{file = "pyinstaller-hooks-contrib-2022.0.tar.gz", hash = "sha256:61b667f51b2525377fae30793f38fd9752a08032c72b209effabf707c840cc38"},
|
||||
{file = "pyinstaller_hooks_contrib-2022.0-py2.py3-none-any.whl", hash = "sha256:29f0bd8fbb2ff6f2df60a0c147e5b5ad65ae5c1a982d90641a5f712de03fa161"},
|
||||
{file = "pyinstaller-hooks-contrib-2022.11.tar.gz", hash = "sha256:2e1870350bb9ef2e09c1c1bb30347eb3185c5ef38c040ed04190d6d0b4b5df62"},
|
||||
{file = "pyinstaller_hooks_contrib-2022.11-py2.py3-none-any.whl", hash = "sha256:8db4064d9c127940455ec363bebbf74228be21cdafb44f7ea4f44cf434d9bcaa"},
|
||||
]
|
||||
pysimplegui = [
|
||||
{file = "PySimpleGUI-4.60.4-py3-none-any.whl", hash = "sha256:e133fbd21779f0f125cebbc2a4e1f5a931a383738661013ff33ad525d5611eda"},
|
||||
@ -636,104 +657,54 @@ python-osc = [
|
||||
{file = "python-osc-1.8.0.tar.gz", hash = "sha256:2f8c187c68d239960fb2eddcb5346a62a9b35e64f2de045b3e5e509f475ca73d"},
|
||||
{file = "python_osc-1.8.0-py3-none-any.whl", hash = "sha256:9e2abb2fc9ba2c356f8e951609a03c9c7017bf0bad82cca8490e9b8af9e92a0b"},
|
||||
]
|
||||
pywin32 = [
|
||||
{file = "pywin32-304-cp310-cp310-win32.whl", hash = "sha256:3c7bacf5e24298c86314f03fa20e16558a4e4138fc34615d7de4070c23e65af3"},
|
||||
{file = "pywin32-304-cp310-cp310-win_amd64.whl", hash = "sha256:4f32145913a2447736dad62495199a8e280a77a0ca662daa2332acf849f0be48"},
|
||||
{file = "pywin32-304-cp310-cp310-win_arm64.whl", hash = "sha256:d3ee45adff48e0551d1aa60d2ec066fec006083b791f5c3527c40cd8aefac71f"},
|
||||
{file = "pywin32-304-cp311-cp311-win32.whl", hash = "sha256:30c53d6ce44c12a316a06c153ea74152d3b1342610f1b99d40ba2795e5af0269"},
|
||||
{file = "pywin32-304-cp311-cp311-win_amd64.whl", hash = "sha256:7ffa0c0fa4ae4077e8b8aa73800540ef8c24530057768c3ac57c609f99a14fd4"},
|
||||
{file = "pywin32-304-cp311-cp311-win_arm64.whl", hash = "sha256:cbbe34dad39bdbaa2889a424d28752f1b4971939b14b1bb48cbf0182a3bcfc43"},
|
||||
{file = "pywin32-304-cp36-cp36m-win32.whl", hash = "sha256:be253e7b14bc601718f014d2832e4c18a5b023cbe72db826da63df76b77507a1"},
|
||||
{file = "pywin32-304-cp36-cp36m-win_amd64.whl", hash = "sha256:de9827c23321dcf43d2f288f09f3b6d772fee11e809015bdae9e69fe13213988"},
|
||||
{file = "pywin32-304-cp37-cp37m-win32.whl", hash = "sha256:f64c0377cf01b61bd5e76c25e1480ca8ab3b73f0c4add50538d332afdf8f69c5"},
|
||||
{file = "pywin32-304-cp37-cp37m-win_amd64.whl", hash = "sha256:bb2ea2aa81e96eee6a6b79d87e1d1648d3f8b87f9a64499e0b92b30d141e76df"},
|
||||
{file = "pywin32-304-cp38-cp38-win32.whl", hash = "sha256:94037b5259701988954931333aafd39cf897e990852115656b014ce72e052e96"},
|
||||
{file = "pywin32-304-cp38-cp38-win_amd64.whl", hash = "sha256:ead865a2e179b30fb717831f73cf4373401fc62fbc3455a0889a7ddac848f83e"},
|
||||
{file = "pywin32-304-cp39-cp39-win32.whl", hash = "sha256:25746d841201fd9f96b648a248f731c1dec851c9a08b8e33da8b56148e4c65cc"},
|
||||
{file = "pywin32-304-cp39-cp39-win_amd64.whl", hash = "sha256:d24a3382f013b21aa24a5cfbfad5a2cd9926610c0affde3e8ab5b3d7dbcf4ac9"},
|
||||
]
|
||||
pywin32-ctypes = [
|
||||
{file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"},
|
||||
{file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
|
||||
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
|
||||
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
||||
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
||||
]
|
||||
scipy = [
|
||||
{file = "scipy-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4f12d13ffbc16e988fa40809cbbd7a8b45bc05ff6ea0ba8e3e41f6f4db3a9e47"},
|
||||
{file = "scipy-1.5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a254b98dbcc744c723a838c03b74a8a34c0558c9ac5c86d5561703362231107d"},
|
||||
{file = "scipy-1.5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:368c0f69f93186309e1b4beb8e26d51dd6f5010b79264c0f1e9ca00cd92ea8c9"},
|
||||
{file = "scipy-1.5.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:4598cf03136067000855d6b44d7a1f4f46994164bcd450fb2c3d481afc25dd06"},
|
||||
{file = "scipy-1.5.4-cp36-cp36m-win32.whl", hash = "sha256:e98d49a5717369d8241d6cf33ecb0ca72deee392414118198a8e5b4c35c56340"},
|
||||
{file = "scipy-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:65923bc3809524e46fb7eb4d6346552cbb6a1ffc41be748535aa502a2e3d3389"},
|
||||
{file = "scipy-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9ad4fcddcbf5dc67619379782e6aeef41218a79e17979aaed01ed099876c0e62"},
|
||||
{file = "scipy-1.5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f87b39f4d69cf7d7529d7b1098cb712033b17ea7714aed831b95628f483fd012"},
|
||||
{file = "scipy-1.5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:25b241034215247481f53355e05f9e25462682b13bd9191359075682adcd9554"},
|
||||
{file = "scipy-1.5.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:fa789583fc94a7689b45834453fec095245c7e69c58561dc159b5d5277057e4c"},
|
||||
{file = "scipy-1.5.4-cp37-cp37m-win32.whl", hash = "sha256:d6d25c41a009e3c6b7e757338948d0076ee1dd1770d1c09ec131f11946883c54"},
|
||||
{file = "scipy-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:2c872de0c69ed20fb1a9b9cf6f77298b04a26f0b8720a5457be08be254366c6e"},
|
||||
{file = "scipy-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e360cb2299028d0b0d0f65a5c5e51fc16a335f1603aa2357c25766c8dab56938"},
|
||||
{file = "scipy-1.5.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3397c129b479846d7eaa18f999369a24322d008fac0782e7828fa567358c36ce"},
|
||||
{file = "scipy-1.5.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:168c45c0c32e23f613db7c9e4e780bc61982d71dcd406ead746c7c7c2f2004ce"},
|
||||
{file = "scipy-1.5.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:213bc59191da2f479984ad4ec39406bf949a99aba70e9237b916ce7547b6ef42"},
|
||||
{file = "scipy-1.5.4-cp38-cp38-win32.whl", hash = "sha256:634568a3018bc16a83cda28d4f7aed0d803dd5618facb36e977e53b2df868443"},
|
||||
{file = "scipy-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:b03c4338d6d3d299e8ca494194c0ae4f611548da59e3c038813f1a43976cb437"},
|
||||
{file = "scipy-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3d5db5d815370c28d938cf9b0809dade4acf7aba57eaf7ef733bfedc9b2474c4"},
|
||||
{file = "scipy-1.5.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6b0ceb23560f46dd236a8ad4378fc40bad1783e997604ba845e131d6c680963e"},
|
||||
{file = "scipy-1.5.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:ed572470af2438b526ea574ff8f05e7f39b44ac37f712105e57fc4d53a6fb660"},
|
||||
{file = "scipy-1.5.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8c8d6ca19c8497344b810b0b0344f8375af5f6bb9c98bd42e33f747417ab3f57"},
|
||||
{file = "scipy-1.5.4-cp39-cp39-win32.whl", hash = "sha256:d84cadd7d7998433334c99fa55bcba0d8b4aeff0edb123b2a1dfcface538e474"},
|
||||
{file = "scipy-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:cc1f78ebc982cd0602c9a7615d878396bec94908db67d4ecddca864d049112f2"},
|
||||
{file = "scipy-1.5.4.tar.gz", hash = "sha256:4a453d5e5689de62e5d38edf40af3f17560bfd63c9c5bd228c18c1f99afa155b"},
|
||||
{file = "scipy-1.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1884b66a54887e21addf9c16fb588720a8309a57b2e258ae1c7986d4444d3bc0"},
|
||||
{file = "scipy-1.9.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:83b89e9586c62e787f5012e8475fbb12185bafb996a03257e9675cd73d3736dd"},
|
||||
{file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a72d885fa44247f92743fc20732ae55564ff2a519e8302fb7e18717c5355a8b"},
|
||||
{file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01e1dd7b15bd2449c8bfc6b7cc67d630700ed655654f0dfcf121600bad205c9"},
|
||||
{file = "scipy-1.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:68239b6aa6f9c593da8be1509a05cb7f9efe98b80f43a5861cd24c7557e98523"},
|
||||
{file = "scipy-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b41bc822679ad1c9a5f023bc93f6d0543129ca0f37c1ce294dd9d386f0a21096"},
|
||||
{file = "scipy-1.9.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:90453d2b93ea82a9f434e4e1cba043e779ff67b92f7a0e85d05d286a3625df3c"},
|
||||
{file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c06e62a390a9167da60bedd4575a14c1f58ca9dfde59830fc42e5197283dab"},
|
||||
{file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abaf921531b5aeaafced90157db505e10345e45038c39e5d9b6c7922d68085cb"},
|
||||
{file = "scipy-1.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:06d2e1b4c491dc7d8eacea139a1b0b295f74e1a1a0f704c375028f8320d16e31"},
|
||||
{file = "scipy-1.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a04cd7d0d3eff6ea4719371cbc44df31411862b9646db617c99718ff68d4840"},
|
||||
{file = "scipy-1.9.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:545c83ffb518094d8c9d83cce216c0c32f8c04aaf28b92cc8283eda0685162d5"},
|
||||
{file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d54222d7a3ba6022fdf5773931b5d7c56efe41ede7f7128c7b1637700409108"},
|
||||
{file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cff3a5295234037e39500d35316a4c5794739433528310e117b8a9a0c76d20fc"},
|
||||
{file = "scipy-1.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:2318bef588acc7a574f5bfdff9c172d0b1bf2c8143d9582e05f878e580a3781e"},
|
||||
{file = "scipy-1.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d644a64e174c16cb4b2e41dfea6af722053e83d066da7343f333a54dae9bc31c"},
|
||||
{file = "scipy-1.9.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:da8245491d73ed0a994ed9c2e380fd058ce2fa8a18da204681f2fe1f57f98f95"},
|
||||
{file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4db5b30849606a95dcf519763dd3ab6fe9bd91df49eba517359e450a7d80ce2e"},
|
||||
{file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0"},
|
||||
{file = "scipy-1.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:5b88e6d91ad9d59478fafe92a7c757d00c59e3bdc3331be8ada76a4f8d683f58"},
|
||||
{file = "scipy-1.9.3.tar.gz", hash = "sha256:fbc5c05c85c1a02be77b1ff591087c83bc44579c6d2bd9fb798bb64ea5e1a027"},
|
||||
]
|
||||
setuptools = [
|
||||
{file = "setuptools-65.5.0-py3-none-any.whl", hash = "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356"},
|
||||
{file = "setuptools-65.5.0.tar.gz", hash = "sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17"},
|
||||
]
|
||||
sortedcontainers = [
|
||||
{file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
|
||||
{file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
|
||||
]
|
||||
sympy = [
|
||||
{file = "sympy-1.9-py3-none-any.whl", hash = "sha256:8bc5de4608b7aa4e7ffd1b25452ae87ccc5f6ca667c661aafb854a1ade337d0c"},
|
||||
{file = "sympy-1.9.tar.gz", hash = "sha256:c7a880e229df96759f955d4f3970d4cabce79f60f5b18830c08b90ce77cd5fdc"},
|
||||
]
|
||||
tomli = [
|
||||
{file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"},
|
||||
{file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"},
|
||||
]
|
||||
typed-ast = [
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
|
||||
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
|
||||
{file = "sympy-1.11.1-py3-none-any.whl", hash = "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf"},
|
||||
{file = "sympy-1.11.1.tar.gz", hash = "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
|
||||
{file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
|
||||
{file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
|
||||
{file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
|
||||
{file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
|
||||
]
|
||||
zipp = [
|
||||
{file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
|
||||
{file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"},
|
||||
]
|
||||
|
||||
@ -7,34 +7,20 @@ license = "MIT"
|
||||
repository = "https://github.com/RedHawk989/EyeTrackVR"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "~3.6.2"
|
||||
PySimpleGUI = "^4.60.4"
|
||||
msgpack = "^1.0.4"
|
||||
python = "~3.11.0"
|
||||
python-osc = "^1.8.0"
|
||||
dacite = "^1.6.0"
|
||||
pywin32 = "^304"
|
||||
dataclasses = "^0.8"
|
||||
numpy= "^1.19.5"
|
||||
sortedcontainers = "^2.4.0"
|
||||
sympy = "^1.9.0"
|
||||
requests = "^2.27.1"
|
||||
# for some reason OpenCV / numpy maintainers arent specifying dependencies correctly resulting in poetry throwing errors
|
||||
# this isnt a good solution and i shouldnt need to do this because opencv 4.5.3.56 is compatible with python ^3.6.2
|
||||
# and numpy 1.19.5 but poetry has forced my hand, and since we are stuck on python 3.6 for now, this is the best solution
|
||||
# we cant upgrade / downgrade numpy or opencv to resolve the errors because that breaks pyinstaller and a lot of other things
|
||||
# this is really shitty and i fucking hate it because it means we have to specify the exact wheel to use per platform / architecture
|
||||
# although this isnt a problem that poses any majour issues it is super annoying
|
||||
# https://github.com/python-poetry/poetry/issues/4451#issuecomment-1200467157
|
||||
opencv-python = [
|
||||
{platform = "win32", markers="platform_machine=='AMD64'", url="https://files.pythonhosted.org/packages/82/5e/f5df9ce92b7d25f43baf64327ea89f832f1eac7f250c1569a22f8b3fca3e/opencv_python-4.5.3.56-cp36-cp36m-win_amd64.whl"},
|
||||
{platform = "win32", markers="platform_machine=='x86_64'", url="https://files.pythonhosted.org/packages/82/5e/f5df9ce92b7d25f43baf64327ea89f832f1eac7f250c1569a22f8b3fca3e/opencv_python-4.5.3.56-cp36-cp36m-win_amd64.whl"},
|
||||
{platform = "win32", markers="platform_machine=='x86'", url="https://files.pythonhosted.org/packages/0b/35/e1b4bd9003ff55ef176d172374629ac5f78533d473b75def07cb6313e467/opencv_python-4.5.3.56-cp36-cp36m-win32.whl"}
|
||||
]
|
||||
scipy = "^1.5.4"
|
||||
opencv-python = "^4.6.0.66"
|
||||
numpy = "^1.23.4"
|
||||
pye3d = "^0.3.1.post1"
|
||||
requests = "^2.28.1"
|
||||
pysimplegui = "^4.60.4"
|
||||
scipy = "^1.9.3"
|
||||
pydantic = "^1.10.2"
|
||||
sympy = "^1.11.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
black = "^22.8.0"
|
||||
pyinstaller = "^4.10.0"
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^22.10.0"
|
||||
pyinstaller = "^5.6.2"
|
||||
flake8 = "^5.0.4"
|
||||
|
||||
[build-system]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user