diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 44e137e..dc3fb20 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,7 +70,7 @@ jobs: - run: source $VENV - name: Build Backend - run: poetry run pyinstaller --noconfirm ETVR.spec TrackingBackend/main.py + run: poetry run pyinstaller --noconfirm ETVR.spec eyetrackvr_backend/main.py - name: Rename Linux Binary if: ${{ matrix.os == 'ubuntu-latest' }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6c6f302..df8866b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,14 +1,24 @@ name: Lint -on: [push, workflow_dispatch] +on: [push, workflow_dispatch, pull_request] jobs: ruff: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Lint with Ruff - uses: chartboost/ruff-action@v1 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: 3.11 + - name: Setup Poetry + uses: snok/install-poetry@v1 with: - args: --select E,F --ignore E501,F401,F541 + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + - name: Install dependencies + run: poetry install + - name: Lint with ruff + run: poetry run ruff check eyetrackvr_backend/ pytest: runs-on: ubuntu-latest @@ -49,4 +59,4 @@ jobs: run: poetry install - name: Lint with Mypy # Wish we could warn instead of erroring - run: poetry run mypy --ignore-missing-imports --check-untyped-defs --show-error-context --implicit-optional --disable-error-code union-attr TrackingBackend/ + run: poetry run mypy --ignore-missing-imports --check-untyped-defs --show-error-context --implicit-optional --disable-error-code union-attr eyetrackvr_backend/ diff --git a/.gitignore b/.gitignore index 82bcf4e..5664137 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,8 @@ dist/ build/ tracker-config.json TrackingBackend/tracker-config.json -**/.DS_Store \ No newline at end of file +**/.DS_Store +# Nix +result +.envrc +.direnv/ diff --git a/ETVR.spec b/ETVR.spec index f9f0177..5e9461d 100644 --- a/ETVR.spec +++ b/ETVR.spec @@ -5,14 +5,11 @@ block_cipher = None a = Analysis( - ['TrackingBackend/main.py'], + ["eyetrackvr_backend/__main__.py"], pathex=[], binaries=[], - # WTF, wildcard doesnt apply to sub folders??? datas=[ - ("TrackingBackend/assets/*", "assets/"), - ("TrackingBackend/assets/models/*", "assets/models/"), - ("TrackingBackend/assets/images/*", "assets/images/") + ("eyetrackvr_backend/assets", "eyetrackvr_backend/assets") ], hiddenimports=["cv2", "numpy"], hookspath=[], @@ -46,5 +43,5 @@ exe = EXE( target_arch=None, codesign_identity=None, entitlements_file=None, - icon="TrackingBackend/assets/images/logo.ico" + icon="eyetrackvr_backend/assets/images/logo.ico" ) diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..99568b0 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2025] [EyeTrackVR] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE b/LICENSE-MIT similarity index 97% rename from LICENSE rename to LICENSE-MIT index f30aaf9..d96d8e2 100644 --- a/LICENSE +++ b/LICENSE-MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 [Assassin] +Copyright (c) 2025 [EyeTrackVR] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 3713321..4d64794 100644 --- a/Makefile +++ b/Makefile @@ -2,35 +2,35 @@ .DEFAULT_GOAL := run stream1: - ffmpeg -stream_loop "-1" -i TrackingBackend/assets/ETVR_SAMPLE.mp4 -attempt_recovery 1 -http_persistent 1 -http_seekable 0 -listen 1 -f mp4 -movflags frag_keyframe+empty_moov http://localhost:8080 + ffmpeg -stream_loop "-1" -i eyetrackvr_backend/assets/ETVR_SAMPLE.mp4 -attempt_recovery 1 -http_persistent 1 -http_seekable 0 -listen 1 -f mp4 -movflags frag_keyframe+empty_moov http://localhost:8080 stream2: - ffmpeg -stream_loop "-1" -i TrackingBackend/assets/ETVR_SAMPLE.mp4 -attempt_recovery 1 -http_persistent 1 -http_seekable 0 -listen 1 -f mp4 -movflags frag_keyframe+empty_moov http://localhost:8081 + ffmpeg -stream_loop "-1" -i eyetrackvr_backend/assets/ETVR_SAMPLE.mp4 -attempt_recovery 1 -http_persistent 1 -http_seekable 0 -listen 1 -f mp4 -movflags frag_keyframe+empty_moov http://localhost:8081 install: poetry install run: - cd TrackingBackend/ && poetry run uvicorn --factory main:setup_app --reload --port 8000 + poetry run uvicorn --factory eyetrackvr_backend:setup_app --reload --port 8000 black: - poetry run black TrackingBackend/ + poetry run black eyetrackvr_backend/ ruff: - poetry run ruff TrackingBackend/ + poetry run ruff eyetrackvr_backend/ mypy: - poetry run mypy --ignore-missing-imports --check-untyped-defs TrackingBackend/ + poetry run mypy --ignore-missing-imports --check-untyped-defs eyetrackvr_backend/ pyinstaller: - poetry run pyinstaller ETVR.spec TrackingBackend/main.py + poetry run pyinstaller ETVR.spec nuitka: - poetry run python -m nuitka --standalone --include-module=cv2 TrackingBackend/main.py + poetry run python -m nuitka --standalone --include-module=cv2 eyetrackvr_backend/__main__.py clean: - rm -rf TrackingBackend/__pycache__/ - rm -rf TrackingBackend/app/__pycache__/ - rm -rf TrackingBackend/app/algorithms/__pycache__/ + rm -rf eyetrackvr_backend/__pycache__/ + rm -rf eyetrackvr_backend/app/__pycache__/ + rm -rf eyetrackvr_backend/app/algorithms/__pycache__/ rm -rf build/ - rm -rf dist/ \ No newline at end of file + rm -rf dist/ diff --git a/README.md b/README.md index 2064e6a..7bb772c 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ To start the local development server run either of the following commands. python build.py run ``` ```bash -cd TrackingBackend/ && poetry run uvicorn --factory main:setup_app --reload --port 8000 +poetry run uvicorn --factory eyetrackvr_backend:setup_app --reload --port 8000 ``` ### The build script @@ -115,7 +115,7 @@ python build.py build ``` If you want to build the backend manually you can do so with the following command. ```bash -poetry run pyinstaller ETVR.spec TrackingBackend/main.py +poetry run pyinstaller ETVR.spec ``` ### Profiling @@ -124,9 +124,9 @@ To start profiling run the following command, this will start the backend and ge If you dont like viztracer you can use almost any other profiler (multi-processing and multi-threading support is required)\ *currently using the build script to start profiling is broken!* ```bash -cd TrackingBackend/ && poetry run viztracer main.py +poetry run viztracer -m eyetrackvr_backend:main ``` ## License -Unless explicitly stated otherwise all code contained within this repository is under the [MIT License](./LICENSE) \ No newline at end of file +Unless explicitly stated otherwise all code contained within this repository is under the [MIT License](./LICENSE-MIT) diff --git a/TrackingBackend/app/__init__.py b/TrackingBackend/app/__init__.py deleted file mode 100644 index e483e70..0000000 --- a/TrackingBackend/app/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .tracker import Tracker -from .types import EyeData, LogLevel, TrackerPosition -from .logger import get_logger, setup_logger -from .config import EyeTrackConfig, CameraConfig, OSCConfig diff --git a/TrackingBackend/app/algorithms/hsf.py b/TrackingBackend/app/algorithms/hsf.py deleted file mode 100644 index 152d8eb..0000000 --- a/TrackingBackend/app/algorithms/hsf.py +++ /dev/null @@ -1,7 +0,0 @@ -from app.processes import EyeProcessor -from app.utils import BaseAlgorithm - - -class HSF(BaseAlgorithm): - def __init__(self, eye_processor: EyeProcessor): - self.ep = eye_processor diff --git a/TrackingBackend/app/algorithms/ransac.py b/TrackingBackend/app/algorithms/ransac.py deleted file mode 100644 index 5b07ff2..0000000 --- a/TrackingBackend/app/algorithms/ransac.py +++ /dev/null @@ -1,7 +0,0 @@ -from app.processes import EyeProcessor -from app.utils import BaseAlgorithm - - -class Ransac(BaseAlgorithm): - def __init__(self, eye_processor: EyeProcessor): - self.ep = eye_processor diff --git a/build.py b/build.py index b7a7240..19d4bad 100644 --- a/build.py +++ b/build.py @@ -20,20 +20,20 @@ def install(): def lint(): - print("Running ruff for linting...") - os.system(f"poetry run ruff TrackingBackend{os.path.sep}") + print("Running black for code formatting...") + os.system(f"poetry run black eyetrackvr_backend{os.path.sep}") print("-" * 80) - print("Running black for code formatting...") - os.system(f"poetry run black TrackingBackend{os.path.sep}") + print("Running ruff for linting...") + os.system(f"poetry run ruff check eyetrackvr_backend{os.path.sep}") print("-" * 80) print("Running mypy for type checking...") - os.system(f"poetry run mypy --ignore-missing-imports --check-untyped-defs TrackingBackend{os.path.sep}") + os.system(f"poetry run mypy --ignore-missing-imports --check-untyped-defs eyetrackvr_backend{os.path.sep}") print("-" * 80) print("Running pytest for unit testing...") - os.system(f"poetry run pytest TrackingBackend{os.path.sep}") + os.system(f"poetry run pytest eyetrackvr_backend{os.path.sep}") print("-" * 80) @@ -53,20 +53,18 @@ def clean(): def build(): - os.system("poetry run pyinstaller ETVR.spec TrackingBackend/main.py") + os.system("poetry run pyinstaller ETVR.spec") def profile(): - os.chdir(f"{os.path.dirname(os.path.abspath(__file__))}{os.path.sep}TrackingBackend") try: - os.system("poetry run viztracer main.py") + os.system("poetry run viztracer -m eyetrackvr_backend.__main__") except KeyboardInterrupt: exit(0) def run(): - os.chdir(f"{os.path.dirname(os.path.abspath(__file__))}{os.path.sep}TrackingBackend") - os.system("poetry run uvicorn --factory main:setup_app --reload --port 8000") + os.system("poetry run uvicorn --factory eyetrackvr_backend:setup_app --reload --port 8000") def emulate(): diff --git a/TrackingBackend/main.py b/eyetrackvr_backend/__init__.py similarity index 61% rename from TrackingBackend/main.py rename to eyetrackvr_backend/__init__.py index 800a927..cb5ba0a 100644 --- a/TrackingBackend/main.py +++ b/eyetrackvr_backend/__init__.py @@ -1,74 +1,56 @@ -import os - -if os.pardir != os.path.dirname(__file__): - os.chdir(os.path.dirname(__file__)) - -from fastapi.staticfiles import StaticFiles -from fastapi.responses import FileResponse -from app.logger import setup_logger -from fastapi import FastAPI -from app.etvr import ETVR - - -def setup_app(): - setup_logger() - etvr_app = ETVR() - etvr_app.add_routes() - app = FastAPI() - app.include_router(etvr_app.router) - app.mount("/images", StaticFiles(directory="assets/images")) - app.add_route("/", FileResponse("assets/index.html"), methods=["GET"]) - - return app - - -def main() -> int: - import uvicorn - import sys - - port: int = 8000 - host: str = "127.0.0.1" - args = sys.argv[1:] - for i, v in enumerate(args): - try: - match v: - case "--help" | "-h": - print("Usage: python main.py [OPTIONS]") - print("Options:") - print(f"--port [PORT] Set the port to listen on. Default: {port}") - print(f"--host [HOST] Set the host to listen on. Default: {host}") - print(f"--help, -h Show this help message and exit") # noqa: F541 - return 0 - case "--port": - if int(args[i + 1]) > 65535: - print("Port must be between 0 and 65535!") - return 1 - port = int(args[i + 1]) - case "--host": - host = args[i + 1] - case _: - if v.startswith("-"): - print("Unknown argument " + v) - except IndexError: - print("Missing value for argument " + v) - return 1 - except ValueError: - print("Invalid value for argument " + v) - return 1 - - app = setup_app() - uvicorn.run(app=app, host=host, port=port, reload=False) - return 0 - - -if __name__ == "__main__": - import multiprocessing - import sys - - # check if we are running directly, if so, warn the user - if not (getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")): - print("WARNING: backend is being run directly but has not been compiled into an executable!") - print("It is recomended to start the backend using uvicorn CLI when not compiled as an executable.") - else: - multiprocessing.freeze_support() - raise SystemExit(main()) +from fastapi.staticfiles import StaticFiles +from fastapi import FastAPI + +from .assets import ASSETS_DIR, IMAGES_DIR +from .etvr import ETVR +from .logger import setup_logger + + +def setup_app(): + setup_logger() + etvr_app = ETVR() + etvr_app.add_routes() + app = FastAPI() + app.include_router(etvr_app.router) + app.mount("/", StaticFiles(directory=ASSETS_DIR, html=True)) + app.mount("/images", StaticFiles(directory=IMAGES_DIR)) + return app + + +def main() -> int: + import uvicorn + import sys + + port: int = 8000 + host: str = "127.0.0.1" + args = sys.argv[1:] + for i, v in enumerate(args): + try: + match v: + case "--help" | "-h": + print(f"Usage: {sys.argv[0]} [OPTIONS]") + print("Options:") + print(f"--port [PORT] Set the port to listen on. Default: {port}") + print(f"--host [HOST] Set the host to listen on. Default: {host}") + print(f"--help, -h Show this help message and exit") # noqa: F541 + return 0 + case "--port": + if int(args[i + 1]) > 65535: + print("Port must be between 0 and 65535!") + return 1 + port = int(args[i + 1]) + case "--host": + host = args[i + 1] + case _: + if v.startswith("-"): + print("Unknown argument " + v) + except IndexError: + print("Missing value for argument " + v) + return 1 + except ValueError: + print("Invalid value for argument " + v) + return 1 + + app = setup_app() + uvicorn.run(app=app, host=host, port=port, reload=False) + return 0 diff --git a/eyetrackvr_backend/__main__.py b/eyetrackvr_backend/__main__.py new file mode 100644 index 0000000..2af422e --- /dev/null +++ b/eyetrackvr_backend/__main__.py @@ -0,0 +1,19 @@ +# This is the main script run by pyinstaller +from eyetrackvr_backend import main as real_main + +import multiprocessing +import sys + + +def main(): + # check if we are running directly, if so, warn the user + if not (getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")): + print("WARNING: backend is being run directly but has not been compiled into an executable!") + print("It is recommended to start the backend using uvicorn CLI when not compiled as an executable.") + else: + multiprocessing.freeze_support() + raise SystemExit(real_main()) + + +if __name__ == "__main__": + main() diff --git a/TrackingBackend/app/algorithms/__init__.py b/eyetrackvr_backend/algorithms/__init__.py similarity index 63% rename from TrackingBackend/app/algorithms/__init__.py rename to eyetrackvr_backend/algorithms/__init__.py index 4a58ac5..4e19aee 100644 --- a/TrackingBackend/app/algorithms/__init__.py +++ b/eyetrackvr_backend/algorithms/__init__.py @@ -2,4 +2,6 @@ from .leap import Leap from .blob import Blob from .hsrac import HSRAC -from .ransac import Ransac + +# from .ransac import RANSAC +from .ahsf import AHSF diff --git a/eyetrackvr_backend/algorithms/ahsf.py b/eyetrackvr_backend/algorithms/ahsf.py new file mode 100644 index 0000000..c9f1dc1 --- /dev/null +++ b/eyetrackvr_backend/algorithms/ahsf.py @@ -0,0 +1,436 @@ +""" +------------------------------------------------------------------------------------------------------ + + ,@@@@@@ + @@@@@@@@@@@ @@@ + @@@@@@@@@@@@ @@@@@@@@@@@ + @@@@@@@@@@@@@ @@@@@@@@@@@@@@ + @@@@@@@/ ,@@@@@@@@@@@@@ + /@@@@@@@@@@@@@@@ @@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@ + @@@@@@@@ @@@@@ + ,@@@ @@@@& + @@@@@@. @@@@ + @@@ @@@@@@@@@/ @@@@@ + ,@@@. @@@@@@((@ @@@@( + //@@@ ,, @@@@ @@@@@ + @@@( @@@@@@@ + @@@ @ @@@@@@@@# + @@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@( + +Adaptive Haar Surround Feature by: Summer, PallasNeko +Algorithm App Implementation By: Prohurtz, ShyAssassin + +Copyright (c) 2024 EyeTrackVR <3 +This project is licensed under the MIT License. See LICENSE for more details. +------------------------------------------------------------------------------------------------------ +""" + +import cv2 +import numpy as np +from cv2.typing import MatLike +from functools import lru_cache +from ..utils import BaseAlgorithm +from ..processes import EyeProcessor +from ..types import EyeData, TrackerPosition, TRACKING_FAILED + +# cache param +lru_maxsize_vvs = 16 +lru_maxsize_vs = 64 +lru_maxsize_s = 128 + + +class AHSF(BaseAlgorithm): + def __init__(self, eye_processor: EyeProcessor): + self.ep = eye_processor + + def draw_coarse(self, frame, pupil_rect, outer_rect, center_fitting): + cv2.rectangle( + frame, + (pupil_rect[0], pupil_rect[1]), + (pupil_rect[0] + pupil_rect[2], pupil_rect[1] + pupil_rect[3]), + (0, 0, 0), + 1, + ) + cv2.rectangle( + frame, + (outer_rect[0], outer_rect[1]), + (outer_rect[0] + outer_rect[2], outer_rect[1] + outer_rect[3]), + (105, 105, 105), + 1, + ) + cv2.drawMarker(frame, center_fitting, (255, 255, 255), cv2.MARKER_CROSS, 15, 1) + + def run(self, frame: MatLike, tracker_position: TrackerPosition) -> tuple[EyeData, MatLike]: + average_color = np.mean(frame) # type: ignore[arg-type] + # Get the dimensions of the rotated image + height, width = frame.shape + # Determine the size of the square background (choose the larger dimension) + max_dimension = max(height, width) + # Create a square background with the average color + square_background = np.full((max_dimension, max_dimension), average_color, dtype=np.uint8) + # Calculate the position to paste the rotated image onto the square background + x_offset = (max_dimension - width) // 2 + y_offset = (max_dimension - height) // 2 + + # Paste the rotated image onto the square background + square_background[y_offset : y_offset + height, x_offset : x_offset + width] = frame + frame = square_background + + # TODO: replace params with a dataclass + params = { # these can be tuned more + "ratio_downsample": 0.5, + "use_init_rect": False, + "mu_outer": 250, # aprroximatly how much pupil should be in the outer rect + "mu_inner": 50, # aprroximatly how much pupil should be in the inner rect + "ratio_outer": 1.0, # rectangular ratio. 1 means square (LIKE REGULAR HSF) + "kf": 2, # noise filter. May lose tracking if too high (or even never start) + "width_min": frame.shape[1] * 0.08, # Minimum width of the pupil + "width_max": frame.shape[1] * 0.5, # Maximum width of the pupil + "wh_step": 5, # Pupil width and height step search size + "xy_step": 10, # Kernel movement step search size + "roi": (0, 0, frame.shape[1], frame.shape[0]), + "init_rect_flag": False, + "init_rect": (0, 0, frame.shape[1], frame.shape[0]), + } + try: + ( + pupil_rect_coarse, + outer_rect_coarse, + max_response_coarse, + mu_inner, + mu_outer, + ) = coarse_detection(frame, params) + ellipse_rect, center_fitting = fine_detection(frame, pupil_rect_coarse) + except TypeError: + return TRACKING_FAILED, frame + + # x = outer_rect_coarse[0] + outer_rect_coarse[2] / 2 + # y = outer_rect_coarse[1] + outer_rect_coarse[3] / 2 + x = center_fitting[0] + y = center_fitting[1] + self.draw_coarse(frame, pupil_rect_coarse, outer_rect_coarse, center_fitting) + + x = x / frame.shape[1] + y = y / frame.shape[0] + + return EyeData(x, y, 1, tracker_position), frame + + +@lru_cache(maxsize=lru_maxsize_vvs) +def get_empty_array(frame_shape, width_min, width_max, wh_step, xy_step, roi, ratio_outer): + frame_int_dtype = np.intc + np_index_dtype = ( + np.intc + ) # memo: Better to use np.intp, but a little slower ref: https://numpy.org/doc/1.25/user/basics.indexing.html#detailed-notes + + row, col = frame_shape + + frame_int = np.empty((row + 1, col + 1), dtype=frame_int_dtype) + + w_arr = np.arange(width_min, width_max + 1, wh_step, dtype=np_index_dtype) + h_arr = (w_arr / ratio_outer).astype(np.int16) + + # memo: It is not smart code and needs to be changed. + y_out_n = np.hstack([np.arange(roi[1] + h, roi[3] - h, xy_step, dtype=np_index_dtype) for h in h_arr]) + x_out_n = np.hstack([np.arange(roi[0] + w, roi[2] - w, xy_step, dtype=np_index_dtype) for w in w_arr]) + y_out_h = np.hstack([np.arange(roi[1] + h, roi[3] - h, xy_step, dtype=np_index_dtype) + h for h in h_arr]) + x_out_w = np.hstack([np.arange(roi[0] + w, roi[2] - w, xy_step, dtype=np_index_dtype) + w for w in w_arr]) + out_h = y_out_h - y_out_n + out_w = x_out_w - x_out_n + + y_in_n = np.hstack([np.arange(roi[1] + h, roi[3] - h, xy_step, dtype=np_index_dtype) + int(h / 4) for h in h_arr]) + x_in_n = np.hstack([np.arange(roi[0] + w, roi[2] - w, xy_step, dtype=np_index_dtype) + int(w / 4) for w in w_arr]) + y_in_h = np.hstack([np.arange(roi[1] + h, roi[3] - h, xy_step, dtype=np_index_dtype) + int(h / 4) + int(h / 2) for h in h_arr]) + x_in_w = np.hstack([np.arange(roi[0] + w, roi[2] - w, xy_step, dtype=np_index_dtype) + int(w / 4) + int(w / 2) for w in w_arr]) + in_h = y_in_h - y_in_n + in_w = x_in_w - x_in_n + + wh_in_arr = ( + np.hstack( + [ + np.full( + ((roi[2] - w) - (roi[0] + w) - 1) // xy_step + 1, + int(w / 2), + dtype=np_index_dtype, + ) + for w in w_arr + ] + )[:, np.newaxis] + * np.hstack( + [ + np.full( + ((roi[3] - h) - (roi[1] + h) - 1) // xy_step + 1, + int(h / 2), + dtype=np_index_dtype, + ) + for h in h_arr + ] + )[np.newaxis, :] + ) + wh_out_arr = ( + np.hstack( + [ + np.full( + ((roi[2] - w) - (roi[0] + w) - 1) // xy_step + 1, + w, + dtype=np_index_dtype, + ) + for w in w_arr + ] + )[:, np.newaxis] + * np.hstack( + [ + np.full( + ((roi[3] - h) - (roi[1] + h) - 1) // xy_step + 1, + h, + dtype=np_index_dtype, + ) + for h in h_arr + ] + )[np.newaxis, :] + ) + + mu_outer_rect = cv2.subtract(wh_out_arr, wh_in_arr) # ,dst=) # == (outer_rect[2] * outer_rect[3] - inner_rect[2] * inner_rect[3]) + + wh_in_arr = 1 / wh_in_arr # .astype(np.float32) + # wh_out_arr=wh_out_arr.astype(np.float64) + mu_outer_rect = 1 / mu_outer_rect.astype(np.float32) + mu_outer_rect2 = -1.0 * mu_outer_rect # cv2.merge([mu_outer_rect,-1.0*mu_outer_rect]) + + # 1/wh_in_arr == wh_in_arr_mul + return ( + frame_int, + y_out_n, + x_out_n, + y_out_h, + x_out_w, + out_h, + out_w, + y_in_n, + x_in_n, + y_in_h, + x_in_w, + in_h, + in_w, + wh_in_arr, + wh_out_arr, + mu_outer_rect, + mu_outer_rect2, + ) + + +def coarse_detection(img_gray, params): + ratio_outer = params["ratio_outer"] + kf = params["kf"] + width_min = params["width_min"] + width_max = params["width_max"] + wh_step = params["wh_step"] + xy_step = params["xy_step"] + roi = params["roi"] + init_rect_flag = params["init_rect_flag"] + init_rect = params["init_rect"] + mu_inner = params["mu_inner"] + mu_outer = params["mu_outer"] + max_response_coarse = -255 + + imgboundary = (0, 0, img_gray.shape[1], img_gray.shape[0]) + img_blur = np.copy(img_gray) + + # Assign values to avoid unassigned errors + pupil_rect_coarse = (10, 10, 10, 10) + outer_rect_coarse = (5, 5, 5, 5) + + if init_rect_flag: + init_rect_down = rect_scale(init_rect, params["ratio_downsample"], False) + init_rect_down = intersect_rect(init_rect_down, imgboundary) + img_blur = img_gray[ + init_rect_down[1] : init_rect_down[1] + init_rect_down[3], + init_rect_down[0] : init_rect_down[0] + init_rect_down[2], + ] + + ( + frame_int, + y_out_n, + x_out_n, + y_out_h, + x_out_w, + out_h, + out_w, + y_in_n, + x_in_n, + y_in_h, + x_in_w, + in_h, + in_w, + wh_in_arr, + wh_out_arr, + mu_outer_rect, + mu_outer_rect2, + ) = get_empty_array(img_blur.shape, width_min, width_max, wh_step, xy_step, roi, ratio_outer) + cv2.integral( + img_blur, sum=frame_int, sdepth=cv2.CV_32S + ) # memo: It becomes slower when using float64, probably because the increase in bits from 32 to 64 causes the arrays to be larger + + # memo: If axis=1 is too slow, just transpose and "take" with axis=0. + # memo: This URL gave me an idea. https://numpy.org/doc/1.25/dev/internals.html#multidimensional-array-indexing-order-issues + out_p_temp = frame_int.take(y_out_n, axis=0, mode="clip") # , out=out_p_temp) + out_p_temp = cv2.transpose(out_p_temp) + out_p00 = out_p_temp.take(x_out_n, axis=0, mode="clip") # , out=out_p00) + # p01 calc + out_p01 = out_p_temp.take(x_out_w, axis=0, mode="clip") # , out=out_p01) + # p11 calc + out_p_temp = frame_int.take(y_out_h, axis=0, mode="clip") # , out=out_p_temp) + out_p_temp = cv2.transpose(out_p_temp) + out_p11 = out_p_temp.take(x_out_w, axis=0, mode="clip") # , out=out_p11) + # p10 calc + out_p10 = out_p_temp.take(x_out_n, axis=0, mode="clip") # , out=out_p10) + + # outer_sum[:, :] = out_p00 + out_p11 - out_p01 - out_p10 + outer_sum = cv2.add(out_p00, out_p11) # , dst=outer_sum) + cv2.subtract(outer_sum, out_p01, dst=outer_sum) + cv2.subtract(outer_sum, out_p10, dst=outer_sum) + + in_p_temp = frame_int.take(y_in_n, axis=0, mode="clip") # , out=in_p_temp) + + in_p_temp = cv2.transpose(in_p_temp) + in_p00 = in_p_temp.take(x_in_n, axis=0, mode="clip") # , out=in_p00) + # p01 calc + in_p01 = in_p_temp.take(x_in_w, axis=0, mode="clip") # , out=in_p01) + # p11 calc + in_p_temp = frame_int.take(y_in_h, axis=0, mode="clip") # , out=in_p_temp) + in_p_temp = cv2.transpose(in_p_temp) + in_p11 = in_p_temp.take(x_in_w, axis=0, mode="clip") # , out=in_p11) + # p10 calc + in_p10 = in_p_temp.take(x_in_n, axis=0, mode="clip") # , out=in_p10) + + inner_sum = cv2.add(in_p00, in_p11) + cv2.subtract(inner_sum, in_p01, dst=inner_sum) + cv2.subtract(inner_sum, in_p10, dst=inner_sum) + + # memo: Multiplication, etc. can be faster by self-assignment, but care must be taken because array initialization is required. + # https://stackoverflow.com/questions/71204415/opencv-python-fastest-way-to-multiply-pixel-value + inner_sum_f = np.empty(inner_sum.shape, dtype=np.float64) + inner_sum_f[:, :] = inner_sum + outer_sum_f = np.empty(outer_sum.shape, dtype=np.float64) + outer_sum_f[:, :] = outer_sum + + response_value = np.empty(outer_sum.shape, dtype=np.float64) + inout_rect_sum = mu_outer_rect2.copy() + inout_rect_mul = mu_outer_rect.copy() + + cv2.multiply(inner_sum_f, inout_rect_mul, inout_rect_mul) + cv2.multiply(outer_sum_f, inout_rect_sum, inout_rect_sum) + cv2.add(inout_rect_mul, inout_rect_sum, dst=inout_rect_sum) + + cv2.multiply(inner_sum_f, wh_in_arr, inner_sum_f, kf) + cv2.add(inout_rect_sum, inner_sum_f, dst=response_value) + + # memo: The input image is transposed, so the coordinate output of this function has x and y swapped. + min_response, max_response, min_loc, max_loc = cv2.minMaxLoc(response_value) + + # The sign is reversed from the original calculation result, so using min. + rec_o = ( + x_out_n[min_loc[1]], + y_out_n[min_loc[0]], + out_w[min_loc[1]], + out_h[min_loc[0]], + ) + rec_in = ( + x_in_n[min_loc[1]], + y_in_n[min_loc[0]], + in_w[min_loc[1]], + in_h[min_loc[0]], + ) + max_response_coarse = -min_response # type: ignore[assignment] + pupil_rect_coarse = rec_in + outer_rect_coarse = rec_o + + return pupil_rect_coarse, outer_rect_coarse, max_response_coarse, mu_inner, mu_outer + + +def fine_detection(frame, pupil_rect_coarse): + valid_ratio = 1.2 + boundary = (0, 0, frame.shape[1], frame.shape[0]) + valid_rect = intersect_rect(rect_scale(pupil_rect_coarse, valid_ratio), boundary) + img_pupil = frame[ + valid_rect[1] : valid_rect[1] + valid_rect[3], + valid_rect[0] : valid_rect[0] + valid_rect[2], + ] + img_pupil_blur = cv2.GaussianBlur(img_pupil, (5, 5), 0, 0) + edges_filter = detect_edges(img_pupil_blur) + # fit ellipse to edges + contours, hierarchy = cv2.findContours(edges_filter, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) + # sort contours by area + contours = sorted(contours, key=lambda x: cv2.contourArea(x), reverse=True) + # fit ellipse to largest contour + try: + if len(contours) > 0 and len(contours[0]) >= 5: + pupil_contour = contours[0] + pupil_ellipse = cv2.fitEllipse(pupil_contour) + center_fitting = ( + int(pupil_ellipse[0][0] + valid_rect[0]), + int(pupil_ellipse[0][1] + valid_rect[1]), + ) + pupil_rect_fine = ( + int(pupil_ellipse[0][0] - pupil_ellipse[1][0] / 2), + int(pupil_ellipse[0][1] - pupil_ellipse[1][1] / 2), + int(pupil_ellipse[1][0]), + int(pupil_ellipse[1][1]), + ) + pupil_rect_fine = ( + pupil_rect_fine[0] + valid_rect[0], + pupil_rect_fine[1] + valid_rect[1], + pupil_rect_fine[2], + pupil_rect_fine[3], + ) + pupil_rect_fine = intersect_rect(pupil_rect_fine, boundary) + pupil_rect_fine = rect_scale(pupil_rect_fine, 1 / valid_ratio) + else: + pupil_rect_fine = pupil_rect_coarse + center_fitting = ( + int(pupil_rect_fine[0] + pupil_rect_fine[2] / 2), + int(pupil_rect_fine[1] + pupil_rect_fine[3] / 2), + ) + return pupil_rect_fine, center_fitting + except Exception: + center = (pupil_rect_coarse[0] + pupil_rect_coarse[2] / 2, pupil_rect_coarse[1] + pupil_rect_coarse[3] / 2) + return pupil_rect_coarse, center + + +def detect_edges(img_pupil_blur): + edges = cv2.Canny(img_pupil_blur, 64, 128) + + # img_bw = np.zeros_like(img_pupil_blur) + # img_bw[img_pupil_blur > 100] = 255 + img_bw = cv2.compare(img_pupil_blur, 100, cv2.CMP_GT) + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) + img_bw = cv2.dilate(img_bw, kernel) + + # edges_filter = edges & (~img_bw) + # or + edges_filter = cv2.bitwise_and(edges, cv2.bitwise_not(img_bw)) + return edges_filter + + +def rect_scale(rect, scale, round_up=True): + x, y, width, height = rect + new_width = int(width * scale) + new_height = int(height * scale) + if round_up: + new_width = int(np.ceil(width * scale)) + new_height = int(np.ceil(height * scale)) + new_x = x + int((width - new_width) / 2) + new_y = y + int((height - new_height) / 2) + return new_x, new_y, new_width, new_height + + +def intersect_rect(rect1, rect2): + x1, y1, w1, h1 = rect1 + x2, y2, w2, h2 = rect2 + x = max(x1, x2) + y = max(y1, y2) + w = min(x1 + w1, x2 + w2) - x + h = min(y1 + h1, y2 + h2) - y + return x, y, w, h diff --git a/TrackingBackend/app/algorithms/blob.py b/eyetrackvr_backend/algorithms/blob.py similarity index 87% rename from TrackingBackend/app/algorithms/blob.py rename to eyetrackvr_backend/algorithms/blob.py index 29ebb5e..a5f5ae1 100644 --- a/TrackingBackend/app/algorithms/blob.py +++ b/eyetrackvr_backend/algorithms/blob.py @@ -29,16 +29,16 @@ import cv2 from cv2.typing import MatLike -from app.utils import BaseAlgorithm -from app.processes import EyeProcessor -from app.types import EyeData, TRACKING_FAILED +from ..utils import BaseAlgorithm +from ..processes import EyeProcessor +from ..types import EyeData, TrackerPosition, TRACKING_FAILED class Blob(BaseAlgorithm): def __init__(self, eye_processor: EyeProcessor): self.ep = eye_processor - def run(self, frame: MatLike) -> EyeData: + def run(self, frame: MatLike, tracker_position: TrackerPosition) -> tuple[EyeData, MatLike]: _, larger_threshold = cv2.threshold(frame, self.ep.config.blob.threshold, 255, cv2.THRESH_BINARY) try: @@ -49,10 +49,10 @@ def run(self, frame: MatLike) -> EyeData: # If we have no contours, we have nothing to blob track. Fail here. if len(contours) == 0: self.ep.logger.warning(f"Failed to find any contours for {self.ep.tracker_position.name}") - return TRACKING_FAILED + return TRACKING_FAILED, frame except (cv2.error, Exception): self.ep.logger.exception("Something went wrong!") - return TRACKING_FAILED + return TRACKING_FAILED, frame for cnt in contours: (x, y, w, h) = cv2.boundingRect(cnt) @@ -71,6 +71,6 @@ def run(self, frame: MatLike) -> EyeData: cv2.drawContours(frame, [cnt], -1, (0, 255, 0), 3) cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2) - return EyeData(x, y, 1, self.ep.tracker_position) + return EyeData(x, y, 1, tracker_position), frame - return TRACKING_FAILED + return TRACKING_FAILED, frame diff --git a/eyetrackvr_backend/algorithms/hsf.py b/eyetrackvr_backend/algorithms/hsf.py new file mode 100644 index 0000000..590a7f8 --- /dev/null +++ b/eyetrackvr_backend/algorithms/hsf.py @@ -0,0 +1,647 @@ +""" +------------------------------------------------------------------------------------------------------ + + ,@@@@@@ + @@@@@@@@@@@ @@@ + @@@@@@@@@@@@ @@@@@@@@@@@ + @@@@@@@@@@@@@ @@@@@@@@@@@@@@ + @@@@@@@/ ,@@@@@@@@@@@@@ + /@@@@@@@@@@@@@@@ @@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@ + @@@@@@@@ @@@@@ + ,@@@ @@@@& + @@@@@@. @@@@ + @@@ @@@@@@@@@/ @@@@@ + ,@@@. @@@@@@((@ @@@@( + //@@@ ,, @@@@ @@@@@ + @@@( @@@@@@@ + @@@ @ @@@@@@@@# + @@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@( + +Haar Surround Feature by: Summer +Algorithm App Implementation By: Prohurtz, PallasNeko, RamesTheGeneric, ShyAssassin + +Copyright (c) 2024 EyeTrackVR <3 +This project is licensed under the MIT License. See LICENSE for more details. +------------------------------------------------------------------------------------------------------ +""" + +# TODO: things we should do +# 1. Add type hints to all functions +# 2. Simplify mainloop logic? +# 3. Smoothing between blinks +# 4. Fix the very rare bug related to weird image shapes + +import cv2 +import numpy as np +from enum import Enum +from copy import deepcopy +from functools import lru_cache +from cv2.typing import MatLike, Point +from ..processes import EyeProcessor +from ..utils import BaseAlgorithm, safe_crop +from ..types import EyeData, TrackerPosition, TRACKING_FAILED + + +# cache param +lru_maxsize_vvs = 16 +lru_maxsize_vs = 64 +lru_maxsize_s = 128 +# CV param +default_radius = 20 +auto_radius_range = (default_radius - 18, default_radius + 15) # (10,30) +auto_radius_step = 1 + + +class CVMode(Enum): + FIRST_FRAME = 0 + RADIUS_ADJUST = 1 + BLINK_ADJUST = 2 + NORMAL = 3 + + +class HSF(BaseAlgorithm): + def __init__(self, eye_processor: EyeProcessor): + self.ep = eye_processor + self.mode = CVMode.FIRST_FRAME + self.center_q1 = BlinkDetector() + self.blink_detector = BlinkDetector() + self.auto_radius_calc = AutoRadiusCalc() + self.center_correct = CenterCorrection() + self.cvparam = CvParameters(default_radius, self.ep.config.hsf.default_step) + + # TODO: i would like to split this into smaller functions + def run(self, frame: MatLike, tracker_position: TrackerPosition) -> tuple[EyeData, MatLike]: + # adjustment of radius + if self.mode == CVMode.RADIUS_ADJUST: + self.cvparam.radius = self.auto_radius_calc.get_radius() + if self.auto_radius_calc.adj_comp_flag: + self.ep.logger.info(f"Auto Radius Complete: {self.cvparam.radius}") + self.mode = CVMode.BLINK_ADJUST if not self.ep.config.hsf.skip_blink_detection else CVMode.NORMAL + + radius, pad, step, hsf = self.cvparam.get_rpsh() + # Calculate the integral image of the frame + ( + frame_pad, + frame_int, + inner_sum, + in_p00, + in_p11, + in_p01, + in_p10, + y_ro_m, + x_ro_m, + y_ro_p, + x_ro_p, + outer_sum, + out_p_temp, + out_p00, + out_p11, + out_p01, + out_p10, + response_list, + frame_conv, + frame_conv_stride, + ) = get_frameint_empty_array(frame.shape, pad, step[0], step[1], hsf.r_in, hsf.r_out) + # BORDER_CONSTANT is faster than BORDER_REPLICATE There seems to be almost no negative impact when BORDER_CONSTANT is used. + cv2.copyMakeBorder(frame, pad, pad, pad, pad, cv2.BORDER_CONSTANT, dst=frame_pad) + cv2.integral(frame_pad, sum=frame_int, sdepth=cv2.CV_32S) + + # Convolve the feature with the integral image + response, hsf_min_loc = conv_int( + frame_int, + hsf, + inner_sum, + in_p00, + in_p11, + in_p01, + in_p10, + y_ro_m, + x_ro_m, + y_ro_p, + x_ro_p, + outer_sum, + out_p_temp, + out_p00, + out_p11, + out_p01, + out_p10, + response_list, + frame_conv_stride, + ) + + # Define the center point and radius + center_x, center_y = get_hsf_center(pad, step[0], step[1], hsf_min_loc) + upper_x = center_x + radius + lower_x = center_x - radius + upper_y = center_y + radius + lower_y = center_y - radius + + # Crop the image using the calculated bounds + cropped_image = safe_crop(frame, lower_x, lower_y, upper_x, upper_y) + if 0 in cropped_image.shape: + self.ep.logger.error("Cropped image has bad dimensions, skipping frame.") + return TRACKING_FAILED, frame + + blink = 1 + match self.mode: + case CVMode.NORMAL: + orig_x, orig_y = deepcopy((center_x, center_y)) + if not self.blink_detector.detect(cv2.mean(cropped_image)[0]): + # The resolution should have changed and the statistics should have changed, so essentially the statistics + # need to be reworked, but implementation will be postponed as viability is the highest priority + if not self.center_correct.setup_comp: + self.center_correct.init_array(frame, self.center_q1.quartile_1) + elif self.center_correct.frame_shape != frame.shape: + self.center_correct.init_array(frame, self.center_q1.quartile_1) + center_x, center_y = self.center_correct.correction(frame, center_x, center_y) + else: + # FIXME: since this is binary blink we should use a smoothing function to avoid flickering from false negatives + blink = 0 + + cv2.circle(frame, (orig_x, orig_y), 6, (0, 0, 255), -1) + case CVMode.BLINK_ADJUST: # We dont have enough frames yet, gather more data + if self.blink_detector.response_len() < self.ep.config.hsf.blink_stat_frames: + lower_x = center_x - max(20, radius) + lower_y = center_y - max(20, radius) + upper_x = center_x + max(20, radius) + upper_y = center_y + max(20, radius) + + self.blink_detector.add_response(cv2.mean(cropped_image)[0]) + self.center_q1.add_response( + cv2.mean( + safe_crop( + frame, + lower_x, + lower_y, + upper_x, + upper_y, + keepsize=False, + ) + )[0] + ) + else: + self.mode = CVMode.NORMAL + self.center_q1.calc_thresh() + self.blink_detector.calc_thresh() + self.ep.logger.info("Blink Adjust Complete") + case CVMode.FIRST_FRAME | CVMode.RADIUS_ADJUST: # record current radius and response + self.auto_radius_calc.add_response(radius, response) + case _: + self.ep.logger.error(f"Invalid mode: {self.mode}") + cv2.circle(frame, (center_x, center_y), 3, (255, 0, 0), -1) + + # Moving from first_frame to the next mode + if self.mode == CVMode.FIRST_FRAME: + self.ep.logger.info("First frame complete") + if self.ep.config.hsf.skip_autoradius and self.ep.config.hsf.skip_blink_detection: + self.mode = CVMode.NORMAL + self.ep.logger.info("Skipping autoradius and blink adjust") + elif self.ep.config.hsf.skip_autoradius: + self.mode = CVMode.BLINK_ADJUST + self.ep.logger.info("Skipping autoradius") + else: + self.mode = CVMode.RADIUS_ADJUST + self.ep.logger.info("Starting autoradius") + + # FIXME: this seems correct, but isnt as sensitive as it should be + # Maybe callibration / ROI cropping plays a role in this? + x = center_x / frame.shape[1] + y = center_y / frame.shape[0] + + return EyeData(x, y, blink, tracker_position), frame + + +# If you want to update response_max. it may be more cost-effective to rewrite response_list in the following way +# https://stackoverflow.com/questions/42771110/fastest-way-to-left-cycle-a-numpy-array-like-pop-push-for-a-queue +class BlinkDetector: + def __init__(self): + self.quartile_1: float = 0.0 + self.response_max: float = 0.0 + self.response_list: list[float] = [] + + def calc_thresh(self): + quartile_1, quartile_3 = np.percentile(np.array(self.response_list), [25, 75]) + self.quartile_1 = quartile_1 + iqr = quartile_3 - quartile_1 + self.response_max = float(quartile_3 + (iqr * 1.5)) + + def detect(self, now_response: float) -> bool: + return now_response > self.response_max + + def add_response(self, response: float): + self.response_list.append(response) + + def response_len(self) -> int: + return len(self.response_list) + + +# What in the name of god is this? +class CvParameters: + # It may be a little slower because a dict named "self" is read for each function call. + def __init__(self, radius: int, step: tuple[int, int]): + self._radius = radius + self.pad = 2 * radius + self._step = step + self._hsf = HaarSurroundFeature(radius) + + def get_rpsh(self): + return self._radius, self.pad, self._step, self._hsf + # Essentially, the following would be preferable, but it would take twice as long to call. + # return self.radius, self.pad, self.step, self.hsf + + @property + def radius(self) -> int: + return self._radius + + @radius.setter + def radius(self, now_radius: int): + self._radius = now_radius + self.pad = 2 * now_radius + self.hsf = now_radius + + @property + def step(self) -> tuple[int, int]: + return self._step + + @step.setter + def step(self, now_step: tuple[int, int]): + self._step = now_step + + @property + def hsf(self): + return self._hsf + + @hsf.setter + def hsf(self, now_radius: int): + self._hsf = HaarSurroundFeature(now_radius) + + +class HaarSurroundFeature: + def __init__(self, r_inner, r_outer=None, val=None): + if r_outer is None: + r_outer = r_inner * 3 + r_inner2 = r_inner * r_inner + count_inner = r_inner2 + count_outer = r_outer * r_outer - r_inner2 + + if val is None: + val_inner = 1.0 / r_inner2 + val_outer = -val_inner * count_inner / count_outer + + else: + val_inner = val[0] + val_outer = val[1] + + self.val_in = float(val_inner) + self.val_out = float(val_outer) + self.r_in = r_inner + self.r_out = r_outer + + def get_kernel(self): + # Defined here, but not yet used? + # Create a kernel filled with the value of self.val_out + kernel = np.ones(shape=(2 * self.r_out - 1, 2 * self.r_out - 1), dtype=np.float64) * self.val_out + + # Set the values of the inner area of the kernel using array slicing + start = self.r_out - self.r_in + end = self.r_out + self.r_in - 1 + kernel[start:end, start:end] = self.val_in + + return kernel + + +class AutoRadiusCalc: + def __init__(self): + self.response_list = [] + self.radius_cand_list = [] + self.adj_comp_flag = False + + # self.radius_middle_index = None + + # self.left_item = None + # self.right_item = None + # self.left_index = None + # self.right_index = None + + def get_radius(self) -> int: + prev_res_len = len(self.response_list) + # adjustment of radius + if prev_res_len == 1: + self.adj_comp_flag = False + return auto_radius_range[0] + elif prev_res_len == 2: + self.adj_comp_flag = False + return auto_radius_range[1] + elif prev_res_len == 3: + if self.response_list[1][1] < self.response_list[2][1]: + self.left_item = self.response_list[1] + self.right_item = self.response_list[0] + else: + self.left_item = self.response_list[0] + self.right_item = self.response_list[2] + self.radius_cand_list = [ + i + for i in range( + self.left_item[0], + self.right_item[0] + auto_radius_step, + auto_radius_step, + ) + ] + self.left_index = 0 + self.right_index: int = len(self.radius_cand_list) - 1 + self.radius_middle_index = (self.left_index + self.right_index) // 2 + self.adj_comp_flag = False + return self.radius_cand_list[self.radius_middle_index] + else: + if self.left_index <= self.right_index and self.left_index != self.radius_middle_index: + if (self.left_item[1] + self.response_list[-1][1]) < (self.right_item[1] + self.response_list[-1][1]): + self.right_item = self.response_list[-1] + self.right_index = self.radius_middle_index - 1 + self.radius_middle_index = (self.left_index + self.right_index) // 2 + self.adj_comp_flag = False + return self.radius_cand_list[self.radius_middle_index] + if (self.left_item[1] + self.response_list[-1][1]) > (self.right_item[1] + self.response_list[-1][1]): + self.left_item = self.response_list[-1] + self.left_index = self.radius_middle_index + 1 + self.radius_middle_index = (self.left_index + self.right_index) // 2 + self.adj_comp_flag = False + return self.radius_cand_list[self.radius_middle_index] + self.adj_comp_flag = True + return self.radius_cand_list[self.radius_middle_index] + + def get_radius_base(self) -> int: + """ + Use it when the new version doesn't work well. + :return: + """ + + prev_res_len = len(self.response_list) + # adjustment of radius + if prev_res_len == 1: + self.adj_comp_flag = False + return auto_radius_range[0] + elif prev_res_len == 2: + self.adj_comp_flag = False + return auto_radius_range[1] + elif prev_res_len == 3: + sort_res = sorted(self.response_list, key=lambda x: x[1])[0] + # Extract the radius with the lowest response value + if sort_res[0] == default_radius: + # If the default value is best, change now_mode to init after setting radius to the default value. + self.adj_comp_flag = True + return default_radius + elif sort_res[0] == auto_radius_range[0]: + self.radius_cand_list = [i for i in range(auto_radius_range[0], default_radius, auto_radius_step)][1:] + self.adj_comp_flag = False + return self.radius_cand_list.pop() + else: + self.radius_cand_list = [i for i in range(default_radius, auto_radius_range[1], auto_radius_step)][1:] + self.adj_comp_flag = False + return self.radius_cand_list.pop() + else: + # Try the contents of the radius_cand_list in order until the radius_cand_list runs out + # Better make it a binary search. + if len(self.radius_cand_list) == 0: + sort_res = sorted(self.response_list, key=lambda x: x[1])[0] + self.adj_comp_flag = True + return sort_res[0] + else: + self.adj_comp_flag = False + return self.radius_cand_list.pop() + + def add_response(self, radius, response): + self.response_list.append((radius, response)) + + +class CenterCorrection: + def __init__(self): + # Tunable parameters + kernel_size = 7 # 3 or 5 or 7 + self.hist_thr = float(4) # 4% + self.center_q1_radius = 20 + + self.setup_comp = False + # self.quartile_1 = None + # self.frame_shape = None + # self.frame_mask = None + # self.frame_bin = None + # self.frame_final = None + self.morph_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_size, kernel_size)) + self.morph_kernel2 = np.ones((3, 3)) + self.hist_index = np.arange(256) + self.hist = np.empty((256, 1)) + self.hist_norm = np.empty((256, 1)) + + def init_array(self, frame: MatLike, quartile_1): + self.frame_shape = frame.shape + self.frame_mask = np.empty(frame.shape, dtype=np.uint8) + self.frame_bin = np.empty(frame.shape, dtype=np.uint8) + self.frame_final: np.ndarray = np.empty(frame.shape, dtype=np.uint8) + self.quartile_1 = quartile_1 + self.setup_comp = True + + def correction(self, frame: MatLike, orig_x: int, orig_y: int) -> tuple[int, int]: + center_x, center_y = orig_x, orig_y + self.frame_mask.fill(0) + + # bottleneck + cv2.calcHist([frame], [0], None, [256], [0, 256], hist=self.hist) + + cv2.normalize(self.hist, self.hist_norm, alpha=100.0, norm_type=cv2.NORM_L1) + hist_per = self.hist_norm.cumsum() + hist_index_list = self.hist_index[hist_per >= self.hist_thr] + bitwise: np.ndarray = cv2.bitwise_or(255 - self.frame_mask, frame) + frame_thr = float(hist_index_list[0] if len(hist_index_list) else np.percentile(bitwise, 4)) + + # bottleneck + self.frame_bin = cv2.threshold(frame, frame_thr, 1, cv2.THRESH_BINARY_INV)[1] # type: ignore[assignment] + cropped_x, cropped_y, cropped_w, cropped_h = cv2.boundingRect(self.frame_bin) + + self.frame_final = cv2.bitwise_and(self.frame_bin, self.frame_mask) + + # bottleneck + self.frame_finalcv: np.ndarray = cv2.morphologyEx(self.frame_final, cv2.MORPH_CLOSE, self.morph_kernel) + self.frame_final = cv2.morphologyEx(self.frame_final, cv2.MORPH_OPEN, self.morph_kernel) + + if not self.frame_shape == (cropped_h, cropped_w): + base_x = cropped_x + cropped_w // 2 + base_y = cropped_y + cropped_h // 2 + if self.frame_final[base_y, base_x] != 1: + if self.frame_final[center_y, center_x] != 1: + self.frame_final = np.ndarray( + cv2.morphologyEx( + self.frame_final, + cv2.MORPH_DILATE, + self.morph_kernel2, + iterations=3, + ), # type: ignore[reportArgumentType] + dtype=np.uint8, + ) + else: + base_x, base_y = center_x, center_y + else: + # Not detected. + base_x, base_y = center_x, center_y + + contours, _ = cv2.findContours(self.frame_final, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) + contours_box = [cv2.boundingRect(cnt) for cnt in contours] + contours_dist = np.array( + [abs(base_x - (cnt_x + cnt_w / 2)) + abs(base_y - (cnt_y + cnt_h / 2)) for cnt_x, cnt_y, cnt_w, cnt_h in contours_box] + ) + + if len(contours_box): + cropped_x2, cropped_y2, cropped_w2, cropped_h2 = contours_box[contours_dist.argmin()] + x = cropped_x2 + cropped_w2 // 2 + y = cropped_y2 + cropped_h2 // 2 + else: + x = center_x + y = center_y + + out_x, out_y = orig_x, orig_y + + if ( + frame[ + int(max(y - 5, 0)) : int(min(y + 5, self.frame_shape[0])), + int(max(x - 5, 0)) : int(min(x + 5, self.frame_shape[1])), + ].min() + < self.quartile_1 + ): + out_x = x + out_y = y + return out_x, out_y + + +@lru_cache(maxsize=lru_maxsize_vvs) +def get_frameint_empty_array(frame_shape, pad, x_step, y_step, r_in, r_out): + frame_int_dtype = np.intc + frame_pad = np.empty((frame_shape[0] + (pad * 2), frame_shape[1] + (pad * 2)), dtype=np.uint8) + + row, col = frame_pad.shape + + frame_int = np.empty((row + 1, col + 1), dtype=frame_int_dtype) + + y_steps_arr = np.arange(pad, row - pad, y_step, dtype=np.int16) + x_steps_arr = np.arange(pad, col - pad, x_step, dtype=np.int16) + len_sx, len_sy = len(x_steps_arr), len(y_steps_arr) + len_syx = (len_sy, len_sx) + y_end = pad + (y_step * (len_sy - 1)) + x_end = pad + (x_step * (len_sx - 1)) + + y_rin_m = slice(pad - r_in, y_end - r_in + 1, y_step) + y_rin_p = slice(pad + r_in, y_end + r_in + 1, y_step) + x_rin_m = slice(pad - r_in, x_end - r_in + 1, x_step) + x_rin_p = slice(pad + r_in, x_end + r_in + 1, x_step) + + in_p00 = frame_int[y_rin_m, x_rin_m] + in_p11 = frame_int[y_rin_p, x_rin_p] + in_p01 = frame_int[y_rin_m, x_rin_p] + in_p10 = frame_int[y_rin_p, x_rin_m] + + y_ro_m = np.maximum(y_steps_arr - r_out, 0) # [:,np.newaxis] + x_ro_m = np.maximum(x_steps_arr - r_out, 0) # [np.newaxis,:] + y_ro_p = np.minimum(row, y_steps_arr + r_out) # [:,np.newaxis] + x_ro_p = np.minimum(col, x_steps_arr + r_out) # [np.newaxis,:] + + inner_sum = np.empty(len_syx, dtype=frame_int_dtype) + outer_sum = np.empty(len_syx, dtype=frame_int_dtype) + + out_p_temp = np.empty((len_sy, col + 1), dtype=frame_int_dtype) + out_p00 = np.empty(len_syx, dtype=frame_int_dtype) + out_p11 = np.empty(len_syx, dtype=frame_int_dtype) + out_p01 = np.empty(len_syx, dtype=frame_int_dtype) + out_p10 = np.empty(len_syx, dtype=frame_int_dtype) + response_list = np.empty(len_syx, dtype=np.float64) # or np.int32 + frame_conv = np.zeros(shape=(row - 2 * pad, col - 2 * pad), dtype=np.uint8) # or np.float64 + frame_conv_stride = frame_conv[::y_step, ::x_step] + + return ( + frame_pad, + frame_int, + inner_sum, + in_p00, + in_p11, + in_p01, + in_p10, + y_ro_m, + x_ro_m, + y_ro_p, + x_ro_p, + outer_sum, + out_p_temp, + out_p00, + out_p11, + out_p01, + out_p10, + response_list, + frame_conv, + frame_conv_stride, + ) + + +def conv_int( + frame_int, + kernel, + inner_sum, + in_p00, + in_p11, + in_p01, + in_p10, + y_ro_m, + x_ro_m, + y_ro_p, + x_ro_p, + outer_sum, + out_p_temp, + out_p00, + out_p11, + out_p01, + out_p10, + response_list, + frame_conv_stride, +) -> tuple[float, Point]: + # inner_sum[:, :] = in_p00 + in_p11 - in_p01 - in_p10 + cv2.add(in_p00, in_p11, dst=inner_sum) + cv2.subtract(inner_sum, in_p01, dst=inner_sum) + cv2.subtract(inner_sum, in_p10, dst=inner_sum) + + # p00 calc + frame_int.take(y_ro_m, axis=0, mode="clip", out=out_p_temp) + out_p_temp.take(x_ro_m, axis=1, mode="clip", out=out_p00) + # p01 calc + out_p_temp.take(x_ro_p, axis=1, mode="clip", out=out_p01) + # p11 calc + frame_int.take(y_ro_p, axis=0, mode="clip", out=out_p_temp) + out_p_temp.take(x_ro_p, axis=1, mode="clip", out=out_p11) + # p10 calc + out_p_temp.take(x_ro_m, axis=1, mode="clip", out=out_p10) + + # outer_sum[:, :] = out_p00 + out_p11 - out_p01 - out_p10 - inner_sum + cv2.add(out_p00, out_p11, dst=outer_sum) + cv2.subtract(outer_sum, out_p01, dst=outer_sum) + cv2.subtract(outer_sum, out_p10, dst=outer_sum) + cv2.subtract(outer_sum, inner_sum, dst=outer_sum) + cv2.addWeighted( + inner_sum, + kernel.val_in, + outer_sum, # or p00 + p11 - p01 - p10 - inner_sum + kernel.val_out, + 0.0, + dtype=cv2.CV_64F, # or cv2.CV_32S + dst=response_list, + ) + + min_response, _, min_loc, _ = cv2.minMaxLoc(response_list) + + frame_conv_stride[:, :] = response_list + + return min_response, min_loc + + +@lru_cache(maxsize=lru_maxsize_s) +def get_hsf_center(padding, x_step, y_step, min_loc) -> tuple[int, int]: + return ( + padding + (x_step * min_loc[0]) - padding, + padding + (y_step * min_loc[1]) - padding, + ) diff --git a/TrackingBackend/app/algorithms/hsrac.py b/eyetrackvr_backend/algorithms/hsrac.py similarity index 60% rename from TrackingBackend/app/algorithms/hsrac.py rename to eyetrackvr_backend/algorithms/hsrac.py index 5676c0d..47fa22e 100644 --- a/TrackingBackend/app/algorithms/hsrac.py +++ b/eyetrackvr_backend/algorithms/hsrac.py @@ -1,5 +1,5 @@ -from app.processes import EyeProcessor -from app.utils import BaseAlgorithm +from ..processes import EyeProcessor +from ..utils import BaseAlgorithm class HSRAC(BaseAlgorithm): diff --git a/TrackingBackend/app/algorithms/leap.py b/eyetrackvr_backend/algorithms/leap.py similarity index 90% rename from TrackingBackend/app/algorithms/leap.py rename to eyetrackvr_backend/algorithms/leap.py index a21127e..c120562 100644 --- a/TrackingBackend/app/algorithms/leap.py +++ b/eyetrackvr_backend/algorithms/leap.py @@ -27,22 +27,27 @@ ------------------------------------------------------------------------------------------------------ """ +import os import cv2 import math import numpy as np import onnxruntime as rt from typing import Final -from app.types import EyeData from cv2.typing import MatLike -from app.processes import EyeProcessor -from app.utils import BaseAlgorithm, OneEuroFilter + +from eyetrackvr_backend.assets import MODELS_DIR + +from ..processes import EyeProcessor +from ..types import EyeData, TrackerPosition +from ..utils import BaseAlgorithm, OneEuroFilter rt.disable_telemetry_events() +os.environ["OMP_NUM_THREADS"] = "1" ONNX_OPTIONS = rt.SessionOptions() ONNX_OPTIONS.inter_op_num_threads = 1 ONNX_OPTIONS.intra_op_num_threads = 1 ONNX_OPTIONS.graph_optimization_level = rt.GraphOptimizationLevel.ORT_ENABLE_ALL -MODEL_PATH: Final = "assets/models/leap.onnx" +MODEL_PATH: Final = os.path.join(MODELS_DIR, "leap.onnx") class Leap(BaseAlgorithm): @@ -53,7 +58,7 @@ def __init__(self, eye_processor: EyeProcessor) -> None: self.session = rt.InferenceSession(MODEL_PATH, ONNX_OPTIONS, ["CPUExecutionProvider"]) self.ep.logger.debug(f"Created Inference Session with `{MODEL_PATH}`") - def run(self, frame: MatLike) -> EyeData: + def run(self, frame: MatLike, tracker_position: TrackerPosition) -> tuple[EyeData, MatLike]: pre_landmark = self.filter(self.run_model(frame.copy())) self.draw_landmarks(frame, pre_landmark) @@ -78,7 +83,7 @@ def run(self, frame: MatLike) -> EyeData: x = pre_landmark[6][0] y = pre_landmark[6][1] - return EyeData(x, y, blink, self.ep.tracker_position) + return EyeData(x, y, blink, tracker_position), frame def run_model(self, frame: MatLike) -> np.ndarray: frame = cv2.resize(frame, (112, 112)) diff --git a/eyetrackvr_backend/algorithms/ransac.py b/eyetrackvr_backend/algorithms/ransac.py new file mode 100644 index 0000000..715323a --- /dev/null +++ b/eyetrackvr_backend/algorithms/ransac.py @@ -0,0 +1,488 @@ +""" +------------------------------------------------------------------------------------------------------ + + ,@@@@@@ + @@@@@@@@@@@ @@@ + @@@@@@@@@@@@ @@@@@@@@@@@ + @@@@@@@@@@@@@ @@@@@@@@@@@@@@ + @@@@@@@/ ,@@@@@@@@@@@@@ + /@@@@@@@@@@@@@@@ @@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@ + @@@@@@@@ @@@@@ + ,@@@ @@@@& + @@@@@@. @@@@ + @@@ @@@@@@@@@/ @@@@@ + ,@@@. @@@@@@((@ @@@@( + //@@@ ,, @@@@ @@@@@ + @@@( @@@@@@@ + @@@ @ @@@@@@@@# + @@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@( + +RANSAC 3D By: Summer#2406 (Main Algorithm Engineer), Pupil Labs (pye3d), PallasNeko (Optimization) +Algorithm App Implementations By: Prohurtz, qdot (Initial App Creator) + +Copyright (c) 2023 EyeTrackVR <3 +------------------------------------------------------------------------------------------------------ +""" + +# ruff: noqa: F841 +# TODO: remove this noqa once unused variables have been cleaned up + +import cv2 +import numpy as np +from enum import IntEnum + +import os +import psutil +import sys +from cv2.typing import MatLike +from ..processes import EyeProcessor +from ..utils import BaseAlgorithm +from ..types import EyeData, TrackerPosition, TRACKING_FAILED +from pye3d.camera import CameraModel +from pye3d.detector_3d import Detector3D, DetectorMode + +process = psutil.Process(os.getpid()) # set process priority to low +try: # medium chance this does absolutely nothing but eh + sys.getwindowsversion() +except AttributeError: + process.nice(0) # UNIX: 0 low 10 high + process.nice() +else: + process.nice(psutil.BELOW_NORMAL_PRIORITY_CLASS) # Windows + process.nice() + + +class EyeId(IntEnum): + RIGHT = 0 + LEFT = 1 + BOTH = 2 + SETTINGS = 3 + + +def ellipse_model(data, y, f): + """ + There is no need to make this process a function, since making the process a function will slow it down a little by calling it. + The results may be slightly different from the lambda version due to calculation errors derived from float types, but the + calculation results are virtually the same. + a = 1.0,b = P[0],c = P[1],d = P[2],e = P[3],f = P[4] + :param data: + :param y: np.c_[d, e, a, c, b] + :param f: f == P[4, 0] + :return: this_return == np.array([ellipse_model(x, y) for (x, y) in data ]) + """ + return data.dot(y) + f + + +# @profile +def fit_rotated_ellipse_ransac( + data: np.ndarray, + rng: np.random.Generator, + iter=100, + sample_num=10, + offset=80, # 80.0, 10, 80 +): # before changing these values, please read up on the ransac algorithm + # However if you want to change any value just know that higher iterations will make processing frames slower + effective_sample = None + + # The array contents do not change during the loop, so only one call is needed. + # They say len is faster than shape. + # Reference url: https://stackoverflow.com/questions/35547853/what-is-faster-python3s-len-or-numpys-shape + len_data = len(data) + + if len_data < sample_num: + return None + + # Type of calculation result + ret_dtype = np.float64 + + # Sorts a random number array of size (iter,len_data). After sorting, returns the index of sample_num random numbers before + # sorting. + # If the array size is less than about 100, this is faster than rng.choice. + rng_sample = rng.random((iter, len_data)).argsort()[:, :sample_num] + # or + # I don't see any advantage to doing this. + # rng_sample = np.asarray(rng.random((iter, len_data)).argsort()[:, :sample_num], dtype=np.int32) + + # I don't think it looks beautiful. + # x,y,x**2,y**2,x*y,1,-1*x**2 + datamod = np.concatenate( + [ + data, + data**2, + (data[:, 0] * data[:, 1])[:, np.newaxis], + np.ones((len_data, 1), dtype=ret_dtype), + (-1 * data[:, 0] ** 2)[:, np.newaxis], + ], + axis=1, + dtype=ret_dtype, + ) + + datamod_slim = np.array(datamod[:, :5], dtype=ret_dtype) + + datamod_rng = datamod[rng_sample] + datamod_rng6 = datamod_rng[:, :, 6] + datamod_rng_swap = datamod_rng[:, :, [4, 3, 0, 1, 5]] + datamod_rng_swap_trans = datamod_rng_swap.transpose((0, 2, 1)) + + # These two lines are one of the bottlenecks + datamod_rng_5x5 = np.matmul(datamod_rng_swap_trans, datamod_rng_swap) + datamod_rng_p5smp = np.matmul(np.linalg.inv(datamod_rng_5x5), datamod_rng_swap_trans) + + datamod_rng_p = np.matmul(datamod_rng_p5smp, datamod_rng6[:, :, np.newaxis]).reshape((-1, 5)) + + # I don't think it looks beautiful. + ellipse_y_arr = np.asarray( + [ + datamod_rng_p[:, 2], + datamod_rng_p[:, 3], + np.ones(len(datamod_rng_p)), + datamod_rng_p[:, 1], + datamod_rng_p[:, 0], + ], + dtype=ret_dtype, + ) + + ellipse_data_arr = ellipse_model(datamod_slim, ellipse_y_arr, np.asarray(datamod_rng_p[:, 4])).transpose((1, 0)) + ellipse_data_abs = np.abs(ellipse_data_arr) + ellipse_data_index = np.argmax(np.sum(ellipse_data_abs < offset, axis=1), axis=0) + effective_data_arr = ellipse_data_arr[ellipse_data_index] + effective_sample_p_arr = datamod_rng_p[ellipse_data_index] + + return fit_rotated_ellipse(effective_data_arr, effective_sample_p_arr) + + +# @profile +def fit_rotated_ellipse(data, P): + a = 1.0 + b = P[0] + c = P[1] + d = P[2] + e = P[3] + f = P[4] + # The cost of trigonometric functions is high. + theta = 0.5 * np.arctan(b / (a - c), dtype=np.float64) + theta_sin = np.sin(theta, dtype=np.float64) + theta_cos = np.cos(theta, dtype=np.float64) + tc2 = theta_cos**2 + ts2 = theta_sin**2 + b_tcs = b * theta_cos * theta_sin + + # Do the calculation only once + cxy = b**2 - 4 * a * c + cx = (2 * c * d - b * e) / cxy + cy = (2 * a * e - b * d) / cxy + + # I just want to clear things up around here. + cu = a * cx**2 + b * cx * cy + c * cy**2 - f + cu_r = np.array([(a * tc2 + b_tcs + c * ts2), (a * ts2 - b_tcs + c * tc2)]) + if cu > 1: # negatives can get thrown which cause errors, just ignore them + wh = np.sqrt(cu / cu_r) + else: + pass + + w, h = wh[0], wh[1] + + error_sum = np.sum(data) + # print("fitting error = %.3f" % (error_sum)) + + return (cx, cy, w, h, theta) + + +def get_center_noclamp(center_xy, radius): + center_x, center_y = center_xy + upper_x = center_x + radius + lower_x = center_x - radius + upper_y = center_y + radius + lower_y = center_y - radius + + ransac_upper_x = center_x + max(20, radius) + ransac_lower_x = center_x - max(20, radius) + ransac_upper_y = center_y + max(20, radius) + ransac_lower_y = center_y - max(20, radius) + ransac_xy_offset = (ransac_lower_x, ransac_lower_y) + return ( + center_x, + center_y, + upper_x, + lower_x, + upper_y, + lower_y, + ransac_lower_x, + ransac_lower_y, + ransac_upper_x, + ransac_upper_y, + ransac_xy_offset, + ) + + +cct = 300 + + +class RANSAC(BaseAlgorithm): + def __init__(self, eye_processor: EyeProcessor): + self.ep = eye_processor + + def run(self, frame: MatLike, tracker_position: TrackerPosition) -> tuple[EyeData, MatLike]: + + # frame = self.current_image_gray_clean + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) + + rng = np.random.default_rng() + # newFrame2 = self.current_image_gray.copy() + newFrame2 = frame.copy() + # Convert the image to grayscale, and set up thresholding. Thresholds here are basically a + # low-pass filter that will set any pixel < the threshold value to 0. Thresholding is user + # configurable in this utility as we're dealing with variable lighting amounts/placement, as + # well as camera positioning and lensing. Therefore, everyone's cutoff may be different. + # + # The goal of thresholding settings is to make sure we can ONLY see the pupil. This is why we + # crop the image earlier; it gives us less possible dark area to get confused about in the + # next step. + + # Crop first to reduce the amount of data to process. + + # frame = self.current_image_gray + # For measuring processing time of image processing + # Crop first to reduce the amount of data to process. + # frame = frame[0:len(frame) - 5, :] + # To reduce the processing data, blur. + if frame is None: + print("[WARN] Frame is empty") + return TRACKING_FAILED + else: + frame_gray = cv2.GaussianBlur(frame, (5, 5), 0) + + # this will need to be adjusted everytime hardware is changed (brightness of IR, Camera postion, etc)m + min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(frame_gray) + + maxloc0_hf, maxloc1_hf = int(0.5 * max_loc[0]), int(0.5 * max_loc[1]) + + # crop 15% sqare around min_loc + # frame_gray = frame_gray[max_loc[1] - maxloc1_hf:max_loc[1] + maxloc1_hf, + # max_loc[0] - maxloc0_hf:max_loc[0] + maxloc0_hf] + # if self.settings.gui_legacy_ransac: + # if self.eye_id in [EyeId.LEFT]: + # threshold_value = self.settings.gui_legacy_ransac_thresh_left + # else: + # threshold_value = self.settings.gui_legacy_ransac_thresh_right + # else: + threshold_value = min_val + 25 # + self.settings.gui_thresh_add TODO: use a setting value for thresh add + + _, thresh = cv2.threshold(frame_gray, threshold_value, 255, cv2.THRESH_BINARY) + try: + opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel) + closing = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel) + th_frame = 255 - closing.astype(np.float32) + except Exception as e: + print(e) + # I want to eliminate try here because try tends to be slow in execution. + th_frame = 255 - frame_gray.astype(np.float32) + + contours, _ = cv2.findContours(th_frame, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) + hull = [] + + for cnt in contours: + hull.append(cv2.convexHull(cnt, False)) + if not hull: + # If empty, go to next loop + pass + try: + + cnt = sorted(hull, key=cv2.contourArea) + maxcnt = cnt[-1] + # ellipse = cv2.fitEllipse(maxcnt) + ransac_data = fit_rotated_ellipse_ransac(maxcnt.reshape(-1, 2), rng) + print(ransac_data) + if ransac_data is None: + # ransac_data is None==maxcnt.shape[0]= 0.2: # TODO setting + # blink = 0.0 + + # if self.settings.gui_RANSACBLINK: + + # if self.ran_blink_check_for_file: + # if self.eye_id in [EyeId.LEFT]: + # file_path = "RANSAC_blink_LEFT.cfg" + # if self.eye_id in [EyeId.RIGHT]: + # file_path = "RANSAC_blink_RIGHT.cfg" + # else: + # file_path = "RANSAC_blink_RIGHT.cfg" + + # if os.path.exists(file_path): + # with open(file_path, "r") as file: + # self.blink_list = [float(line.strip()) for line in file] + # else: + # print( + # f"\033[93m[INFO] RANSAC Blink Config '{file_path}' not found. Waiting for calibration.\033[0m" + # ) + # self.ran_blink_check_for_file = False + + # if len(self.blink_list) == 10000: # self calibrate ransac blink IN TESTING + # if self.eye_id in [EyeId.LEFT]: + # with open("RANSAC_BLINK_LEFT.cfg", "w") as file: + # for item in self.blink_list: + # file.write(str(item) + "\n") + + # if self.eye_id in [EyeId.RIGHT]: + # with open("RANSAC_BLINK_RIGHT.cfg", "w") as file: + # for item in self.blink_list: + # file.write(str(item) + "\n") + # print("SAVE") + + # self.blink_list.pop(0) + # self.blink_list.append(abs(perscalarw - perscalarh)) + + # elif len(self.blink_list) < 10000: + # self.blink_list.append(abs(perscalarw - perscalarh)) + + # if abs(perscalarw - perscalarh) >= np.percentile(self.blink_list, 92): + # blink = 0.0 + + try: + cv2.drawContours(frame, contours, -1, (255, 0, 0), 1) # TODO: fix visualizations with HSRAC + cv2.circle(frame, (int(cx), int(cy)), 2, (0, 0, 255), -1) + except Exception as e: + print(e) + + # try: #for some reason the pye3d visualizations are wack, im going to just not visualize it for now.. + # cv2.ellipse( + # self.current_image_gray, + # tuple(int(v) for v in ellipse_3d["center"]), + # tuple(int(v) for v in ellipse_3d["axes"]), + # ellipse_3d["angle"], + # 0, + # 360, # start/end angle for drawing + # (0, 255, 0), # color (BGR): red + # ) + # except Exception: + # Sometimes we get bogus axes and trying to draw this throws. Ideally we should check for + # validity beforehand, but for now just pass. It usually fixes itself on the next frame. + # pass + + try: + # print(self.lkg_projected_sphere["angle"], self.lkg_projected_sphere["axes"], self.lkg_projected_sphere["center"]) + cv2.ellipse( + newFrame2, + tuple(int(v) for v in lkg_projected_sphere["center"]), + tuple(int(v) for v in lkg_projected_sphere["axes"]), + lkg_projected_sphere["angle"], + 0, + 360, # start/end angle for drawing + (0, 255, 0), # color (BGR): red + ) + + # draw line from center of eyeball to center of pupil + cv2.line( + frame, + tuple(int(v) for v in lkg_projected_sphere["center"]), + tuple(int(v) for v in ellipse_3d["center"]), + (0, 255, 0), # color (BGR): red + ) + + except Exception as e: + print(e) + + # self.current_image_gray = newFrame2 + y, x = frame.shape + thresh = cv2.resize(thresh, (x, y)) + + print(cx) + try: + return (EyeData(cx, cy, 1, tracker_position), newFrame2) + # return cx, cy, angle, thresh, blink, w, h + except Exception as e: + print(e) + # return 0, 0, 0, thresh, blink, 0, 0 + return (TRACKING_FAILED, newFrame2) diff --git a/TrackingBackend/assets/ETVR_SAMPLE.mp4 b/eyetrackvr_backend/assets/ETVR_SAMPLE.mp4 similarity index 100% rename from TrackingBackend/assets/ETVR_SAMPLE.mp4 rename to eyetrackvr_backend/assets/ETVR_SAMPLE.mp4 diff --git a/eyetrackvr_backend/assets/__init__.py b/eyetrackvr_backend/assets/__init__.py new file mode 100644 index 0000000..7916b92 --- /dev/null +++ b/eyetrackvr_backend/assets/__init__.py @@ -0,0 +1,6 @@ +import os.path + +from .images import IMAGES_DIR +from .models import MODELS_DIR + +ASSETS_DIR = os.path.dirname(__file__) diff --git a/eyetrackvr_backend/assets/images/__init__.py b/eyetrackvr_backend/assets/images/__init__.py new file mode 100644 index 0000000..0feadac --- /dev/null +++ b/eyetrackvr_backend/assets/images/__init__.py @@ -0,0 +1,3 @@ +import os.path + +IMAGES_DIR = os.path.dirname(__file__) diff --git a/TrackingBackend/assets/images/camera_offline.png b/eyetrackvr_backend/assets/images/camera_offline.png similarity index 100% rename from TrackingBackend/assets/images/camera_offline.png rename to eyetrackvr_backend/assets/images/camera_offline.png diff --git a/TrackingBackend/assets/images/logo.ico b/eyetrackvr_backend/assets/images/logo.ico similarity index 100% rename from TrackingBackend/assets/images/logo.ico rename to eyetrackvr_backend/assets/images/logo.ico diff --git a/TrackingBackend/assets/images/logo.png b/eyetrackvr_backend/assets/images/logo.png similarity index 100% rename from TrackingBackend/assets/images/logo.png rename to eyetrackvr_backend/assets/images/logo.png diff --git a/TrackingBackend/assets/index.html b/eyetrackvr_backend/assets/index.html similarity index 100% rename from TrackingBackend/assets/index.html rename to eyetrackvr_backend/assets/index.html diff --git a/eyetrackvr_backend/assets/models/__init__.py b/eyetrackvr_backend/assets/models/__init__.py new file mode 100644 index 0000000..1989b64 --- /dev/null +++ b/eyetrackvr_backend/assets/models/__init__.py @@ -0,0 +1,3 @@ +import os.path + +MODELS_DIR = os.path.dirname(__file__) diff --git a/TrackingBackend/assets/models/leap.onnx b/eyetrackvr_backend/assets/models/leap.onnx similarity index 100% rename from TrackingBackend/assets/models/leap.onnx rename to eyetrackvr_backend/assets/models/leap.onnx diff --git a/TrackingBackend/app/config.py b/eyetrackvr_backend/config.py similarity index 96% rename from TrackingBackend/app/config.py rename to eyetrackvr_backend/config.py index 581138f..74e826b 100644 --- a/TrackingBackend/app/config.py +++ b/eyetrackvr_backend/config.py @@ -9,13 +9,13 @@ import os.path import multiprocessing from copy import deepcopy -from app.logger import get_logger +from .logger import get_logger from typing import Callable, Final -from app.utils import mask_to_cpu_list +from .utils import mask_to_cpu_list from watchdog.observers import Observer from fastapi import Request, HTTPException from watchdog.observers.api import BaseObserver -from app.types import Algorithms, TrackerPosition +from .types import Algorithms, TrackerPosition from pydantic import BaseModel, ValidationError, field_validator from watchdog.events import FileSystemEventHandler, FileModifiedEvent @@ -36,6 +36,7 @@ r"(?::\d{1,5})?\b|localhost(?::\d{1,5})?|http:\/\/localhost(?::\d{1,5})?|[\w-]+\.local(?::\d{1,5})?)" ) + # TODO: move algorithm configs into the same file as the algorithms they control class BlobConfig(BaseModel): threshold: int = 65 @@ -53,10 +54,27 @@ def blink_threshold_validator(cls, value: float) -> float: return value +class HSFConfig(BaseModel): + skip_autoradius: bool = False + skip_blink_detection: bool = False + # amount of frames to use for blink baseline + blink_stat_frames: int = 60 * 3 + # bigger step = faster tracking, but less accurate + default_step: tuple[int, int] = (5, 5) + + class AlgorithmConfig(BaseModel): - algorithm_order: list[Algorithms] = [Algorithms.LEAP, Algorithms.BLOB, Algorithms.HSRAC, Algorithms.RANSAC, Algorithms.HSF] + algorithm_order: list[Algorithms] = [ + Algorithms.LEAP, + Algorithms.BLOB, + Algorithms.HSRAC, + Algorithms.RANSAC, + Algorithms.HSF, + Algorithms.AHSF, + ] blob: BlobConfig = BlobConfig() leap: LeapConfig = LeapConfig() + hsf: HSFConfig = HSFConfig() @field_validator("algorithm_order") def algorithm_order_validator(cls, value: list[Algorithms]) -> list[Algorithms]: diff --git a/TrackingBackend/app/etvr.py b/eyetrackvr_backend/etvr.py similarity index 89% rename from TrackingBackend/app/etvr.py rename to eyetrackvr_backend/etvr.py index 15cb7f9..943adf7 100644 --- a/TrackingBackend/app/etvr.py +++ b/eyetrackvr_backend/etvr.py @@ -1,9 +1,10 @@ -from app.processes import VRChatOSCReceiver -from app.config import ConfigManager +from .processes import VRChatOSCReceiver +from .config import ConfigManager from multiprocessing import Manager -from app.logger import get_logger +from .logger import get_logger from fastapi import APIRouter -from app.tracker import Tracker +from .tracker import Tracker +import sys logger = get_logger() @@ -74,6 +75,13 @@ def restart(self) -> None: self.stop() self.start() + def shutdown(self) -> None: + # NOTE: in theory this should eventually stop all child processes once they receive the stop signal + # but it's not guaranteed to work, so we should probably find a better way to handle this as sys.exit(0) + # is not a good way to handle this and doesnt work in all cases but should be good enough for now... + self.stop() + sys.exit(0) + def add_routes(self) -> None: logger.debug("Adding routes to ETVR") # region: Image streaming endpoints @@ -113,6 +121,16 @@ def add_routes(self) -> None: Stop the ETVR backend, this will stop all trackers and the OSC sender / receiver. """, ) + self.router.add_api_route( + name="Shutdown the ETVR backend", + path="/etvr/shutdown", + endpoint=self.shutdown, + methods=["GET"], + tags=["default"], + description=""" + Shutdown the ETVR backend, this will stop all trackers and the OSC sender / receiver and exit the program. + """, + ) self.router.add_api_route( name="Restart ETVR", path="/etvr/restart", diff --git a/TrackingBackend/app/logger.py b/eyetrackvr_backend/logger.py similarity index 96% rename from TrackingBackend/app/logger.py rename to eyetrackvr_backend/logger.py index 9237f3a..816167d 100644 --- a/TrackingBackend/app/logger.py +++ b/eyetrackvr_backend/logger.py @@ -1,4 +1,4 @@ -from app.types import LogLevel +from .types import LogLevel import logging import inspect import coloredlogs diff --git a/TrackingBackend/app/processes/__init__.py b/eyetrackvr_backend/processes/__init__.py similarity index 100% rename from TrackingBackend/app/processes/__init__.py rename to eyetrackvr_backend/processes/__init__.py diff --git a/TrackingBackend/app/processes/camera.py b/eyetrackvr_backend/processes/camera.py similarity index 92% rename from TrackingBackend/app/processes/camera.py rename to eyetrackvr_backend/processes/camera.py index 751bc05..05833c8 100644 --- a/TrackingBackend/app/processes/camera.py +++ b/eyetrackvr_backend/processes/camera.py @@ -1,6 +1,6 @@ -from app.utils import WorkerProcess, mat_crop, mat_rotate, clear_queue, is_serial -from app.config import CameraConfig, TrackerConfig -from app.types import CameraState +from ..utils import WorkerProcess, mat_crop, mat_rotate, clear_queue, is_serial +from ..config import CameraConfig, TrackerConfig +from ..types import CameraState from multiprocessing import Value import serial.tools.list_ports from cv2.typing import MatLike @@ -122,24 +122,26 @@ def get_camera_image(self) -> None: # region: Serial camera implementation def connect_serial_camera(self) -> None: - self.logger.info(f"Connecting to serial capture source {self.current_capture_source}") - if not any(p for p in serial.tools.list_ports.comports() if self.config.capture_source in p): - self.logger.warning(f"Serial port `{self.current_capture_source}` not found, waiting for reconnect.") + # Resolve actual path + capture_source = self.config.capture_source + if os.path.islink(capture_source): + capture_source = os.path.realpath(capture_source) + self.logger.info(f"Connecting to serial capture source {self.current_capture_source} ({capture_source})") + if not any(p for p in serial.tools.list_ports.comports() if capture_source in p): + self.logger.warning(f"Serial port `{self.current_capture_source}` (`{capture_source}`) not found, waiting for reconnect.") self.set_state(CameraState.DISCONNECTED) time.sleep(COM_PORT_NOT_FOUND_TIMEOUT) return try: - self.serial_camera = serial.Serial( - port=self.current_capture_source, baudrate=3000000, xonxoff=False, dsrdtr=False, rtscts=False - ) + self.serial_camera = serial.Serial(port=capture_source, baudrate=3000000, xonxoff=False, dsrdtr=False, rtscts=False) # The `set_buffer_size` method is only available on Windows if os.name == "nt": self.serial_camera.set_buffer_size(rx_size=32768, tx_size=32768) - self.logger.info(f"Serial camera connected to `{self.current_capture_source}`") + self.logger.info(f"Serial camera connected to `{self.current_capture_source}` (`{capture_source}`)") self.set_state(CameraState.CONNECTED) except Exception: - self.logger.exception(f"Failed to connect to serial port `{self.current_capture_source}`") + self.logger.exception(f"Failed to connect to serial port `{self.current_capture_source}` (`{capture_source}`)") self.set_state(CameraState.DISCONNECTED) # TODO: maybe move this into `get_serial_image`? diff --git a/TrackingBackend/app/processes/eye_processor.py b/eyetrackvr_backend/processes/eye_processor.py similarity index 63% rename from TrackingBackend/app/processes/eye_processor.py rename to eyetrackvr_backend/processes/eye_processor.py index 9493011..bb3c646 100644 --- a/TrackingBackend/app/processes/eye_processor.py +++ b/eyetrackvr_backend/processes/eye_processor.py @@ -1,8 +1,10 @@ -from app.types import EyeData, Algorithms, TRACKING_FAILED -from app.config import AlgorithmConfig, TrackerConfig -from app.utils import WorkerProcess, BaseAlgorithm +from ..types import EyeData, Algorithms, TRACKING_FAILED +from ..config import AlgorithmConfig, TrackerConfig +from ..utils import WorkerProcess, BaseAlgorithm from cv2.typing import MatLike from queue import Queue, Full +from copy import deepcopy +import numpy as np import queue import cv2 @@ -38,17 +40,29 @@ def run(self) -> None: self.logger.exception("Failed to get image from queue") return + frames = [] result = EyeData(0, 0, 0, self.tracker_position) + # TODO: add support for running one algorithm for blink detection and another for gaze tracking for algorithm in self.algorithms: - result = algorithm.run(current_frame) - + result, frame = algorithm.run(deepcopy(current_frame), self.tracker_position) + frames.append(frame) if result == TRACKING_FAILED: self.logger.debug(f"Algorithm {algorithm.get_name()} failed to find a result") continue break - self.osc_queue.put(result) try: + # This is kinda bad, i would like to use a bitwise or but ahsf modifies the frame dimensions + frame_shape = max(frames, key=lambda x: x.shape[0] * x.shape[1]).shape + current_frame = np.zeros(frame_shape, dtype=np.uint8) + frame_weight = min(1.0 / (len(frames)), 0.5) + for frame in frames: + if frame.shape != frame_shape: + frame = cv2.resize(frame, (frame_shape[1], frame_shape[0])) + current_frame = cv2.addWeighted(current_frame, 1 - frame_weight, frame, frame_weight, 1) + # make dark colors darker and light colors lighter + current_frame = cv2.addWeighted(current_frame, 1.5, current_frame, 0, 0) + self.osc_queue.put(result) self.frontend_queue.put(current_frame, block=False) except Full: pass @@ -63,7 +77,7 @@ def on_tracker_config_update(self, tracker_config: TrackerConfig) -> None: self.setup_algorithms() def setup_algorithms(self) -> None: - from app.algorithms import Blob, HSF, HSRAC, Ransac, Leap + from ..algorithms import Blob, HSF, HSRAC, Leap, AHSF self.algorithms.clear() for algorithm in self.config.algorithm_order: @@ -74,9 +88,11 @@ def setup_algorithms(self) -> None: self.algorithms.append(HSF(self)) case Algorithms.HSRAC: self.algorithms.append(HSRAC(self)) - case Algorithms.RANSAC: - self.algorithms.append(Ransac(self)) + # case Algorithms.RANSAC: + # self.algorithms.append(RANSAC(self)) case Algorithms.LEAP: self.algorithms.append(Leap(self)) + case Algorithms.AHSF: + self.algorithms.append(AHSF(self)) case _: self.logger.warning(f"Unknown algorithm: {algorithm}") diff --git a/TrackingBackend/app/processes/osc.py b/eyetrackvr_backend/processes/osc.py similarity index 87% rename from TrackingBackend/app/processes/osc.py rename to eyetrackvr_backend/processes/osc.py index 6534b82..3a7e63a 100644 --- a/TrackingBackend/app/processes/osc.py +++ b/eyetrackvr_backend/processes/osc.py @@ -1,7 +1,7 @@ -from app.utils import WorkerProcess, OneEuroFilter -from app.config import EyeTrackConfig, OSCConfig -from app.types import EyeData, TrackerPosition -from app.logger import get_logger +from ..utils import WorkerProcess, OneEuroFilter +from ..config import EyeTrackConfig, OSCConfig +from ..types import EyeData, TrackerPosition +from ..logger import get_logger from queue import Queue, Empty from copy import deepcopy from typing import Final @@ -75,6 +75,8 @@ def shutdown(self) -> None: def on_config_update(self, config: EyeTrackConfig) -> None: self.config = config + # The address and port may have changed, so we need to update the client + self.client = SimpleUDPClient(self.config.osc.address, self.config.osc.sending_port) def smooth(self, data: EyeData) -> EyeData: original = deepcopy(data) @@ -89,10 +91,19 @@ def draw_debug(self, window: str, original: EyeData, smoothed: EyeData) -> None: y1 = int(original.y * HEIGHT) x2 = int(smoothed.x * WIDTH) y2 = int(smoothed.y * HEIGHT) + # draw blink + cv2.rectangle(frame, (0, 0), (WIDTH, int(HEIGHT * (1 - smoothed.blink) / 2)), (238, 130, 238), -1) + cv2.rectangle(frame, (0, HEIGHT), (WIDTH, int(HEIGHT * (1 + smoothed.blink) / 2)), (238, 130, 238), -1) + # draw max bounds, assuming the user has a round eye + cv2.circle(frame, (int(WIDTH / 2), int(HEIGHT / 2)), 2, (0, 0, 0), -1) + cv2.circle(frame, (int(WIDTH / 2), int(HEIGHT / 2)), (WIDTH + HEIGHT) // 4, (0, 0, 0), 1) + # draw look directions cv2.circle(frame, (x1, y1), 5, (0, 0, 255), -1) cv2.circle(frame, (x2, y2), 5, (255, 0, 0), -1) + # draw text cv2.putText(frame, "original", (0, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 1) cv2.putText(frame, "smoothed", (0, 35), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 0, 0), 1) + cv2.putText(frame, f"blink: {smoothed.blink}", (0, HEIGHT), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 1) self.window.imshow(self.process_name(), frame) diff --git a/TrackingBackend/app/tracker.py b/eyetrackvr_backend/tracker.py similarity index 85% rename from TrackingBackend/app/tracker.py rename to eyetrackvr_backend/tracker.py index c66f8e3..8288fc1 100644 --- a/TrackingBackend/app/tracker.py +++ b/eyetrackvr_backend/tracker.py @@ -1,12 +1,12 @@ from queue import Queue from fastapi import APIRouter -from app.types import EyeData +from .types import EyeData from cv2.typing import MatLike -from app.utils import clear_queue -from app.config import EyeTrackConfig -from app.visualizer import Visualizer +from .utils import clear_queue +from .config import EyeTrackConfig +from .visualizer import Visualizer from multiprocessing.managers import SyncManager -from app.processes import EyeProcessor, Camera, VRChatOSC +from .processes import EyeProcessor, Camera, VRChatOSC # TODO: when we start to integrate babble this should become a common interface that eye trackers and mouth trackers inherit from @@ -18,8 +18,8 @@ def __init__(self, config: EyeTrackConfig, uuid: str, manager: SyncManager, rout self.tracker_config = config.get_tracker_by_uuid(uuid) # IPC stuff self.manager = manager - self.osc_queue: Queue[EyeData] = self.manager.Queue() - self.image_queue: Queue[MatLike] = self.manager.Queue() + self.osc_queue: Queue[EyeData] = self.manager.Queue(maxsize=60) + self.image_queue: Queue[MatLike] = self.manager.Queue(maxsize=60) # Used purely for visualization in the frontend self.camera_queue: Queue[MatLike] = self.manager.Queue(maxsize=15) self.algo_frame_queue: Queue[MatLike] = self.manager.Queue(maxsize=15) diff --git a/TrackingBackend/app/types.py b/eyetrackvr_backend/types.py similarity index 91% rename from TrackingBackend/app/types.py rename to eyetrackvr_backend/types.py index 4d36db4..81174a0 100644 --- a/TrackingBackend/app/types.py +++ b/eyetrackvr_backend/types.py @@ -1,6 +1,7 @@ # This file exists purely because circular imports are a thing and im too lazy to come up with a better # solution that doesnt involve a bunch of refactoring. import logging +import numpy as np from typing import Final from enum import Enum, StrEnum from dataclasses import dataclass @@ -12,6 +13,7 @@ class Algorithms(StrEnum): LEAP = "LEAP" HSRAC = "HSRAC" RANSAC = "RANSAC" + AHSF = "AHSF" class TrackerPosition(StrEnum): @@ -45,4 +47,5 @@ class EyeData: DEBUG_FLAG: Final = "ETVR_DEBUG" +EMPTY_FRAME: Final = np.zeros((1, 1), dtype=np.uint8) TRACKING_FAILED: Final = EyeData(0, 0, 0, TrackerPosition.UNDEFINED) diff --git a/TrackingBackend/app/utils/__init__.py b/eyetrackvr_backend/utils/__init__.py similarity index 74% rename from TrackingBackend/app/utils/__init__.py rename to eyetrackvr_backend/utils/__init__.py index 070b741..dd5a1d5 100644 --- a/TrackingBackend/app/utils/__init__.py +++ b/eyetrackvr_backend/utils/__init__.py @@ -1,4 +1,4 @@ from .misc_utils import clamp, BaseAlgorithm, clear_queue, is_serial, mask_to_cpu_list -from .image_utils import mat_crop, mat_rotate +from .image_utils import mat_crop, mat_rotate, safe_crop from .one_euro_filter import OneEuroFilter from .process import WorkerProcess diff --git a/TrackingBackend/app/utils/image_utils.py b/eyetrackvr_backend/utils/image_utils.py similarity index 51% rename from TrackingBackend/app/utils/image_utils.py rename to eyetrackvr_backend/utils/image_utils.py index bd50a14..baa79f0 100644 --- a/TrackingBackend/app/utils/image_utils.py +++ b/eyetrackvr_backend/utils/image_utils.py @@ -2,6 +2,16 @@ from cv2.typing import MatLike +def safe_crop(frame: MatLike, x: int, y: int, w: int, h: int, keepsize=False): + frame_h, frame_w = frame.shape[:2] + outframe = frame[max(0, y) : min(frame_h, h), max(0, x) : min(frame_w, w)].copy() + reqsize_x, reqsize_y = abs(w - x), abs(h - y) + if keepsize and outframe.shape[:2] != (reqsize_y, reqsize_x): + # If the size is different from the expected size (smaller by the amount that is out of range) + outframe = cv2.resize(outframe, (reqsize_x, reqsize_y)) + return outframe + + def mat_crop(x: int, y: int, w: int, h: int, frame: MatLike) -> MatLike: if x <= 0 or y <= 0 or w <= 0 or h <= 0: return frame diff --git a/TrackingBackend/app/utils/misc_utils.py b/eyetrackvr_backend/utils/misc_utils.py similarity index 81% rename from TrackingBackend/app/utils/misc_utils.py rename to eyetrackvr_backend/utils/misc_utils.py index b74a63c..b4083fb 100644 --- a/TrackingBackend/app/utils/misc_utils.py +++ b/eyetrackvr_backend/utils/misc_utils.py @@ -1,10 +1,10 @@ -from app.types import EyeData, TRACKING_FAILED +from ..types import EyeData, TrackerPosition, TRACKING_FAILED, EMPTY_FRAME from queue import Queue, Empty from cv2.typing import MatLike def is_serial(source: str) -> bool: - serial_prefixes = ["com", "/dev/tty"] + serial_prefixes = ["com", "/dev/tty", "/dev/serial"] return any(source.lower().startswith(prefix) for prefix in serial_prefixes) @@ -23,8 +23,8 @@ def clear_queue(queue: Queue) -> None: # Base class for all algorithms class BaseAlgorithm: # all algorithms must implement this method - def run(self, frame: MatLike) -> EyeData: - return TRACKING_FAILED + def run(self, frame: MatLike, tracker_position: TrackerPosition) -> tuple[EyeData, MatLike]: + return TRACKING_FAILED, EMPTY_FRAME def normalize(self, x: float, y: float, width: int, height: int) -> tuple[float, float]: """takes a point and normalizes it to a range of 0 to 1""" diff --git a/TrackingBackend/app/utils/one_euro_filter.py b/eyetrackvr_backend/utils/one_euro_filter.py similarity index 100% rename from TrackingBackend/app/utils/one_euro_filter.py rename to eyetrackvr_backend/utils/one_euro_filter.py diff --git a/TrackingBackend/app/utils/process.py b/eyetrackvr_backend/utils/process.py similarity index 97% rename from TrackingBackend/app/utils/process.py rename to eyetrackvr_backend/utils/process.py index 9589fad..b568368 100644 --- a/TrackingBackend/app/utils/process.py +++ b/eyetrackvr_backend/utils/process.py @@ -1,10 +1,10 @@ import time import psutil -from app.window import Window +from ..window import Window from multiprocessing import Process, Event -from app.logger import get_logger, setup_logger -from app.utils.misc_utils import mask_to_cpu_list -from app.config import EyeTrackConfig, ConfigManager, TrackerConfig +from ..logger import get_logger, setup_logger +from ..utils.misc_utils import mask_to_cpu_list +from ..config import EyeTrackConfig, ConfigManager, TrackerConfig # Welcome to assassin's multiprocessing realm # To not repeat the same mistakes I made, here are some tips: diff --git a/TrackingBackend/app/visualizer.py b/eyetrackvr_backend/visualizer.py similarity index 88% rename from TrackingBackend/app/visualizer.py rename to eyetrackvr_backend/visualizer.py index d19c421..73557b5 100644 --- a/TrackingBackend/app/visualizer.py +++ b/eyetrackvr_backend/visualizer.py @@ -1,9 +1,11 @@ import cv2 +import os.path from typing import Any from queue import Queue from fastapi.responses import StreamingResponse +from .assets import IMAGES_DIR -OFLINE_IMAGE = cv2.imread("assets/images/camera_offline.png") +OFLINE_IMAGE = cv2.imread(os.path.join(IMAGES_DIR, "camera_offline.png")) class Visualizer: diff --git a/TrackingBackend/app/window.py b/eyetrackvr_backend/window.py similarity index 100% rename from TrackingBackend/app/window.py rename to eyetrackvr_backend/window.py diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..40229aa --- /dev/null +++ b/flake.lock @@ -0,0 +1,155 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nix-github-actions": { + "inputs": { + "nixpkgs": [ + "poetry2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1703863825, + "narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=", + "owner": "nix-community", + "repo": "nix-github-actions", + "rev": "5163432afc817cf8bd1f031418d1869e4c9d5547", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nix-github-actions", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1725816686, + "narHash": "sha256-0Kq2MkQ/sQX1rhWJ/ySBBQlBJBUK8mPMDcuDhhdBkSU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "add0443ee587a0c44f22793b8c8649a0dbc3bb00", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1719763542, + "narHash": "sha256-mXkOj9sJ0f69Nkc2dGGOWtof9d1YNY8Le/Hia3RN+8Q=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e6cdd8a11b26b4d60593733106042141756b54a3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable-small", + "repo": "nixpkgs", + "type": "github" + } + }, + "poetry2nix": { + "inputs": { + "flake-utils": "flake-utils", + "nix-github-actions": "nix-github-actions", + "nixpkgs": "nixpkgs_2", + "systems": "systems_2", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1725532428, + "narHash": "sha256-dCfawQDwpukcwQw++Cn/3LIh/RZMmH+k3fm91Oc5Pf0=", + "owner": "nix-community", + "repo": "poetry2nix", + "rev": "a313fd7169ae43ecd1a2ea2f1e4899fe3edba4d2", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "poetry2nix", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "poetry2nix": "poetry2nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "id": "systems", + "type": "indirect" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "poetry2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1719749022, + "narHash": "sha256-ddPKHcqaKCIFSFc/cvxS14goUhCOAwsM1PbMr0ZtHMg=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "8df5ff62195d4e67e2264df0b7f5e8c9995fd0bd", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..64cfbed --- /dev/null +++ b/flake.nix @@ -0,0 +1,95 @@ +{ + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + inputs.poetry2nix.url = "github:nix-community/poetry2nix"; + + outputs = + { + self, + nixpkgs, + poetry2nix, + }: + let + supportedSystems = [ + "x86_64-linux" + "x86_64-darwin" + "aarch64-linux" + "aarch64-darwin" + ]; + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + nixpkgs' = forAllSystems (system: nixpkgs.legacyPackages.${system}); + poetry2nix' = forAllSystems (system: poetry2nix.lib.mkPoetry2Nix { pkgs = nixpkgs'.${system}; }); + + mkPoetryProject = { pkgs, overrides }: { + projectDir = self; + python = pkgs.python311; + overrides = overrides.withDefaults ( + final: prev: { + mypy = prev.mypy.override { + preferWheel = true; + }; + numpy = prev.numpy.override { + preferWheel = true; + }; + objprint = final.addBuildSystem "setuptools" prev.objprint; + opencv-python = prev.opencv-python.override { + preferWheel = true; + }; + pye3d = prev.pye3d.overridePythonAttrs (prevAttrs: { + nativeBuildInputs = prevAttrs.nativeBuildInputs or [] ++ [ + final.setuptools + final.cmake + final.cython + ]; + + buildInputs = prevAttrs.buildInputs or [] ++ [ + pkgs.eigen + final.scikit-build + ]; + + postPatch = '' + sed -i "2i version = ${prevAttrs.version}" setup.cfg + ''; + + dontUseCmakeConfigure = true; + }); + viztracer = final.addBuildSystem "setuptools" prev.viztracer; + } + ); + }; + in + { + formatter = forAllSystems (system: nixpkgs'.${system}.nixfmt-rfc-style); + + packages = forAllSystems ( + system: + let + inherit (poetry2nix'.${system}) mkPoetryApplication overrides; + pkgs = nixpkgs'.${system}; + in + { + default = mkPoetryApplication (mkPoetryProject { inherit overrides pkgs; }); + } + ); + + devShells = forAllSystems ( + system: + let + inherit (poetry2nix'.${system}) mkPoetryEnv overrides; + pkgs = nixpkgs'.${system}; + in + { + default = pkgs.mkShellNoCC { + shellHook = '' + echo -e "\033[0;36m:: Welcome to the EyeTrackVR Backend!\033[0m" + echo -e "\033[0;36m:: Run \"python -m eyetrackvr_backend.main\" to start the backend\033[0m" + ''; + packages = with pkgs; [ + (mkPoetryEnv (mkPoetryProject { inherit overrides pkgs; })) + binutils + poetry + ]; + }; + } + ); + }; +} diff --git a/poetry.lock b/poetry.lock index 9fae525..3622ede 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "altgraph" @@ -24,13 +24,13 @@ files = [ [[package]] name = "anyio" -version = "4.2.0" +version = "4.3.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, ] [package.dependencies] @@ -44,34 +44,45 @@ trio = ["trio (>=0.23)"] [[package]] name = "black" -version = "22.12.0" +version = "24.3.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, - {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, - {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, - {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, - {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, - {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, - {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, - {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, - {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, - {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, - {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, - {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, + {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, + {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, + {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, + {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, + {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, + {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, + {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, + {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, + {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, + {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, + {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, + {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, + {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, + {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, + {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, + {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, + {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, + {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, + {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, + {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, + {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, + {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" +packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -119,32 +130,32 @@ cron = ["capturer (>=2.4)"] [[package]] name = "fastapi" -version = "0.100.1" +version = "0.110.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "fastapi-0.100.1-py3-none-any.whl", hash = "sha256:ec6dd52bfc4eff3063cfcd0713b43c87640fefb2687bbbe3d8a08d94049cdf32"}, - {file = "fastapi-0.100.1.tar.gz", hash = "sha256:522700d7a469e4a973d92321ab93312448fbe20fca9c8da97effc7e7bc56df23"}, + {file = "fastapi-0.110.1-py3-none-any.whl", hash = "sha256:5df913203c482f820d31f48e635e022f8cbfe7350e4830ef05a3163925b1addc"}, + {file = "fastapi-0.110.1.tar.gz", hash = "sha256:6feac43ec359dfe4f45b2c18ec8c94edb8dc2dfc461d417d9e626590c071baad"}, ] [package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<3.0.0" -starlette = ">=0.27.0,<0.28.0" -typing-extensions = ">=4.5.0" +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.37.2,<0.38.0" +typing-extensions = ">=4.8.0" [package.extras] -all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "flatbuffers" -version = "23.5.26" +version = "24.3.25" description = "The FlatBuffers serialization format for Python" optional = false python-versions = "*" files = [ - {file = "flatbuffers-23.5.26-py2.py3-none-any.whl", hash = "sha256:c0ff356da363087b915fde4b8b45bdda73432fc17cddb3c8157472eab1422ad1"}, - {file = "flatbuffers-23.5.26.tar.gz", hash = "sha256:9ea1144cac05ce5d86e2859f431c6cd5e66cd9c78c558317c7955fb8d4c78d89"}, + {file = "flatbuffers-24.3.25-py2.py3-none-any.whl", hash = "sha256:8dbdec58f935f3765e4f7f3cf635ac3a77f83568138d6a2311f524ec96364812"}, + {file = "flatbuffers-24.3.25.tar.gz", hash = "sha256:de2ec5b203f21441716617f38443e0a8ebf3d25bf0d9c0bb0ce68fa00ad546a4"}, ] [[package]] @@ -225,40 +236,105 @@ docs = ["sphinx"] gmpy = ["gmpy2 (>=2.1.0a4)"] tests = ["pytest (>=4.6)"] +[[package]] +name = "msgpack" +version = "1.0.8" +description = "MessagePack serializer" +optional = false +python-versions = ">=3.8" +files = [ + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, + {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, + {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, + {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, + {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, + {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, + {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, + {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, + {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, + {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, + {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, +] + [[package]] name = "mypy" -version = "1.8.0" +version = "1.9.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, - {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, - {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, - {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, - {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, - {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, - {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, - {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, - {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, - {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, - {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, - {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, - {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, - {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, - {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, - {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, - {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, - {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, - {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, + {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, + {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, + {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, + {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, + {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, + {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, + {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, + {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, + {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, + {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, + {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, + {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, + {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, + {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, + {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, + {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, + {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, ] [package.dependencies] @@ -284,47 +360,47 @@ files = [ [[package]] name = "numpy" -version = "1.26.3" +version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-1.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf"}, - {file = "numpy-1.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd"}, - {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6"}, - {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b"}, - {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178"}, - {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485"}, - {file = "numpy-1.26.3-cp310-cp310-win32.whl", hash = "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3"}, - {file = "numpy-1.26.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce"}, - {file = "numpy-1.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374"}, - {file = "numpy-1.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6"}, - {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2"}, - {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda"}, - {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e"}, - {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00"}, - {file = "numpy-1.26.3-cp311-cp311-win32.whl", hash = "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b"}, - {file = "numpy-1.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4"}, - {file = "numpy-1.26.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13"}, - {file = "numpy-1.26.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e"}, - {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3"}, - {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419"}, - {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166"}, - {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36"}, - {file = "numpy-1.26.3-cp312-cp312-win32.whl", hash = "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511"}, - {file = "numpy-1.26.3-cp312-cp312-win_amd64.whl", hash = "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b"}, - {file = "numpy-1.26.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f"}, - {file = "numpy-1.26.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f"}, - {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b"}, - {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137"}, - {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58"}, - {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb"}, - {file = "numpy-1.26.3-cp39-cp39-win32.whl", hash = "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03"}, - {file = "numpy-1.26.3-cp39-cp39-win_amd64.whl", hash = "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2"}, - {file = "numpy-1.26.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e"}, - {file = "numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0"}, - {file = "numpy-1.26.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5"}, - {file = "numpy-1.26.3.tar.gz", hash = "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, ] [[package]] @@ -340,35 +416,36 @@ files = [ [[package]] name = "onnxruntime" -version = "1.16.3" +version = "1.17.1" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {file = "onnxruntime-1.16.3-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:3bc41f323ac77acfed190be8ffdc47a6a75e4beeb3473fbf55eeb075ccca8df2"}, - {file = "onnxruntime-1.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:212741b519ee61a4822c79c47147d63a8b0ffde25cd33988d3d7be9fbd51005d"}, - {file = "onnxruntime-1.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f91f5497fe3df4ceee2f9e66c6148d9bfeb320cd6a71df361c66c5b8bac985a"}, - {file = "onnxruntime-1.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef2b1fc269cabd27f129fb9058917d6fdc89b188c49ed8700f300b945c81f889"}, - {file = "onnxruntime-1.16.3-cp310-cp310-win32.whl", hash = "sha256:f36b56a593b49a3c430be008c2aea6658d91a3030115729609ec1d5ffbaab1b6"}, - {file = "onnxruntime-1.16.3-cp310-cp310-win_amd64.whl", hash = "sha256:3c467eaa3d2429c026b10c3d17b78b7f311f718ef9d2a0d6938e5c3c2611b0cf"}, - {file = "onnxruntime-1.16.3-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:a225bb683991001d111f75323d355b3590e75e16b5e0f07a0401e741a0143ea1"}, - {file = "onnxruntime-1.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9aded21fe3d898edd86be8aa2eb995aa375e800ad3dfe4be9f618a20b8ee3630"}, - {file = "onnxruntime-1.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00cccc37a5195c8fca5011b9690b349db435986bd508eb44c9fce432da9228a4"}, - {file = "onnxruntime-1.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e253e572021563226a86f1c024f8f70cdae28f2fb1cc8c3a9221e8b1ce37db5"}, - {file = "onnxruntime-1.16.3-cp311-cp311-win32.whl", hash = "sha256:a82a8f0b4c978d08f9f5c7a6019ae51151bced9fd91e5aaa0c20a9e4ac7a60b6"}, - {file = "onnxruntime-1.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:78d81d9af457a1dc90db9a7da0d09f3ccb1288ea1236c6ab19f0ca61f3eee2d3"}, - {file = "onnxruntime-1.16.3-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:04ebcd29c20473596a1412e471524b2fb88d55e6301c40b98dd2407b5911595f"}, - {file = "onnxruntime-1.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9996bab0f202a6435ab867bc55598f15210d0b72794d5de83712b53d564084ae"}, - {file = "onnxruntime-1.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b8f5083f903408238883821dd8c775f8120cb4a604166dbdabe97f4715256d5"}, - {file = "onnxruntime-1.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c2dcf1b70f8434abb1116fe0975c00e740722aaf321997195ea3618cc00558e"}, - {file = "onnxruntime-1.16.3-cp38-cp38-win32.whl", hash = "sha256:d4a0151e1accd04da6711f6fd89024509602f82c65a754498e960b032359b02d"}, - {file = "onnxruntime-1.16.3-cp38-cp38-win_amd64.whl", hash = "sha256:e8aa5bba78afbd4d8a2654b14ec7462ff3ce4a6aad312a3c2d2c2b65009f2541"}, - {file = "onnxruntime-1.16.3-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:6829dc2a79d48c911fedaf4c0f01e03c86297d32718a3fdee7a282766dfd282a"}, - {file = "onnxruntime-1.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:76f876c53bfa912c6c242fc38213a6f13f47612d4360bc9d599bd23753e53161"}, - {file = "onnxruntime-1.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4137e5d443e2dccebe5e156a47f1d6d66f8077b03587c35f11ee0c7eda98b533"}, - {file = "onnxruntime-1.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56695c1a343c7c008b647fff3df44da63741fbe7b6003ef576758640719be7b"}, - {file = "onnxruntime-1.16.3-cp39-cp39-win32.whl", hash = "sha256:985a029798744ce4743fcf8442240fed35c8e4d4d30ec7d0c2cdf1388cd44408"}, - {file = "onnxruntime-1.16.3-cp39-cp39-win_amd64.whl", hash = "sha256:28ff758b17ce3ca6bcad3d936ec53bd7f5482e7630a13f6dcae518eba8f71d85"}, + {file = "onnxruntime-1.17.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d43ac17ac4fa3c9096ad3c0e5255bb41fd134560212dc124e7f52c3159af5d21"}, + {file = "onnxruntime-1.17.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55b5e92a4c76a23981c998078b9bf6145e4fb0b016321a8274b1607bd3c6bd35"}, + {file = "onnxruntime-1.17.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebbcd2bc3a066cf54e6f18c75708eb4d309ef42be54606d22e5bdd78afc5b0d7"}, + {file = "onnxruntime-1.17.1-cp310-cp310-win32.whl", hash = "sha256:5e3716b5eec9092e29a8d17aab55e737480487deabfca7eac3cd3ed952b6ada9"}, + {file = "onnxruntime-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:fbb98cced6782ae1bb799cc74ddcbbeeae8819f3ad1d942a74d88e72b6511337"}, + {file = "onnxruntime-1.17.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:36fd6f87a1ecad87e9c652e42407a50fb305374f9a31d71293eb231caae18784"}, + {file = "onnxruntime-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99a8bddeb538edabc524d468edb60ad4722cff8a49d66f4e280c39eace70500b"}, + {file = "onnxruntime-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd7fddb4311deb5a7d3390cd8e9b3912d4d963efbe4dfe075edbaf18d01c024e"}, + {file = "onnxruntime-1.17.1-cp311-cp311-win32.whl", hash = "sha256:606a7cbfb6680202b0e4f1890881041ffc3ac6e41760a25763bd9fe146f0b335"}, + {file = "onnxruntime-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:53e4e06c0a541696ebdf96085fd9390304b7b04b748a19e02cf3b35c869a1e76"}, + {file = "onnxruntime-1.17.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:40f08e378e0f85929712a2b2c9b9a9cc400a90c8a8ca741d1d92c00abec60843"}, + {file = "onnxruntime-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac79da6d3e1bb4590f1dad4bb3c2979d7228555f92bb39820889af8b8e6bd472"}, + {file = "onnxruntime-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ae9ba47dc099004e3781f2d0814ad710a13c868c739ab086fc697524061695ea"}, + {file = "onnxruntime-1.17.1-cp312-cp312-win32.whl", hash = "sha256:2dff1a24354220ac30e4a4ce2fb1df38cb1ea59f7dac2c116238d63fe7f4c5ff"}, + {file = "onnxruntime-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:6226a5201ab8cafb15e12e72ff2a4fc8f50654e8fa5737c6f0bd57c5ff66827e"}, + {file = "onnxruntime-1.17.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:cd0c07c0d1dfb8629e820b05fda5739e4835b3b82faf43753d2998edf2cf00aa"}, + {file = "onnxruntime-1.17.1-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:617ebdf49184efa1ba6e4467e602fbfa029ed52c92f13ce3c9f417d303006381"}, + {file = "onnxruntime-1.17.1-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dae9071e3facdf2920769dceee03b71c684b6439021defa45b830d05e148924"}, + {file = "onnxruntime-1.17.1-cp38-cp38-win32.whl", hash = "sha256:835d38fa1064841679433b1aa8138b5e1218ddf0cfa7a3ae0d056d8fd9cec713"}, + {file = "onnxruntime-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:96621e0c555c2453bf607606d08af3f70fbf6f315230c28ddea91754e17ad4e6"}, + {file = "onnxruntime-1.17.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:7a9539935fb2d78ebf2cf2693cad02d9930b0fb23cdd5cf37a7df813e977674d"}, + {file = "onnxruntime-1.17.1-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45c6a384e9d9a29c78afff62032a46a993c477b280247a7e335df09372aedbe9"}, + {file = "onnxruntime-1.17.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e19f966450f16863a1d6182a685ca33ae04d7772a76132303852d05b95411ea"}, + {file = "onnxruntime-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e2ae712d64a42aac29ed7a40a426cb1e624a08cfe9273dcfe681614aa65b07dc"}, + {file = "onnxruntime-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:f7e9f7fb049825cdddf4a923cfc7c649d84d63c0134315f8e0aa9e0c3004672c"}, ] [package.dependencies] @@ -400,13 +477,13 @@ numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] @@ -433,28 +510,28 @@ files = [ [[package]] name = "platformdirs" -version = "4.1.0" +version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -463,47 +540,47 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "protobuf" -version = "4.25.1" +version = "5.26.1" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-4.25.1-cp310-abi3-win32.whl", hash = "sha256:193f50a6ab78a970c9b4f148e7c750cfde64f59815e86f686c22e26b4fe01ce7"}, - {file = "protobuf-4.25.1-cp310-abi3-win_amd64.whl", hash = "sha256:3497c1af9f2526962f09329fd61a36566305e6c72da2590ae0d7d1322818843b"}, - {file = "protobuf-4.25.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:0bf384e75b92c42830c0a679b0cd4d6e2b36ae0cf3dbb1e1dfdda48a244f4bcd"}, - {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:0f881b589ff449bf0b931a711926e9ddaad3b35089cc039ce1af50b21a4ae8cb"}, - {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:ca37bf6a6d0046272c152eea90d2e4ef34593aaa32e8873fc14c16440f22d4b7"}, - {file = "protobuf-4.25.1-cp38-cp38-win32.whl", hash = "sha256:abc0525ae2689a8000837729eef7883b9391cd6aa7950249dcf5a4ede230d5dd"}, - {file = "protobuf-4.25.1-cp38-cp38-win_amd64.whl", hash = "sha256:1484f9e692091450e7edf418c939e15bfc8fc68856e36ce399aed6889dae8bb0"}, - {file = "protobuf-4.25.1-cp39-cp39-win32.whl", hash = "sha256:8bdbeaddaac52d15c6dce38c71b03038ef7772b977847eb6d374fc86636fa510"}, - {file = "protobuf-4.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:becc576b7e6b553d22cbdf418686ee4daa443d7217999125c045ad56322dda10"}, - {file = "protobuf-4.25.1-py3-none-any.whl", hash = "sha256:a19731d5e83ae4737bb2a089605e636077ac001d18781b3cf489b9546c7c80d6"}, - {file = "protobuf-4.25.1.tar.gz", hash = "sha256:57d65074b4f5baa4ab5da1605c02be90ac20c8b40fb137d6a8df9f416b0d0ce2"}, + {file = "protobuf-5.26.1-cp310-abi3-win32.whl", hash = "sha256:3c388ea6ddfe735f8cf69e3f7dc7611e73107b60bdfcf5d0f024c3ccd3794e23"}, + {file = "protobuf-5.26.1-cp310-abi3-win_amd64.whl", hash = "sha256:e6039957449cb918f331d32ffafa8eb9255769c96aa0560d9a5bf0b4e00a2a33"}, + {file = "protobuf-5.26.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:38aa5f535721d5bb99861166c445c4105c4e285c765fbb2ac10f116e32dcd46d"}, + {file = "protobuf-5.26.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:fbfe61e7ee8c1860855696e3ac6cfd1b01af5498facc6834fcc345c9684fb2ca"}, + {file = "protobuf-5.26.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:f7417703f841167e5a27d48be13389d52ad705ec09eade63dfc3180a959215d7"}, + {file = "protobuf-5.26.1-cp38-cp38-win32.whl", hash = "sha256:d693d2504ca96750d92d9de8a103102dd648fda04540495535f0fec7577ed8fc"}, + {file = "protobuf-5.26.1-cp38-cp38-win_amd64.whl", hash = "sha256:9b557c317ebe6836835ec4ef74ec3e994ad0894ea424314ad3552bc6e8835b4e"}, + {file = "protobuf-5.26.1-cp39-cp39-win32.whl", hash = "sha256:b9ba3ca83c2e31219ffbeb9d76b63aad35a3eb1544170c55336993d7a18ae72c"}, + {file = "protobuf-5.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ee014c2c87582e101d6b54260af03b6596728505c79f17c8586e7523aaa8f8c"}, + {file = "protobuf-5.26.1-py3-none-any.whl", hash = "sha256:da612f2720c0183417194eeaa2523215c4fcc1a1949772dc65f05047e08d5932"}, + {file = "protobuf-5.26.1.tar.gz", hash = "sha256:8ca2a1d97c290ec7b16e4e5dff2e5ae150cc1582f55b5ab300d45cb0dfa90e51"}, ] [[package]] name = "psutil" -version = "5.9.7" +version = "5.9.8" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "psutil-5.9.7-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0bd41bf2d1463dfa535942b2a8f0e958acf6607ac0be52265ab31f7923bcd5e6"}, - {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:5794944462509e49d4d458f4dbfb92c47539e7d8d15c796f141f474010084056"}, - {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:fe361f743cb3389b8efda21980d93eb55c1f1e3898269bc9a2a1d0bb7b1f6508"}, - {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:e469990e28f1ad738f65a42dcfc17adaed9d0f325d55047593cb9033a0ab63df"}, - {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:3c4747a3e2ead1589e647e64aad601981f01b68f9398ddf94d01e3dc0d1e57c7"}, - {file = "psutil-5.9.7-cp27-none-win32.whl", hash = "sha256:1d4bc4a0148fdd7fd8f38e0498639ae128e64538faa507df25a20f8f7fb2341c"}, - {file = "psutil-5.9.7-cp27-none-win_amd64.whl", hash = "sha256:4c03362e280d06bbbfcd52f29acd79c733e0af33d707c54255d21029b8b32ba6"}, - {file = "psutil-5.9.7-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ea36cc62e69a13ec52b2f625c27527f6e4479bca2b340b7a452af55b34fcbe2e"}, - {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1132704b876e58d277168cd729d64750633d5ff0183acf5b3c986b8466cd0284"}, - {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8b7f07948f1304497ce4f4684881250cd859b16d06a1dc4d7941eeb6233bfe"}, - {file = "psutil-5.9.7-cp36-cp36m-win32.whl", hash = "sha256:b27f8fdb190c8c03914f908a4555159327d7481dac2f01008d483137ef3311a9"}, - {file = "psutil-5.9.7-cp36-cp36m-win_amd64.whl", hash = "sha256:44969859757f4d8f2a9bd5b76eba8c3099a2c8cf3992ff62144061e39ba8568e"}, - {file = "psutil-5.9.7-cp37-abi3-win32.whl", hash = "sha256:c727ca5a9b2dd5193b8644b9f0c883d54f1248310023b5ad3e92036c5e2ada68"}, - {file = "psutil-5.9.7-cp37-abi3-win_amd64.whl", hash = "sha256:f37f87e4d73b79e6c5e749440c3113b81d1ee7d26f21c19c47371ddea834f414"}, - {file = "psutil-5.9.7-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:032f4f2c909818c86cea4fe2cc407f1c0f0cde8e6c6d702b28b8ce0c0d143340"}, - {file = "psutil-5.9.7.tar.gz", hash = "sha256:3f02134e82cfb5d089fddf20bb2e03fd5cd52395321d1c8458a9e58500ff417c"}, + {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, + {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, + {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, + {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, + {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, + {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, + {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, + {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, + {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, + {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, ] [package.extras] @@ -511,18 +588,18 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] name = "pydantic" -version = "2.5.3" +version = "2.6.4" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"}, - {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"}, + {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"}, + {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.14.6" +pydantic-core = "2.16.3" typing-extensions = ">=4.6.1" [package.extras] @@ -530,121 +607,135 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.14.6" +version = "2.16.3" description = "" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"}, - {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"}, - {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"}, - {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"}, - {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"}, - {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"}, - {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"}, - {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"}, - {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"}, - {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"}, - {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"}, - {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"}, - {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"}, - {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"}, - {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"}, - {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"}, - {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"}, - {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"}, - {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"}, - {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"}, - {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"}, - {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"}, - {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"}, - {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"}, - {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"}, - {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"}, - {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"}, - {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"}, - {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"}, - {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"}, - {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"}, - {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"}, - {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"}, - {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"}, - {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, + {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, + {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, + {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, + {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, + {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, + {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, + {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, + {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, + {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, + {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, + {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, + {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, + {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, ] [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pye3d" +version = "0.3.2" +description = "3D eye model" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pye3d-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:25875702bfb644171b7e5ffc7c778289e4adf3d57aa3959aa8935b44f9ed81f9"}, + {file = "pye3d-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b527a02a3872f8a38f8a74fb2f5b3d2bad677190f13fae6d1997c963be264eb"}, + {file = "pye3d-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:01f96d315f218e2a3da27eb6a831eb8d0f1e56a62292ff08f7ca839d0eb98e61"}, + {file = "pye3d-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e5b61e47761dc6f455447ffb817a8a2900b0c231f6ac2744cbe77633317a2007"}, + {file = "pye3d-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c356f102334b4f0f50bc459c85e556567c6b115a48fb689cd293588ef80ca0e3"}, + {file = "pye3d-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:b1577c6081414a07a457d9e8b6bd6920cd3ccc86415eec286f5b92504bb597c0"}, + {file = "pye3d-0.3.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:569bcfb86f29cd7c3f54fcb5849583c8299168e4f4b2bc01616dacd02af7f9c8"}, + {file = "pye3d-0.3.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffe579fdb90491a7ae16e9bf541c0ceab2e940e1502f35e747b441a776d61989"}, + {file = "pye3d-0.3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:3c5ec18e11cc21e44ec438ea86e8d4b79702032eb3738e0272f7104b9d01364a"}, + {file = "pye3d-0.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5cead11165c0936ee12e3021ab9bafa600400981726c2d53c51350a89f74f18d"}, + {file = "pye3d-0.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b785502d21aac56fea97a382227e69ddc8ecbcf9dc31d5025f66e2ca1cb1a998"}, + {file = "pye3d-0.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:0f03a946bb3caa8153e3b22f3464598096396e3038f26f75ea5234cb0b7c43ca"}, + {file = "pye3d-0.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba5f9208736709dbd328122ecb7153324490a885291fd1b0942add179e59a258"}, + {file = "pye3d-0.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ddda0564ec37d6a150222c2c826ba9e1090d5559f7ec1ea5a7dc877f45c7b33"}, + {file = "pye3d-0.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:239cc403748bab8b08b1c682552163dc8eeb3d93a70e1a9a20bba3d429ffe5c5"}, + {file = "pye3d-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aa3a52b9f52ce1b6681b7e49ea870f825a86a1ee82d81d90f1b0079b651e0932"}, + {file = "pye3d-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e58f0b8f8cfb7bbe956cd9b7eb4d5cdbfea62138e80158200aa2da52e703374"}, + {file = "pye3d-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:50780d21a6d4fdf2758493e855f56c6a7c92912bb938a5eb3df6805ecbf8fce9"}, + {file = "pye3d-0.3.2.tar.gz", hash = "sha256:12793dd0b2926539252d6b2ba9df6b0c1774947a007661b2c1670ef73c905339"}, +] + +[package.dependencies] +msgpack = ">=1.0" +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 (==4.6.0.66)", "pandas", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "scikit-image"] +with-opencv = ["opencv-python"] + [[package]] name = "pyinstaller" version = "5.13.2" @@ -680,13 +771,13 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2023.12" +version = "2024.3" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstaller-hooks-contrib-2023.12.tar.gz", hash = "sha256:11a9d59d903723dd693e8c10b054f3ea1ecad390623c9fa527c731d715fc5b3f"}, - {file = "pyinstaller_hooks_contrib-2023.12-py2.py3-none-any.whl", hash = "sha256:6a601a0d783fa725327fc6ac712779475dc8979f639419c7fcd460dd8d0a6d2a"}, + {file = "pyinstaller-hooks-contrib-2024.3.tar.gz", hash = "sha256:d18657c29267c63563a96b8fc78db6ba9ae40af6702acb2f8c871df12c75b60b"}, + {file = "pyinstaller_hooks_contrib-2024.3-py2.py3-none-any.whl", hash = "sha256:6701752d525e1f4eda1eaec2c2affc206171e15c7a4e188a152fcf3ed3308024"}, ] [package.dependencies] @@ -780,73 +871,85 @@ files = [ [[package]] name = "ruff" -version = "0.1.11" +version = "0.6.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a7f772696b4cdc0a3b2e527fc3c7ccc41cdcb98f5c80fdd4f2b8c50eb1458196"}, - {file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:934832f6ed9b34a7d5feea58972635c2039c7a3b434fe5ba2ce015064cb6e955"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea0d3e950e394c4b332bcdd112aa566010a9f9c95814844a7468325290aabfd9"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bd4025b9c5b429a48280785a2b71d479798a69f5c2919e7d274c5f4b32c3607"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1ad00662305dcb1e987f5ec214d31f7d6a062cae3e74c1cbccef15afd96611d"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4b077ce83f47dd6bea1991af08b140e8b8339f0ba8cb9b7a484c30ebab18a23f"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a88efecec23c37b11076fe676e15c6cdb1271a38f2b415e381e87fe4517f18"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b25093dad3b055667730a9b491129c42d45e11cdb7043b702e97125bcec48a1"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:231d8fb11b2cc7c0366a326a66dafc6ad449d7fcdbc268497ee47e1334f66f77"}, - {file = "ruff-0.1.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:09c415716884950080921dd6237767e52e227e397e2008e2bed410117679975b"}, - {file = "ruff-0.1.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0f58948c6d212a6b8d41cd59e349751018797ce1727f961c2fa755ad6208ba45"}, - {file = "ruff-0.1.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:190a566c8f766c37074d99640cd9ca3da11d8deae2deae7c9505e68a4a30f740"}, - {file = "ruff-0.1.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6464289bd67b2344d2a5d9158d5eb81025258f169e69a46b741b396ffb0cda95"}, - {file = "ruff-0.1.11-py3-none-win32.whl", hash = "sha256:9b8f397902f92bc2e70fb6bebfa2139008dc72ae5177e66c383fa5426cb0bf2c"}, - {file = "ruff-0.1.11-py3-none-win_amd64.whl", hash = "sha256:eb85ee287b11f901037a6683b2374bb0ec82928c5cbc984f575d0437979c521a"}, - {file = "ruff-0.1.11-py3-none-win_arm64.whl", hash = "sha256:97ce4d752f964ba559c7023a86e5f8e97f026d511e48013987623915431c7ea9"}, - {file = "ruff-0.1.11.tar.gz", hash = "sha256:f9d4d88cb6eeb4dfe20f9f0519bd2eaba8119bde87c3d5065c541dbae2b5a2cb"}, + {file = "ruff-0.6.1-py3-none-linux_armv6l.whl", hash = "sha256:b4bb7de6a24169dc023f992718a9417380301b0c2da0fe85919f47264fb8add9"}, + {file = "ruff-0.6.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:45efaae53b360c81043e311cdec8a7696420b3d3e8935202c2846e7a97d4edae"}, + {file = "ruff-0.6.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bc60c7d71b732c8fa73cf995efc0c836a2fd8b9810e115be8babb24ae87e0850"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c7477c3b9da822e2db0b4e0b59e61b8a23e87886e727b327e7dcaf06213c5cf"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a0af7ab3f86e3dc9f157a928e08e26c4b40707d0612b01cd577cc84b8905cc9"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:392688dbb50fecf1bf7126731c90c11a9df1c3a4cdc3f481b53e851da5634fa5"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5278d3e095ccc8c30430bcc9bc550f778790acc211865520f3041910a28d0024"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe6d5f65d6f276ee7a0fc50a0cecaccb362d30ef98a110f99cac1c7872df2f18"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2e0dd11e2ae553ee5c92a81731d88a9883af8db7408db47fc81887c1f8b672e"}, + {file = "ruff-0.6.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d812615525a34ecfc07fd93f906ef5b93656be01dfae9a819e31caa6cfe758a1"}, + {file = "ruff-0.6.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:faaa4060f4064c3b7aaaa27328080c932fa142786f8142aff095b42b6a2eb631"}, + {file = "ruff-0.6.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99d7ae0df47c62729d58765c593ea54c2546d5de213f2af2a19442d50a10cec9"}, + {file = "ruff-0.6.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9eb18dfd7b613eec000e3738b3f0e4398bf0153cb80bfa3e351b3c1c2f6d7b15"}, + {file = "ruff-0.6.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c62bc04c6723a81e25e71715aa59489f15034d69bf641df88cb38bdc32fd1dbb"}, + {file = "ruff-0.6.1-py3-none-win32.whl", hash = "sha256:9fb4c4e8b83f19c9477a8745e56d2eeef07a7ff50b68a6998f7d9e2e3887bdc4"}, + {file = "ruff-0.6.1-py3-none-win_amd64.whl", hash = "sha256:c2ebfc8f51ef4aca05dad4552bbcf6fe8d1f75b2f6af546cc47cc1c1ca916b5b"}, + {file = "ruff-0.6.1-py3-none-win_arm64.whl", hash = "sha256:3bc81074971b0ffad1bd0c52284b22411f02a11a012082a76ac6da153536e014"}, + {file = "ruff-0.6.1.tar.gz", hash = "sha256:af3ffd8c6563acb8848d33cd19a69b9bfe943667f0419ca083f8ebe4224a3436"}, ] [[package]] name = "setuptools" -version = "69.0.3" +version = "69.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, - {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, + {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, + {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = false +python-versions = "*" +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] [[package]] name = "starlette" -version = "0.27.0" +version = "0.37.2" description = "The little ASGI library that shines." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, - {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, + {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, + {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, ] [package.dependencies] anyio = ">=3.4.0,<5" [package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] [[package]] name = "sympy" @@ -864,13 +967,13 @@ mpmath = ">=0.19" [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.10.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] @@ -984,4 +1087,4 @@ watchmedo = ["PyYAML (>=3.10)"] [metadata] lock-version = "2.0" python-versions = "~3.11.0" -content-hash = "e694e7371327b88602774aa62b3e764accd9ab2bfaa217004d1a961c9a083708" +content-hash = "5e2d80a0da9ac5292934bf9b95d63a0c196f30e79a74dc50a69787678c236fc2" diff --git a/pyproject.toml b/pyproject.toml index 92538be..6119901 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,17 @@ [tool] [tool.poetry] -name = "EyeTrackVR" +name = "eyetrackvr_backend" version = "1.6.1" description = "Opensource, affordable VR eye tracker for VRChat" -authors = ["ShyAssassin <49711232+ShyAssassin@users.noreply.github.com>", "RedHawk989 <48768484+RedHawk989@users.noreply.github.com>"] +authors = ["ShyAssassin ", "RedHawk989 <48768484+RedHawk989@users.noreply.github.com>"] license = "MIT" repository = "https://github.com/RedHawk989/EyeTrackVR" +packages = [ + { include = "eyetrackvr_backend" } +] + +[tool.poetry.scripts] +eyetrackvr-backend = 'eyetrackvr_backend.__main__:main' [tool.poetry.dependencies] python = "~3.11.0" @@ -13,7 +19,7 @@ python-osc = "^1.8.1" opencv-python = "^4.8.0.74" numpy = "^1.23.5" pydantic = "^2.0.3" -fastapi = "^0.100.0" +fastapi = "^0.110.0" uvicorn = "^0.20.0" coloredlogs = "^15.0.1" colorama = "^0.4.6" @@ -21,15 +27,16 @@ watchdog = "^3.0.0" onnxruntime = "^1.16.0" pyserial = "^3.5" psutil = "^5.9.7" +pye3d = "^0.3.1.post1" [tool.poetry.group.dev.dependencies] pytest-asyncio = "^0.21.1" pyinstaller = "^5.6.2" viztracer = "^0.15.6" -black = "^22.10.0" +black = "^24.3.0" pytest = "^7.2.0" mypy = "^1.4.1" -ruff = "^0.1.5" +ruff = "^0.6.1" [tool.black] line-length = 135 @@ -37,8 +44,8 @@ exclude = "(.git|.env|venv|.venv|build|dist|.vscode|.idea|__pycache__|.ruff_cach target-version = ["py310", "py311"] [tool.ruff] -select = ["E", "F", "W", "Q"] -src = ["TrackingBackend", "test"] +lint.select = ["E", "F", "W", "Q"] +src = ["eyetrackvr-backend", "test"] respect-gitignore = true target-version = "py311" output-format = "grouped" @@ -46,9 +53,10 @@ indent-width = 4 exclude = ["__pycache__", ".ruff_cache", ".vscode", ".idea", ".venv", "build", "dist", ".git", ".env", "venv"] line-length = 135 -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/TrackingBackend/tests/__init__.py b/tests/__init__.py similarity index 100% rename from TrackingBackend/tests/__init__.py rename to tests/__init__.py diff --git a/TrackingBackend/tests/test_config.py b/tests/test_config.py similarity index 97% rename from TrackingBackend/tests/test_config.py rename to tests/test_config.py index 2c7bd68..e005c02 100644 --- a/TrackingBackend/tests/test_config.py +++ b/tests/test_config.py @@ -1,4 +1,4 @@ -from app.config import IP_ADDRESS_REGEX, EyeTrackConfig, TrackerConfig, ConfigManager, CONFIG_FILE +from eyetrackvr_backend.config import IP_ADDRESS_REGEX, EyeTrackConfig, TrackerConfig, ConfigManager, CONFIG_FILE import pytest import json import re diff --git a/TrackingBackend/tests/test_logger.py b/tests/test_logger.py similarity index 74% rename from TrackingBackend/tests/test_logger.py rename to tests/test_logger.py index 157c84a..3998d8e 100644 --- a/TrackingBackend/tests/test_logger.py +++ b/tests/test_logger.py @@ -1,5 +1,5 @@ -from app.logger import get_logger, set_log_level -from app.types import LogLevel +from eyetrackvr_backend.logger import get_logger, set_log_level +from eyetrackvr_backend.types import LogLevel import logging diff --git a/TrackingBackend/tests/test_util.py b/tests/test_util.py similarity index 88% rename from TrackingBackend/tests/test_util.py rename to tests/test_util.py index 58a37b6..98b22c1 100644 --- a/TrackingBackend/tests/test_util.py +++ b/tests/test_util.py @@ -1,4 +1,4 @@ -from app.utils import clamp +from eyetrackvr_backend.utils import clamp import pytest