├── .editorconfig ├── .github └── workflows │ ├── close-inactive-issues.yaml │ ├── main.yaml │ └── secret-scanning.yaml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── assets └── open-image-models-logo.png ├── docs ├── contributing.md ├── index.md ├── installation.md └── reference.md ├── mkdocs.yml ├── open_image_models ├── __init__.py ├── detection │ ├── __init__.py │ ├── core │ │ ├── __init__.py │ │ ├── base.py │ │ ├── hub.py │ │ └── yolo_v9 │ │ │ ├── __init__.py │ │ │ ├── inference.py │ │ │ ├── postprocess.py │ │ │ └── preprocess.py │ └── pipeline │ │ ├── __init__.py │ │ └── license_plate.py ├── py.typed └── utils.py ├── poetry.lock ├── poetry.toml ├── pyproject.toml └── test ├── __init__.py ├── assets ├── __init__.py └── car_image.webp ├── conftest.py └── open_image_models ├── __init__.py └── detection ├── __init__.py └── core ├── __init__.py ├── test_base.py ├── test_hub.py └── yolo_v9 ├── __init__.py ├── test_inference.py ├── test_postprocess.py └── test_preprocess.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: https://EditorConfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.py] 12 | indent_size = 4 13 | max_line_length = 120 14 | -------------------------------------------------------------------------------- /.github/workflows/close-inactive-issues.yaml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | 3 | on: 4 | schedule: 5 | - cron: "30 1 * * *" # Runs daily at 1:30 AM UTC 6 | 7 | jobs: 8 | close-issues: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - uses: actions/stale@v5 15 | with: 16 | days-before-issue-stale: 90 # The number of days old an issue can be before marking it stale 17 | days-before-issue-close: 14 # The number of days to wait to close an issue after it being marked stale 18 | stale-issue-label: "stale" 19 | stale-issue-message: "This issue is stale because it has been open for 90 days with no activity." 20 | close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." 21 | days-before-pr-stale: -1 # Disables stale behavior for PRs 22 | days-before-pr-close: -1 # Disables closing behavior for PRs 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Test and Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | tags: 7 | - 'v*' 8 | pull_request: 9 | branches: 10 | - '**' 11 | jobs: 12 | test: 13 | name: Test 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | python-version: [ '3.10', '3.11', '3.12' ] 18 | os: [ ubuntu-latest ] 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v3 22 | 23 | - name: Install poetry 24 | run: pipx install poetry 25 | 26 | - uses: actions/setup-python@v4 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | cache: 'poetry' 30 | 31 | - name: Install dependencies 32 | run: poetry install --all-extras 33 | 34 | - name: Check format 35 | run: make check_format 36 | 37 | - name: Run Linters 38 | run: make lint 39 | 40 | - name: Run tests 41 | run: make test 42 | 43 | build-and-publish-pypi: 44 | name: Build and Publish to PyPI 45 | needs: 46 | - test 47 | if: "startsWith(github.ref, 'refs/tags/v')" 48 | runs-on: ubuntu-latest 49 | environment: 50 | name: pypi 51 | url: https://pypi.org/p/open-image-models 52 | permissions: 53 | id-token: write 54 | steps: 55 | - uses: actions/checkout@v3 56 | 57 | - name: Install poetry 58 | run: pipx install poetry 59 | 60 | - name: Setup Python 61 | uses: actions/setup-python@v4 62 | with: 63 | python-version: '3.10' 64 | 65 | - name: Build a binary wheel 66 | run: poetry build 67 | 68 | - name: Publish distribution 📦 to PyPI 69 | uses: pypa/gh-action-pypi-publish@release/v1 70 | 71 | github-release: 72 | name: Create GitHub release 73 | needs: 74 | - build-and-publish-pypi 75 | runs-on: ubuntu-latest 76 | 77 | permissions: 78 | contents: write 79 | 80 | steps: 81 | - uses: actions/checkout@v3 82 | 83 | - name: Check package version matches tag 84 | id: check-version 85 | uses: samuelcolvin/check-python-version@v4.1 86 | with: 87 | version_file_path: 'pyproject.toml' 88 | 89 | - name: Create GitHub Release 90 | env: 91 | GITHUB_TOKEN: ${{ github.token }} 92 | tag: ${{ github.ref_name }} 93 | run: | 94 | gh release create "$tag" \ 95 | --repo="$GITHUB_REPOSITORY" \ 96 | --title="${GITHUB_REPOSITORY#*/} ${tag#v}" \ 97 | --generate-notes 98 | 99 | update_docs: 100 | name: Update documentation 101 | needs: 102 | - github-release 103 | runs-on: ubuntu-latest 104 | permissions: 105 | contents: write 106 | 107 | steps: 108 | - uses: actions/checkout@v3 109 | with: 110 | fetch-depth: 0 111 | 112 | - name: Install Poetry using pipx 113 | run: pipx install poetry 114 | 115 | - uses: actions/setup-python@v4 116 | with: 117 | python-version: '3.10' 118 | cache: 'poetry' 119 | 120 | - name: Configure Git user 121 | run: | 122 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 123 | git config --local user.name "github-actions[bot]" 124 | 125 | - name: Retrieve version 126 | id: check-version 127 | uses: samuelcolvin/check-python-version@v4.1 128 | with: 129 | version_file_path: 'pyproject.toml' 130 | skip_env_check: true 131 | 132 | - name: Deploy Documentation 133 | run: | 134 | poetry run mike deploy \ 135 | --update-aliases \ 136 | --push \ 137 | --branch docs-site \ 138 | ${{ steps.check-version.outputs.VERSION_MAJOR_MINOR }} latest 139 | -------------------------------------------------------------------------------- /.github/workflows/secret-scanning.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | branches: 7 | - '**' 8 | 9 | name: Secret Leaks 10 | jobs: 11 | trufflehog: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | - name: Secret Scanning 19 | uses: trufflesecurity/trufflehog@main 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # pyenv 156 | .python-version 157 | 158 | # CUDA DNN 159 | cudnn64_7.dll 160 | 161 | # Train folder 162 | train_val_set/ 163 | 164 | # IDEA 165 | .idea/ 166 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ankandrew 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Directories 2 | SRC_PATHS := open_image_models/ test/ 3 | 4 | # Tasks 5 | .PHONY: help 6 | help: 7 | @echo "Available targets:" 8 | @echo " help : Show this help message" 9 | @echo " format : Format code using Ruff format" 10 | @echo " check_format : Check code formatting with Ruff format" 11 | @echo " ruff : Run Ruff linter" 12 | @echo " pylint : Run Pylint linter" 13 | @echo " mypy : Run MyPy static type checker" 14 | @echo " lint : Run linters (Ruff, Pylint and Mypy)" 15 | @echo " test : Run tests using pytest" 16 | @echo " checks : Check format, lint, and test" 17 | @echo " clean : Clean up caches and build artifacts" 18 | 19 | .PHONY: format 20 | format: 21 | @echo "==> Sorting imports..." 22 | @# Currently, the Ruff formatter does not sort imports, see https://docs.astral.sh/ruff/formatter/#sorting-imports 23 | @poetry run ruff check --select I --fix $(SRC_PATHS) 24 | @echo "=====> Formatting code..." 25 | @poetry run ruff format $(SRC_PATHS) 26 | 27 | .PHONY: check_format 28 | check_format: 29 | @echo "=====> Checking format..." 30 | @poetry run ruff format --check --diff $(SRC_PATHS) 31 | @echo "=====> Checking imports are sorted..." 32 | @poetry run ruff check --select I --exit-non-zero-on-fix $(SRC_PATHS) 33 | 34 | .PHONY: ruff 35 | ruff: 36 | @echo "=====> Running Ruff..." 37 | @poetry run ruff check $(SRC_PATHS) 38 | 39 | .PHONY: pylint 40 | pylint: 41 | @echo "=====> Running Pylint..." 42 | @poetry run pylint $(SRC_PATHS) 43 | 44 | .PHONY: mypy 45 | mypy: 46 | @echo "=====> Running Mypy..." 47 | @poetry run mypy $(SRC_PATHS) 48 | 49 | .PHONY: lint 50 | lint: ruff pylint mypy 51 | 52 | .PHONY: test 53 | test: 54 | @echo "=====> Running tests..." 55 | @poetry run pytest test/ 56 | 57 | .PHONY: clean 58 | clean: 59 | @echo "=====> Cleaning caches..." 60 | @poetry run ruff clean 61 | @rm -rf .cache .pytest_cache .mypy_cache build dist *.egg-info 62 | 63 | checks: format lint test 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Open Image Models 2 | 3 | [![Actions status](https://github.com/ankandrew/open-image-models/actions/workflows/main.yaml/badge.svg)](https://github.com/ankandrew/open-image-models/actions) 4 | [![GitHub version](https://img.shields.io/github/v/release/ankandrew/open-image-models)](https://github.com/ankandrew/open-image-models/releases) 5 | [![image](https://img.shields.io/pypi/pyversions/open-image-models.svg)](https://pypi.python.org/pypi/open-image-models) 6 | [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) 7 | [![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg)](https://ankandrew.github.io/open-image-models/) 8 | [![Pylint](https://img.shields.io/badge/linting-pylint-yellowgreen)](https://github.com/pylint-dev/pylint) 9 | [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/) 10 | [![ONNX Model](https://img.shields.io/badge/model-ONNX-blue?logo=onnx&logoColor=white)](https://onnx.ai/) 11 | [![Hugging Face Spaces](https://img.shields.io/badge/🤗%20Hugging%20Face-Spaces-orange)](https://huggingface.co/spaces/ankandrew/open-image-models) 12 | ![License](https://img.shields.io/github/license/ankandrew/open-image-models) 13 | 14 | 15 | * [Open Image Models](#open-image-models) 16 | * [Introduction](#introduction) 17 | * [Features](#features) 18 | * [Installation](#installation) 19 | * [Available Models](#available-models) 20 | * [Object Detection](#object-detection) 21 | * [Plate Detection](#plate-detection) 22 | * [Contributing](#contributing) 23 | * [Citation](#citation) 24 | 25 | 26 | --- 27 | 28 | ## Introduction 29 | 30 | **Ready-to-use** models for a range of **computer vision** tasks like **detection**, **classification**, and 31 | **more**. With **ONNX** support, you get **fast** and **accurate** results right out of the box. 32 | 33 | Easily integrate these models into your apps for **real-time** processing—ideal for edge devices, cloud setups, or 34 | production environments. In **one line of code**, you can have **powerful** model **inference** running! 35 | 36 | ```python 37 | from open_image_models import LicensePlateDetector 38 | 39 | lp_detector = LicensePlateDetector(detection_model="yolo-v9-t-256-license-plate-end2end") 40 | lp_detector.predict("path/to/license_plate_image.jpg") 41 | ``` 42 | 43 | ✨ That's it! Powerful license plate detection with just a few lines of code. 44 | 45 | ## Features 46 | 47 | - 🚀 Pre-trained: Models are **ready** for immediate use, no additional training required. 48 | - 🌟 ONNX: Cross-platform support for **fast inference** on both CPU and GPU environments. 49 | - ⚡ Performance: Optimized for both speed and accuracy, ensuring efficient **real-time** applications. 50 | - 💻 Simple API: Power up your applications with robust model inference in just one line of code. 51 | 52 | ## Installation 53 | 54 | To install open-image-models via pip, use the following command: 55 | 56 | ```shell 57 | pip install open-image-models 58 | ``` 59 | 60 | ## Available Models 61 | 62 | ### Object Detection 63 | 64 | #### Plate Detection 65 | 66 | ![](https://raw.githubusercontent.com/ankandrew/LocalizadorPatentes/2e765012f69c4fbd8decf998e61ed136004ced24/extra/demo_localizador.gif) 67 | 68 | | Model | Image Size | Precision (P) | Recall (R) | mAP50 | mAP50-95 | 69 | |:-------------------------------------:|------------|---------------|------------|-------|----------| 70 | | `yolo-v9-s-608-license-plate-end2end` | 608 | 0.957 | 0.917 | 0.966 | 0.772 | 71 | | `yolo-v9-t-640-license-plate-end2end` | 640 | 0.966 | 0.896 | 0.958 | 0.758 | 72 | | `yolo-v9-t-512-license-plate-end2end` | 512 | 0.955 | 0.901 | 0.948 | 0.724 | 73 | | `yolo-v9-t-416-license-plate-end2end` | 416 | 0.94 | 0.894 | 0.94 | 0.702 | 74 | | `yolo-v9-t-384-license-plate-end2end` | 384 | 0.942 | 0.863 | 0.92 | 0.687 | 75 | | `yolo-v9-t-256-license-plate-end2end` | 256 | 0.937 | 0.797 | 0.858 | 0.606 | 76 | 77 |
78 | Usage 79 | 80 | ```python 81 | import cv2 82 | from rich import print 83 | 84 | from open_image_models import LicensePlateDetector 85 | 86 | # Initialize the License Plate Detector with the pre-trained YOLOv9 model 87 | lp_detector = LicensePlateDetector(detection_model="yolo-v9-t-384-license-plate-end2end") 88 | 89 | # Load an image 90 | image_path = "path/to/license_plate_image.jpg" 91 | image = cv2.imread(image_path) 92 | 93 | # Perform license plate detection 94 | detections = lp_detector.predict(image) 95 | print(detections) 96 | 97 | # Benchmark the model performance 98 | lp_detector.show_benchmark(num_runs=1000) 99 | 100 | # Display predictions on the image 101 | annotated_image = lp_detector.display_predictions(image) 102 | 103 | # Show the annotated image 104 | cv2.imshow("Annotated Image", annotated_image) 105 | cv2.waitKey(0) 106 | cv2.destroyAllWindows() 107 | ``` 108 | 109 |
110 | 111 | > [!TIP] 112 | > Checkout the [docs](https://ankandrew.github.io/open-image-models)! 113 | 114 | ## Contributing 115 | 116 | Contributions to the repo are greatly appreciated. Whether it's bug fixes, feature enhancements, or new models, 117 | your contributions are warmly welcomed. 118 | 119 | To start contributing or to begin development, you can follow these steps: 120 | 121 | 1. Clone repo 122 | ```shell 123 | git clone https://github.com/ankandrew/open-image-models.git 124 | ``` 125 | 2. Install all dependencies using [Poetry](https://python-poetry.org/docs/#installation): 126 | ```shell 127 | poetry install --all-extras 128 | ``` 129 | 3. To ensure your changes pass linting and tests before submitting a PR: 130 | ```shell 131 | make checks 132 | ``` 133 | 134 | ## Citation 135 | 136 | ``` 137 | @article{wang2024yolov9, 138 | title={{YOLOv9}: Learning What You Want to Learn Using Programmable Gradient Information}, 139 | author={Wang, Chien-Yao and Liao, Hong-Yuan Mark}, 140 | booktitle={arXiv preprint arXiv:2402.13616}, 141 | year={2024} 142 | } 143 | ``` 144 | -------------------------------------------------------------------------------- /assets/open-image-models-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankandrew/open-image-models/69007bba53eef71f19fecc083e412d8c4a7b6b88/assets/open-image-models-logo.png -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | Contributions to the repo are greatly appreciated. Whether it's bug fixes, feature enhancements, or new models, 2 | your contributions are warmly welcomed. 3 | 4 | To start contributing or to begin development, you can follow these steps: 5 | 6 | 1. Clone repo 7 | ```shell 8 | git clone https://github.com/ankandrew/open-image-models.git 9 | ``` 10 | 2. Install all dependencies using [Poetry](https://python-poetry.org/docs/#installation): 11 | ```shell 12 | poetry install --all-extras 13 | ``` 14 | 3. To ensure your changes pass linting and tests before submitting a PR: 15 | ```shell 16 | make checks 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Open Image Models 2 | 3 | **Ready-to-use** models for a range of **computer vision** tasks like **detection**, **classification**, and 4 | **more**. With **ONNX** support, you get **fast** and **accurate** results right out of the box. 5 | 6 | Easily integrate these models into your apps for **real-time** processing—ideal for edge devices, cloud setups, or 7 | production environments. In **one line of code**, you can have **powerful** model **inference** running! 8 | 9 | ```python 10 | from open_image_models import LicensePlateDetector 11 | 12 | lp_detector = LicensePlateDetector(detection_model="yolo-v9-t-256-license-plate-end2end") 13 | lp_detector.predict("path/to/license_plate_image.jpg") 14 | ``` 15 | 16 | ???+ info 17 | ✨ That’s it! Fast, accurate computer vision models with just one line of code. 18 | 19 | ## Features 20 | 21 | - 🚀 Pre-trained: Models are **ready** for immediate use, no additional training required. 22 | - 🌟 ONNX: Cross-platform support for **fast inference** on both CPU and GPU environments. 23 | - ⚡ Performance: Optimized for both speed and accuracy, ensuring efficient **real-time** applications. 24 | - 💻 Simple API: Power up your applications with robust model inference in just one line of code. 25 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | ### Installation 2 | 3 | ```shell 4 | pip install open-image-models 5 | ``` 6 | 7 | ???+ info 8 | Currently, **ONNX** framework is used to run the models. When installing `open-image-models`, the corresponding 9 | version **GPU**/**CPU** should be installed based on your machine architecture. 10 | -------------------------------------------------------------------------------- /docs/reference.md: -------------------------------------------------------------------------------- 1 | # 🛠 Pipelines Overview 2 | 3 | ## License Plate Detection 4 | 5 | 🚗 **License Plate Detection** allows you to detect and identify license plates in images using a specialized pipeline based on the YOLOv9 model. 6 | 7 | The `LicensePlateDetector` is specialized for license plate detection. It utilizes the **YOLOv9** object detection model to recognize license plates in images. 8 | 9 | ::: open_image_models.detection.pipeline.license_plate.LicensePlateDetector 10 | 11 | --- 12 | 13 | # Core API Documentation 14 | 15 | The `core` module provides base classes and protocols for object detection models, including essential data structures like `BoundingBox` and `DetectionResult`. 16 | 17 | ### 🔧 Core Components 18 | 19 | The following components are used across detection pipelines and models: 20 | 21 | - **`BoundingBox`**: Represents a bounding box for detected objects. 22 | - **`DetectionResult`**: Stores label, confidence, and bounding box for a detection. 23 | - **`ObjectDetector`**: Protocol defining essential methods like `predict`, `show_benchmark`, and `display_predictions`. 24 | 25 | ::: open_image_models.detection.core.base 26 | 27 | ::: open_image_models.detection.core.hub 28 | options: 29 | group_by_category: false 30 | members: 31 | - PlateDetectorModel 32 | 33 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: OpenImageModels 2 | site_author: ankandrew 3 | site_description: Pre-trained image models using ONNX for fast, out-of-the-box inference. 4 | repo_url: https://github.com/ankandrew/open-image-models 5 | theme: 6 | name: material 7 | features: 8 | - navigation.instant 9 | - navigation.instant.progress 10 | - search.suggest 11 | - navigation.sections 12 | - navigation.expand 13 | - search.highlight 14 | - content.code.copy 15 | palette: 16 | - scheme: default 17 | toggle: 18 | icon: material/lightbulb-outline 19 | name: Switch to dark mode 20 | - scheme: slate 21 | toggle: 22 | icon: material/lightbulb 23 | name: Switch to light mode 24 | nav: 25 | - Introduction: index.md 26 | - Installation: installation.md 27 | - Reference: reference.md 28 | - Contributing: contributing.md 29 | plugins: 30 | - search 31 | - mike: 32 | alias_type: symlink 33 | canonical_version: latest 34 | - mkdocstrings: 35 | handlers: 36 | python: 37 | paths: [ open_image_models ] 38 | options: 39 | members_order: source 40 | separate_signature: true 41 | filters: [ "!^_" ] 42 | docstring_options: 43 | ignore_init_summary: true 44 | show_signature: true 45 | show_source: true 46 | heading_level: 2 47 | show_root_full_path: false 48 | merge_init_into_class: true 49 | show_signature_annotations: true 50 | signature_crossrefs: true 51 | extra: 52 | version: 53 | provider: mike 54 | generator: false 55 | markdown_extensions: 56 | - admonition 57 | - pymdownx.highlight: 58 | anchor_linenums: true 59 | line_spans: __span 60 | pygments_lang_class: true 61 | - pymdownx.inlinehilite 62 | - pymdownx.snippets 63 | - pymdownx.details 64 | - pymdownx.superfences 65 | - toc: 66 | permalink: true 67 | title: Page contents 68 | -------------------------------------------------------------------------------- /open_image_models/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Open Image Models package. 3 | """ 4 | 5 | from open_image_models.detection.pipeline.license_plate import LicensePlateDetector 6 | 7 | __all__ = ["LicensePlateDetector"] 8 | -------------------------------------------------------------------------------- /open_image_models/detection/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankandrew/open-image-models/69007bba53eef71f19fecc083e412d8c4a7b6b88/open_image_models/detection/__init__.py -------------------------------------------------------------------------------- /open_image_models/detection/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankandrew/open-image-models/69007bba53eef71f19fecc083e412d8c4a7b6b88/open_image_models/detection/core/__init__.py -------------------------------------------------------------------------------- /open_image_models/detection/core/base.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Iterator 2 | from dataclasses import dataclass 3 | from typing import Any, Optional, Protocol 4 | 5 | import numpy as np 6 | 7 | 8 | @dataclass(frozen=True) 9 | class BoundingBox: 10 | """ 11 | Represents a bounding box with top-left and bottom-right coordinates. 12 | """ 13 | 14 | x1: int 15 | """X-coordinate of the top-left corner""" 16 | y1: int 17 | """Y-coordinate of the top-left corner""" 18 | x2: int 19 | """X-coordinate of the bottom-right corner""" 20 | y2: int 21 | """Y-coordinate of the bottom-right corner""" 22 | 23 | @property 24 | def width(self) -> int: 25 | """Returns the width of the bounding box.""" 26 | return self.x2 - self.x1 27 | 28 | @property 29 | def height(self) -> int: 30 | """Returns the height of the bounding box.""" 31 | return self.y2 - self.y1 32 | 33 | @property 34 | def area(self) -> int: 35 | """Returns the area of the bounding box.""" 36 | return self.width * self.height 37 | 38 | @property 39 | def center(self) -> tuple[float, float]: 40 | """ 41 | Returns the (x, y) coordinates of the center of the bounding box. 42 | """ 43 | cx = (self.x1 + self.x2) / 2.0 44 | cy = (self.y1 + self.y2) / 2.0 45 | 46 | return cx, cy 47 | 48 | def intersection(self, other: "BoundingBox") -> Optional["BoundingBox"]: 49 | """ 50 | Returns the intersection of this bounding box with another bounding box. If they do not intersect, returns None. 51 | """ 52 | x1 = max(self.x1, other.x1) 53 | y1 = max(self.y1, other.y1) 54 | x2 = min(self.x2, other.x2) 55 | y2 = min(self.y2, other.y2) 56 | 57 | if x2 > x1 and y2 > y1: 58 | return BoundingBox(x1, y1, x2, y2) 59 | 60 | return None 61 | 62 | def iou(self, other: "BoundingBox") -> float: 63 | """ 64 | Computes the Intersection-over-Union (IoU) between this bounding box and another bounding box. 65 | """ 66 | inter = self.intersection(other) 67 | 68 | if inter is None: 69 | return 0.0 70 | 71 | inter_area = inter.area 72 | union_area = self.area + other.area - inter_area 73 | return inter_area / union_area if union_area > 0 else 0.0 74 | 75 | def to_xywh(self) -> tuple[int, int, int, int]: 76 | """ 77 | Converts bounding box to (x, y, width, height) format, where (x, y) is the top-left corner. 78 | 79 | :returns: A tuple containing the top-left x and y coordinates, width, and height of the bounding box. 80 | """ 81 | return self.x1, self.y1, self.width, self.height 82 | 83 | def __iter__(self) -> Iterator[int]: 84 | """ 85 | Allows unpacking of the bounding box coordinates. 86 | 87 | :return: An iterator over the bounding box coordinates (x1, y1, x2, y2). 88 | """ 89 | return iter((self.x1, self.y1, self.x2, self.y2)) 90 | 91 | def clamp(self, max_width: int, max_height: int) -> "BoundingBox": 92 | """ 93 | Returns a new `BoundingBox` with coordinates clamped within the range [0, max_width] and [0, max_height]. 94 | 95 | :param max_width: The maximum width. 96 | :param max_height: The maximum height. 97 | :return: A new, clamped `BoundingBox`. 98 | """ 99 | return BoundingBox( 100 | x1=max(0, min(self.x1, max_width)), 101 | y1=max(0, min(self.y1, max_height)), 102 | x2=max(0, min(self.x2, max_width)), 103 | y2=max(0, min(self.y2, max_height)), 104 | ) 105 | 106 | def is_valid(self, frame_width: int, frame_height: int) -> bool: 107 | """ 108 | Checks if the bounding box is valid by ensuring that: 109 | 110 | 1. The coordinates are in the correct order (x1 < x2 and y1 < y2). 111 | 2. The bounding box lies entirely within the frame boundaries. 112 | 113 | :param frame_width: The width of the frame. 114 | :param frame_height: The height of the frame. 115 | :return: True if the bounding box is valid, False otherwise. 116 | """ 117 | return 0 <= self.x1 < self.x2 <= frame_width and 0 <= self.y1 < self.y2 <= frame_height 118 | 119 | 120 | @dataclass(frozen=True) 121 | class DetectionResult: 122 | """ 123 | Represents the result of an object detection. 124 | """ 125 | 126 | label: str 127 | """Detected object label""" 128 | confidence: float 129 | """Confidence score of the detection""" 130 | bounding_box: BoundingBox 131 | """Bounding box of the detected object""" 132 | 133 | @classmethod 134 | def from_detection_data( 135 | cls, 136 | bbox_data: tuple[int, int, int, int], 137 | confidence: float, 138 | class_id: str, 139 | ) -> "DetectionResult": 140 | """ 141 | Creates a `DetectionResult` instance from bounding box data, confidence, and a class label. 142 | 143 | :param bbox_data: A tuple containing bounding box coordinates (x1, y1, x2, y2). 144 | :param confidence: The detection confidence score. 145 | :param class_id: The detected class label as a string. 146 | :return: A `DetectionResult` instance. 147 | """ 148 | bounding_box = BoundingBox(*bbox_data) 149 | return cls(class_id, confidence, bounding_box) 150 | 151 | 152 | class ObjectDetector(Protocol): 153 | def predict(self, images: Any) -> list[DetectionResult] | list[list[DetectionResult]]: 154 | """ 155 | Perform object detection on one or multiple images. 156 | 157 | Args: 158 | images: A single image as a numpy array, a single image path as a string, a list of images as numpy arrays, 159 | or a list of image file paths. 160 | 161 | Returns: 162 | A list of DetectionResult for a single image input, 163 | or a list of lists of DetectionResult for multiple images. 164 | """ 165 | 166 | def show_benchmark(self, num_runs: int = 10) -> None: 167 | """ 168 | Display the benchmark results of the model with a single random image. 169 | 170 | Args: 171 | num_runs: Number of times to run inference on the image for averaging. 172 | 173 | Displays: 174 | Model information and benchmark results in a formatted table. 175 | """ 176 | 177 | def display_predictions(self, image: np.ndarray) -> np.ndarray: 178 | """ 179 | Run object detection on the input image and display the predictions on the image. 180 | 181 | Args: 182 | image: An input image as a numpy array. 183 | 184 | Returns: 185 | The image with bounding boxes and labels drawn on it. 186 | """ 187 | -------------------------------------------------------------------------------- /open_image_models/detection/core/hub.py: -------------------------------------------------------------------------------- 1 | """ 2 | Open Image Models HUB. 3 | """ 4 | 5 | import logging 6 | import pathlib 7 | import shutil 8 | import urllib.request 9 | from http import HTTPStatus 10 | from typing import Literal 11 | 12 | from tqdm.asyncio import tqdm 13 | 14 | from open_image_models.utils import safe_write 15 | 16 | BASE_URL: str = "https://github.com/ankandrew/open-image-models/releases/download" 17 | """Base URL where models will be fetched.""" 18 | PlateDetectorModel = Literal[ 19 | "yolo-v9-s-608-license-plate-end2end", 20 | "yolo-v9-t-640-license-plate-end2end", 21 | "yolo-v9-t-512-license-plate-end2end", 22 | "yolo-v9-t-416-license-plate-end2end", 23 | "yolo-v9-t-384-license-plate-end2end", 24 | "yolo-v9-t-256-license-plate-end2end", 25 | ] 26 | """Available ONNX models for doing detection.""" 27 | 28 | AVAILABLE_ONNX_MODELS: dict[PlateDetectorModel, str] = { 29 | # Plate Detection 30 | "yolo-v9-s-608-license-plate-end2end": f"{BASE_URL}/assets/yolo-v9-s-608-license-plates-end2end.onnx", 31 | "yolo-v9-t-640-license-plate-end2end": f"{BASE_URL}/assets/yolo-v9-t-640-license-plates-end2end.onnx", 32 | "yolo-v9-t-512-license-plate-end2end": f"{BASE_URL}/assets/yolo-v9-t-512-license-plates-end2end.onnx", 33 | "yolo-v9-t-416-license-plate-end2end": f"{BASE_URL}/assets/yolo-v9-t-416-license-plates-end2end.onnx", 34 | "yolo-v9-t-384-license-plate-end2end": f"{BASE_URL}/assets/yolo-v9-t-384-license-plates-end2end.onnx", 35 | "yolo-v9-t-256-license-plate-end2end": f"{BASE_URL}/assets/yolo-v9-t-256-license-plates-end2end.onnx", 36 | } 37 | """Available ONNX models for doing inference.""" 38 | MODEL_CACHE_DIR: pathlib.Path = pathlib.Path.home() / ".cache" / "open-image-models" 39 | """Default location where models will be stored.""" 40 | 41 | 42 | def _download_with_progress(url: str, filename: pathlib.Path) -> None: 43 | """ 44 | Download utility function with progress bar. 45 | 46 | :param url: URL of the model to download. 47 | :param filename: Where to save the model. 48 | """ 49 | with urllib.request.urlopen(url) as response, safe_write(filename, mode="wb") as out_file: 50 | if response.getcode() != HTTPStatus.OK: 51 | raise ValueError(f"Failed to download file from {url}. Status code: {response.status}") 52 | 53 | file_size = int(response.headers.get("Content-Length", 0)) 54 | desc = f"Downloading {filename.name}" 55 | 56 | with tqdm.wrapattr(out_file, "write", total=file_size, desc=desc) as f_out: 57 | shutil.copyfileobj(response, f_out) 58 | 59 | 60 | def download_model( 61 | model_name: PlateDetectorModel, 62 | save_directory: pathlib.Path | None = None, 63 | force_download: bool = False, 64 | ) -> pathlib.Path: 65 | """ 66 | Download a detection model to a given directory. 67 | 68 | :param model_name: Which model to download. 69 | :param save_directory: Directory to save the model. It should point to a folder. If not supplied, this will point 70 | to '~/.cache/' 71 | :param force_download: Force and download the model if it already exists in `save_directory`. 72 | :return: Path where the model lives. 73 | """ 74 | if model_name not in AVAILABLE_ONNX_MODELS: 75 | available_models = ", ".join(AVAILABLE_ONNX_MODELS.keys()) 76 | raise ValueError(f"Unknown model {model_name}. Use one of [{available_models}]") 77 | 78 | if save_directory is None: 79 | save_directory = MODEL_CACHE_DIR / model_name 80 | elif save_directory.is_file(): 81 | raise ValueError(f"Expected a directory, but got {save_directory}") 82 | 83 | save_directory.mkdir(parents=True, exist_ok=True) 84 | 85 | model_url = AVAILABLE_ONNX_MODELS[model_name] 86 | model_filename = save_directory / model_url.split("/")[-1] 87 | 88 | if not force_download and model_filename.is_file(): 89 | logging.info( 90 | "Skipping download of '%s' model, already exists at %s", 91 | model_name, 92 | save_directory, 93 | ) 94 | return model_filename 95 | 96 | # Download the model if not present or if we want to force the download 97 | if force_download or not model_filename.is_file(): 98 | logging.info("Downloading model to %s", model_filename) 99 | _download_with_progress(url=model_url, filename=model_filename) 100 | 101 | return model_filename 102 | -------------------------------------------------------------------------------- /open_image_models/detection/core/yolo_v9/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankandrew/open-image-models/69007bba53eef71f19fecc083e412d8c4a7b6b88/open_image_models/detection/core/yolo_v9/__init__.py -------------------------------------------------------------------------------- /open_image_models/detection/core/yolo_v9/inference.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import pathlib 4 | from collections.abc import Sequence 5 | from typing import Any, cast, overload 6 | 7 | import cv2 8 | import numpy as np 9 | import onnxruntime as ort 10 | from rich.console import Console 11 | from rich.panel import Panel 12 | from rich.table import Table 13 | from rich.text import Text 14 | 15 | from open_image_models.detection.core.base import DetectionResult, ObjectDetector 16 | from open_image_models.detection.core.yolo_v9.postprocess import convert_to_detection_result 17 | from open_image_models.detection.core.yolo_v9.preprocess import preprocess 18 | from open_image_models.utils import measure_time, set_seed 19 | 20 | # Setup logging 21 | LOGGER = logging.getLogger(__name__) 22 | LOGGER.setLevel(logging.INFO) 23 | 24 | 25 | class YoloV9ObjectDetector(ObjectDetector): 26 | """ 27 | YoloV9-specific ONNX inference class for performing object detection using the Yolo v9 ONNX model. 28 | """ 29 | 30 | def __init__( 31 | self, 32 | model_path: str | os.PathLike[str], 33 | class_labels: list[str], 34 | conf_thresh: float = 0.25, 35 | providers: Sequence[str | tuple[str, dict]] | None = None, 36 | sess_options: ort.SessionOptions = None, 37 | ) -> None: 38 | """ 39 | Initializes the YoloV9ObjectDetector with the specified detection model and inference device. 40 | 41 | Args: 42 | model_path: Path to the ONNX model file to use. 43 | class_labels: List of class labels corresponding to the class IDs. 44 | conf_thresh: Confidence threshold for filtering predictions. 45 | providers: Optional sequence of providers in order of decreasing precedence. If not specified, all available 46 | providers are used. 47 | sess_options: Advanced session options for ONNX Runtime. 48 | """ 49 | self.conf_thresh = conf_thresh 50 | self.class_labels = class_labels 51 | # Check if model path exists 52 | model_path = pathlib.Path(model_path) 53 | if not model_path.exists(): 54 | raise FileNotFoundError(f"ONNX model not found at '{model_path}'") 55 | self.model_name = model_path.stem 56 | # Use all available providers if none are specified 57 | providers = providers or ort.get_available_providers() 58 | self.model = ort.InferenceSession(str(model_path), providers=providers, sess_options=sess_options) 59 | # Get input and output names from the model 60 | self.input_name = self.model.get_inputs()[0].name 61 | self.output_name = self.model.get_outputs()[0].name 62 | # Validate model input shape for square images only 63 | _, _, h, w = self.model.get_inputs()[0].shape 64 | if h != w: 65 | raise ValueError(f"Model only supports square images, but received shape: {h}x{w}") 66 | self.img_size = h, w 67 | self.providers = providers 68 | LOGGER.info("Using ONNX Runtime with %s provider(s)", self.providers) 69 | 70 | @overload 71 | def predict(self, images: np.ndarray) -> list[DetectionResult]: ... 72 | 73 | @overload 74 | def predict(self, images: list[np.ndarray]) -> list[list[DetectionResult]]: ... 75 | 76 | @overload 77 | def predict(self, images: str) -> list[DetectionResult]: ... 78 | 79 | @overload 80 | def predict(self, images: list[str]) -> list[list[DetectionResult]]: ... 81 | 82 | @overload 83 | def predict(self, images: os.PathLike[str]) -> list[DetectionResult]: ... 84 | 85 | @overload 86 | def predict(self, images: list[os.PathLike[str]]) -> list[list[DetectionResult]]: ... 87 | 88 | def predict(self, images: Any) -> list[DetectionResult] | list[list[DetectionResult]]: 89 | """ 90 | Perform object detection on one or multiple images. 91 | 92 | Args: 93 | images: A single image as a numpy array, a single image path as a string, a list of images as numpy arrays, 94 | or a list of image file paths. 95 | 96 | Returns: 97 | A list of DetectionResult for a single image input, 98 | or a list of lists of DetectionResult for multiple images. 99 | """ 100 | # Check the type of input and process accordingly 101 | if isinstance(images, np.ndarray): 102 | # Single image array 103 | return self._predict(images) 104 | # Check if a single image path is provided as a string 105 | if isinstance(images, str | os.PathLike): 106 | # Try loading the image 107 | image = cv2.imread(str(images)) 108 | if image is None: 109 | raise ValueError(f"Failed to load image at path: {images}") 110 | # Predict for the single loaded image 111 | return self._predict(image) 112 | if isinstance(images, list): 113 | # List of images or image paths 114 | # TODO: Upload ONNX models with dynamic batch, so this is performed in truly batch inference fashion. 115 | # Dynamic batch inference hurts performance running locally too. 116 | # See also https://stackoverflow.com/a/76735504/4544940 117 | if all(isinstance(img, np.ndarray) for img in images): 118 | # List of image arrays 119 | images = cast(list[np.ndarray], images) 120 | return [self._predict(img) for img in images] 121 | if all(isinstance(img, str | os.PathLike) for img in images): 122 | # List of image paths 123 | loaded_images = [cv2.imread(str(img)) for img in images] 124 | # Check for any images that failed to load 125 | for idx, img in enumerate(loaded_images): 126 | if img is None: 127 | raise ValueError(f"Failed to load image at path: {images[idx]}") 128 | return [self._predict(img) for img in loaded_images] 129 | raise TypeError("List must contain either all numpy arrays or all image file paths.") 130 | raise TypeError("Input must be a numpy array, a list of numpy arrays, or a list of image file paths.") 131 | 132 | def _predict(self, image: np.ndarray) -> list[DetectionResult]: 133 | """ 134 | Perform object detection on a single image frame. 135 | 136 | This function takes an image in BGR format, runs it through the object detection model, 137 | and returns a list of detected objects, including their class labels and bounding boxes. 138 | 139 | Args: 140 | image: Input image frame in BGR format. 141 | 142 | Returns: 143 | A list of DetectionResult containing detected objects information. 144 | """ 145 | # Preprocess the image using YoloV9-specific preprocessing function 146 | inputs, ratio, (dw, dh) = preprocess(image, self.img_size) 147 | # Run inference 148 | try: 149 | predictions = self.model.run([self.output_name], {self.input_name: inputs})[0] 150 | # CoreML doesn't handle empty data scenario, so sometimes the end to end NMS might fail. 151 | # For more information see https://github.com/microsoft/onnxruntime/issues/20372 152 | # pylint: disable=broad-except 153 | except Exception as e: 154 | # Log a generic warning message with the exception details 155 | LOGGER.warning("An error occurred during model inference: %s", e) 156 | return [] 157 | # Convert raw predictions to a list of DetectionResult objects 158 | return convert_to_detection_result( 159 | predictions=predictions, 160 | class_labels=self.class_labels, 161 | ratio=ratio, 162 | padding=(dw, dh), 163 | score_threshold=self.conf_thresh, 164 | ) 165 | 166 | def show_benchmark(self, num_runs: int = 1_000): 167 | """ 168 | Display the model performance with a single random image. 169 | 170 | Args: 171 | num_runs: Number of times to run inference on the image for averaging. 172 | 173 | Displays: 174 | Model information and benchmark results in a formatted table. 175 | """ 176 | # Set seed for generating same image 177 | set_seed(1337) 178 | # Generate a single random image 179 | image = np.random.randint(0, 256, (*self.img_size, 3), dtype=np.uint8) 180 | # Warm-up phase 181 | self._warm_up(image, num_runs=100) 182 | # Measure performance 183 | total_time_ms = self._benchmark_inference(image, num_runs) 184 | avg_time_ms = total_time_ms / num_runs 185 | fps = 1_000 / avg_time_ms if avg_time_ms > 0 else float("inf") 186 | # Display model information and benchmark results 187 | self._display_benchmark_results(avg_time_ms, fps, num_runs) 188 | 189 | def _warm_up(self, image: np.ndarray, num_runs: int = 100): 190 | """ 191 | Warm-up phase to ensure any initial model setup is done before benchmarking. 192 | 193 | Args: 194 | image (np.ndarray): Sample image for model warm-up. 195 | num_runs (int, optional): Number of warm-up iterations. Default is 100. 196 | """ 197 | LOGGER.info("Starting model warm-up with %d runs...", num_runs) 198 | for _ in range(num_runs): 199 | self.predict(image) 200 | LOGGER.info("Model warm-up completed.") 201 | 202 | def _benchmark_inference(self, image: np.ndarray, num_runs: int) -> float: 203 | """ 204 | Measure the total inference time over a specified number of runs for benchmarking. 205 | 206 | Args: 207 | image (np.ndarray): Input image for inference benchmarking. 208 | num_runs (int): Number of benchmark iterations. 209 | 210 | Returns: 211 | float: Total inference time in milliseconds over all iterations. 212 | """ 213 | LOGGER.info("Starting benchmark with %d runs...", num_runs) 214 | total_time_ms = 0.0 215 | for _ in range(num_runs): 216 | with measure_time() as timer: 217 | self.predict(image) 218 | total_time_ms += timer() 219 | LOGGER.info("Benchmark completed.") 220 | return total_time_ms 221 | 222 | def _display_benchmark_results(self, avg_time_ms: float, fps: float, num_runs: int): 223 | """ 224 | Display benchmark results including average inference time and frames per second (FPS). 225 | 226 | This method presents the benchmarking results in a formatted table using the rich library, 227 | including the number of runs, average inference time, and the calculated FPS. 228 | 229 | Args: 230 | avg_time_ms (float): Average inference time in milliseconds. 231 | fps (float): Calculated frames per second based on average time. 232 | num_runs (int): Total number of benchmark runs. 233 | """ 234 | console = Console() 235 | # Printing model details outside the table 236 | model_info = Panel( 237 | Text(f"Model: {self.model_name}\nProvider: {self.providers}", style="bold green"), 238 | title="Model Information", 239 | border_style="bright_blue", 240 | expand=False, 241 | ) 242 | console.print(model_info) 243 | # Creating the results table 244 | table = Table(title="YoloV9 Object Detector Performance", border_style="bright_blue") 245 | table.add_column("Metric", justify="center", style="cyan", no_wrap=True) 246 | table.add_column("Value", justify="center", style="magenta") 247 | table.add_row("Number of Runs", str(num_runs)) 248 | table.add_row("Average Time (ms)", f"{avg_time_ms:.2f}") 249 | table.add_row("Frames Per Second (FPS)", f"{fps:.2f}") 250 | # Display the table 251 | console.print(table) 252 | 253 | def display_predictions(self, image: np.ndarray) -> np.ndarray: 254 | """ 255 | Run object detection on the input image and display the predictions on the image. 256 | 257 | Args: 258 | image: An input image as a numpy array. 259 | 260 | Returns: 261 | The image with bounding boxes and labels drawn on it. 262 | """ 263 | # Get the predictions 264 | detections: list[DetectionResult] = self.predict(image) 265 | # Draw predictions on the image 266 | for detection in detections: 267 | bbox = detection.bounding_box 268 | label = f"{detection.label}: {detection.confidence:.2f}" 269 | # Draw bounding box 270 | cv2.rectangle(image, (bbox.x1, bbox.y1), (bbox.x2, bbox.y2), (0, 255, 0), 2) 271 | # Calculate the position for the label text above the bounding box 272 | (text_width, text_height), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1) 273 | cv2.rectangle( 274 | image, 275 | (bbox.x1, bbox.y1 - text_height - baseline), 276 | (bbox.x1 + text_width, bbox.y1), 277 | (0, 255, 0), 278 | thickness=cv2.FILLED, 279 | ) 280 | cv2.putText( 281 | image, 282 | label, 283 | (bbox.x1, bbox.y1 - baseline), 284 | cv2.FONT_HERSHEY_SIMPLEX, 285 | 0.5, 286 | (0, 0, 0), 287 | 1, 288 | ) 289 | return image 290 | -------------------------------------------------------------------------------- /open_image_models/detection/core/yolo_v9/postprocess.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from open_image_models.detection.core.base import BoundingBox, DetectionResult 4 | 5 | 6 | def convert_to_detection_result( 7 | predictions: np.ndarray, 8 | class_labels: list[str], 9 | ratio: tuple[float, float], 10 | padding: tuple[float, float], 11 | score_threshold: float = 0.5, 12 | ) -> list[DetectionResult]: 13 | """ 14 | Convert raw model output into a list of DetectionResult objects, with adjustments 15 | for padding and scaling to the original image size. 16 | 17 | Args: 18 | predictions: The concatenated array of the form 19 | [X, selected_boxes, selected_categories, selected_scores]. 20 | class_labels: List of class labels corresponding to the class IDs. 21 | ratio: Scaling ratio used during preprocessing. 22 | padding: Tuple of padding values (dw, dh) added during preprocessing. 23 | score_threshold: Minimum confidence score to include a detection result. 24 | 25 | Returns: 26 | A list of DetectionResult objects. 27 | """ 28 | results = [] 29 | # Extract bounding box coordinates 30 | bboxes = predictions[:, 1:5] 31 | # Convert to int for indexing class labels 32 | class_ids = predictions[:, 5].astype(int) 33 | # Confidence scores 34 | scores = predictions[:, 6] 35 | 36 | for bbox, class_id, score in zip(bboxes, class_ids, scores, strict=False): 37 | # Only include results that meet the score threshold 38 | if score < score_threshold: 39 | continue 40 | 41 | # Adjust bounding box from scaled image back to original image size 42 | bbox[0] = (bbox[0] - padding[0]) / ratio[0] 43 | bbox[1] = (bbox[1] - padding[1]) / ratio[1] 44 | bbox[2] = (bbox[2] - padding[0]) / ratio[0] 45 | bbox[3] = (bbox[3] - padding[1]) / ratio[1] 46 | 47 | # Map class_id to label if available 48 | label = class_labels[class_id] if class_id < len(class_labels) else str(class_id) 49 | 50 | # Create BoundingBox and DetectionResult instances 51 | bounding_box = BoundingBox(x1=int(bbox[0]), y1=int(bbox[1]), x2=int(bbox[2]), y2=int(bbox[3])) 52 | detection_result = DetectionResult( 53 | label=label, 54 | confidence=float(score), 55 | bounding_box=bounding_box, 56 | ) 57 | results.append(detection_result) 58 | 59 | return results 60 | -------------------------------------------------------------------------------- /open_image_models/detection/core/yolo_v9/preprocess.py: -------------------------------------------------------------------------------- 1 | """ 2 | Preprocess functions for YOLOv9. 3 | """ 4 | 5 | import cv2 6 | import numpy as np 7 | 8 | 9 | def preprocess(img: np.ndarray, img_size: tuple[int, int] | int): 10 | """ 11 | Preprocess the input image for model inference. 12 | 13 | :param img: Input image in BGR format. 14 | :param img_size: Desired size to resize the image. 15 | :return: Preprocessed image tensor, resize ratio, and padding (dw, dh). 16 | """ 17 | # Resize the input image to match training format 18 | im, ratio, (dw, dh) = letterbox(img, img_size) 19 | # HWC to CHW, BGR to RGB 20 | im = im.transpose((2, 0, 1))[::-1] 21 | # 0 - 255 to 0.0 - 1.0 22 | im = im / 255.0 23 | # Model precision is FP32 24 | im = im.astype(np.float32) 25 | # Add batch dimension 26 | im = np.expand_dims(im, 0) 27 | return im, ratio, (dw, dh) 28 | 29 | 30 | def letterbox( 31 | im: np.ndarray, 32 | new_shape: tuple[int, int] | int = (640, 640), 33 | color: tuple[int, int, int] = (114, 114, 114), 34 | scaleup: bool = True, 35 | ) -> tuple[np.ndarray, tuple[float, float], tuple[float, float]]: 36 | """ 37 | Simplified letterbox function with fixed behavior for YOLOv9 preprocessing. 38 | 39 | Resizes and pads the input image to the desired size while maintaining aspect ratio. 40 | """ 41 | shape = im.shape[:2] # current shape [height, width] 42 | 43 | # Convert integer new_shape to a tuple (new_shape, new_shape) 44 | if isinstance(new_shape, int): 45 | new_shape = (new_shape, new_shape) 46 | 47 | # Calculate the scaling ratio and resize the image 48 | r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) 49 | if not scaleup: 50 | r = min(r, 1.0) 51 | 52 | # Calculate new unpadded dimensions and padding 53 | new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) 54 | dw = (new_shape[1] - new_unpad[0]) / 2 # divide padding into 2 sides 55 | dh = (new_shape[0] - new_unpad[1]) / 2 56 | 57 | # Resize the image to the new unpadded dimensions 58 | if shape[::-1] != new_unpad: 59 | im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR) 60 | 61 | # Add padding to maintain the new shape with the specified color 62 | top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1)) 63 | left, right = int(round(dw - 0.1)), int(round(dw + 0.1)) 64 | im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border 65 | 66 | return im, (r, r), (dw, dh) 67 | -------------------------------------------------------------------------------- /open_image_models/detection/pipeline/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankandrew/open-image-models/69007bba53eef71f19fecc083e412d8c4a7b6b88/open_image_models/detection/pipeline/__init__.py -------------------------------------------------------------------------------- /open_image_models/detection/pipeline/license_plate.py: -------------------------------------------------------------------------------- 1 | """ 2 | License Plate object detection pipeline. 3 | """ 4 | 5 | import logging 6 | import os 7 | from collections.abc import Sequence 8 | from typing import Any, overload 9 | 10 | import numpy as np 11 | import onnxruntime as ort 12 | 13 | from open_image_models.detection.core.base import DetectionResult 14 | from open_image_models.detection.core.hub import PlateDetectorModel, download_model 15 | from open_image_models.detection.core.yolo_v9.inference import YoloV9ObjectDetector 16 | 17 | # Setup logging 18 | LOGGER = logging.getLogger(__name__) 19 | LOGGER.setLevel(logging.INFO) 20 | 21 | 22 | class LicensePlateDetector(YoloV9ObjectDetector): 23 | """ 24 | Specialized detector for license plates using YoloV9 model. 25 | Inherits from YoloV9ObjectDetector and sets up license plate specific configuration. 26 | """ 27 | 28 | def __init__( 29 | self, 30 | detection_model: PlateDetectorModel, 31 | conf_thresh: float = 0.25, 32 | providers: Sequence[str | tuple[str, dict]] | None = None, 33 | sess_options: ort.SessionOptions = None, 34 | ) -> None: 35 | """ 36 | Initializes the LicensePlateDetector with the specified detection model and inference device. 37 | 38 | Args: 39 | detection_model: Detection model to use, see `PlateDetectorModel`. 40 | conf_thresh: Confidence threshold for filtering predictions. 41 | providers: Optional sequence of providers in order of decreasing precedence. If not specified, all available 42 | providers are used. 43 | sess_options: Advanced session options for ONNX Runtime. 44 | """ 45 | # Download model if needed 46 | detector_model_path = download_model(detection_model) 47 | super().__init__( 48 | model_path=detector_model_path, 49 | conf_thresh=conf_thresh, 50 | class_labels=["License Plate"], 51 | providers=providers, 52 | sess_options=sess_options, 53 | ) 54 | LOGGER.info("Initialized LicensePlateDetector with model %s", detector_model_path) 55 | 56 | # pylint: disable=duplicate-code 57 | @overload 58 | def predict(self, images: np.ndarray) -> list[DetectionResult]: ... 59 | 60 | @overload 61 | def predict(self, images: list[np.ndarray]) -> list[list[DetectionResult]]: ... 62 | 63 | @overload 64 | def predict(self, images: str) -> list[DetectionResult]: ... 65 | 66 | @overload 67 | def predict(self, images: list[str]) -> list[list[DetectionResult]]: ... 68 | 69 | @overload 70 | def predict(self, images: os.PathLike[str]) -> list[DetectionResult]: ... 71 | 72 | @overload 73 | def predict(self, images: list[os.PathLike[str]]) -> list[list[DetectionResult]]: ... 74 | 75 | def predict(self, images: Any) -> list[DetectionResult] | list[list[DetectionResult]]: 76 | """ 77 | Perform license plate detection on one or multiple images. 78 | 79 | This method is a specialized version of the `YoloV9ObjectDetector.predict` method, 80 | focusing on detecting license plates in images. 81 | 82 | Args: 83 | images: A single image as a numpy array, a single image path as a string, a list of images as numpy arrays, 84 | or a list of image file paths. 85 | 86 | Returns: 87 | A list of `DetectionResult` for a single image input, or a list of lists of `DetectionResult` for multiple 88 | images. 89 | 90 | Example usage: 91 | 92 | ```python 93 | from open_image_models import LicensePlateDetector 94 | 95 | lp_detector = LicensePlateDetector(detection_model="yolo-v9-t-384-license-plate-end2end") 96 | lp_detector.predict("path/to/license_plate_image.jpg") 97 | ``` 98 | 99 | Raises: 100 | ValueError: If the image could not be loaded or processed. 101 | """ 102 | return super().predict(images) 103 | -------------------------------------------------------------------------------- /open_image_models/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankandrew/open-image-models/69007bba53eef71f19fecc083e412d8c4a7b6b88/open_image_models/py.typed -------------------------------------------------------------------------------- /open_image_models/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Common utilities used across the package. 3 | """ 4 | 5 | import logging 6 | import os 7 | import random 8 | import time 9 | from collections.abc import Callable, Iterator 10 | from contextlib import contextmanager 11 | from pathlib import Path 12 | from typing import IO, Any 13 | 14 | import numpy as np 15 | 16 | 17 | @contextmanager 18 | def log_time_taken(process_name: str) -> Iterator[None]: 19 | """ 20 | A concise context manager to time code snippets and log the result. 21 | 22 | Usage: 23 | with log_time_taken("process_name"): 24 | # Code snippet to be timed 25 | 26 | :param process_name: Name of the process being timed. 27 | """ 28 | time_start: float = time.perf_counter() 29 | try: 30 | yield 31 | finally: 32 | time_end: float = time.perf_counter() 33 | time_elapsed: float = time_end - time_start 34 | logger = logging.getLogger(__name__) 35 | logger.info("Computation time of '%s' = %.3fms", process_name, 1_000 * time_elapsed) 36 | 37 | 38 | @contextmanager 39 | def measure_time() -> Iterator[Callable[[], float]]: 40 | """ 41 | A context manager for measuring execution time (in milliseconds) within its code block. 42 | 43 | usage: 44 | with code_timer() as timer: 45 | # Code snippet to be timed 46 | print(f"Code took: {timer()} seconds") 47 | """ 48 | start_time = end_time = time.perf_counter() 49 | yield lambda: (end_time - start_time) * 1_000 50 | end_time = time.perf_counter() 51 | 52 | 53 | @contextmanager 54 | def safe_write( 55 | file: str | os.PathLike[str], 56 | mode: str = "wb", 57 | encoding: str | None = None, 58 | **kwargs: Any, 59 | ) -> Iterator[IO]: 60 | """ 61 | Context manager for safe file writing. 62 | 63 | Opens the specified file for writing and yields a file object. 64 | If an exception occurs during writing, the file is removed before raising the exception. 65 | """ 66 | try: 67 | with open(file, mode, encoding=encoding, **kwargs) as f: 68 | yield f 69 | except Exception as e: 70 | Path(file).unlink(missing_ok=True) 71 | raise e 72 | 73 | 74 | def set_seed(seed: int = 7331): 75 | """ 76 | Set seed for common random number generators to ensure reproducibility. 77 | 78 | Args: 79 | seed: An integer seed value for random number generators. 80 | """ 81 | # Python's built-in random module 82 | random.seed(seed) 83 | # NumPy's random module 84 | np.random.seed(seed) 85 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "astroid" 5 | version = "3.3.8" 6 | description = "An abstract syntax tree for Python with inference support." 7 | optional = false 8 | python-versions = ">=3.9.0" 9 | files = [ 10 | {file = "astroid-3.3.8-py3-none-any.whl", hash = "sha256:187ccc0c248bfbba564826c26f070494f7bc964fd286b6d9fff4420e55de828c"}, 11 | {file = "astroid-3.3.8.tar.gz", hash = "sha256:a88c7994f914a4ea8572fac479459f4955eeccc877be3f2d959a33273b0cf40b"}, 12 | ] 13 | 14 | [package.dependencies] 15 | typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} 16 | 17 | [[package]] 18 | name = "babel" 19 | version = "2.16.0" 20 | description = "Internationalization utilities" 21 | optional = true 22 | python-versions = ">=3.8" 23 | files = [ 24 | {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, 25 | {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, 26 | ] 27 | 28 | [package.extras] 29 | dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] 30 | 31 | [[package]] 32 | name = "certifi" 33 | version = "2024.12.14" 34 | description = "Python package for providing Mozilla's CA Bundle." 35 | optional = false 36 | python-versions = ">=3.6" 37 | files = [ 38 | {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, 39 | {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, 40 | ] 41 | 42 | [[package]] 43 | name = "charset-normalizer" 44 | version = "3.4.1" 45 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 46 | optional = false 47 | python-versions = ">=3.7" 48 | files = [ 49 | {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, 50 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, 51 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, 52 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, 53 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, 54 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, 55 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, 56 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, 57 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, 58 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, 59 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, 60 | {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, 61 | {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, 62 | {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, 63 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, 64 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, 65 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, 66 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, 67 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, 68 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, 69 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, 70 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, 71 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, 72 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, 73 | {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, 74 | {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, 75 | {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, 76 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, 77 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, 78 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, 79 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, 80 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, 81 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, 82 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, 83 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, 84 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, 85 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, 86 | {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, 87 | {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, 88 | {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, 89 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, 90 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, 91 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, 92 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, 93 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, 94 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, 95 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, 96 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, 97 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, 98 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, 99 | {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, 100 | {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, 101 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, 102 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, 103 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, 104 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, 105 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, 106 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, 107 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, 108 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, 109 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, 110 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, 111 | {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, 112 | {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, 113 | {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, 114 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, 115 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, 116 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, 117 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, 118 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, 119 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, 120 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, 121 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, 122 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, 123 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, 124 | {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, 125 | {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, 126 | {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, 127 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, 128 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, 129 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, 130 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, 131 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, 132 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, 133 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, 134 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, 135 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, 136 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, 137 | {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, 138 | {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, 139 | {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, 140 | {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, 141 | ] 142 | 143 | [[package]] 144 | name = "click" 145 | version = "8.1.8" 146 | description = "Composable command line interface toolkit" 147 | optional = true 148 | python-versions = ">=3.7" 149 | files = [ 150 | {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, 151 | {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, 152 | ] 153 | 154 | [package.dependencies] 155 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 156 | 157 | [[package]] 158 | name = "colorama" 159 | version = "0.4.6" 160 | description = "Cross-platform colored terminal text." 161 | optional = false 162 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 163 | files = [ 164 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 165 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 166 | ] 167 | 168 | [[package]] 169 | name = "coloredlogs" 170 | version = "15.0.1" 171 | description = "Colored terminal output for Python's logging module" 172 | optional = false 173 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 174 | files = [ 175 | {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, 176 | {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, 177 | ] 178 | 179 | [package.dependencies] 180 | humanfriendly = ">=9.1" 181 | 182 | [package.extras] 183 | cron = ["capturer (>=2.4)"] 184 | 185 | [[package]] 186 | name = "dill" 187 | version = "0.3.9" 188 | description = "serialize all of Python" 189 | optional = false 190 | python-versions = ">=3.8" 191 | files = [ 192 | {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, 193 | {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, 194 | ] 195 | 196 | [package.extras] 197 | graph = ["objgraph (>=1.7.2)"] 198 | profile = ["gprof2dot (>=2022.7.29)"] 199 | 200 | [[package]] 201 | name = "exceptiongroup" 202 | version = "1.2.2" 203 | description = "Backport of PEP 654 (exception groups)" 204 | optional = false 205 | python-versions = ">=3.7" 206 | files = [ 207 | {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, 208 | {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, 209 | ] 210 | 211 | [package.extras] 212 | test = ["pytest (>=6)"] 213 | 214 | [[package]] 215 | name = "flatbuffers" 216 | version = "25.1.24" 217 | description = "The FlatBuffers serialization format for Python" 218 | optional = false 219 | python-versions = "*" 220 | files = [ 221 | {file = "flatbuffers-25.1.24-py2.py3-none-any.whl", hash = "sha256:1abfebaf4083117225d0723087ea909896a34e3fec933beedb490d595ba24145"}, 222 | {file = "flatbuffers-25.1.24.tar.gz", hash = "sha256:e0f7b7d806c0abdf166275492663130af40c11f89445045fbef0aa3c9a8643ad"}, 223 | ] 224 | 225 | [[package]] 226 | name = "ghp-import" 227 | version = "2.1.0" 228 | description = "Copy your docs directly to the gh-pages branch." 229 | optional = true 230 | python-versions = "*" 231 | files = [ 232 | {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, 233 | {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, 234 | ] 235 | 236 | [package.dependencies] 237 | python-dateutil = ">=2.8.1" 238 | 239 | [package.extras] 240 | dev = ["flake8", "markdown", "twine", "wheel"] 241 | 242 | [[package]] 243 | name = "griffe" 244 | version = "1.5.5" 245 | description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." 246 | optional = true 247 | python-versions = ">=3.9" 248 | files = [ 249 | {file = "griffe-1.5.5-py3-none-any.whl", hash = "sha256:2761b1e8876c6f1f9ab1af274df93ea6bbadd65090de5f38f4cb5cc84897c7dd"}, 250 | {file = "griffe-1.5.5.tar.gz", hash = "sha256:35ee5b38b93d6a839098aad0f92207e6ad6b70c3e8866c08ca669275b8cba585"}, 251 | ] 252 | 253 | [package.dependencies] 254 | colorama = ">=0.4" 255 | 256 | [[package]] 257 | name = "humanfriendly" 258 | version = "10.0" 259 | description = "Human friendly output for text interfaces using Python" 260 | optional = false 261 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 262 | files = [ 263 | {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, 264 | {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, 265 | ] 266 | 267 | [package.dependencies] 268 | pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} 269 | 270 | [[package]] 271 | name = "idna" 272 | version = "3.10" 273 | description = "Internationalized Domain Names in Applications (IDNA)" 274 | optional = false 275 | python-versions = ">=3.6" 276 | files = [ 277 | {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, 278 | {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, 279 | ] 280 | 281 | [package.extras] 282 | all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] 283 | 284 | [[package]] 285 | name = "importlib-metadata" 286 | version = "8.6.1" 287 | description = "Read metadata from Python packages" 288 | optional = true 289 | python-versions = ">=3.9" 290 | files = [ 291 | {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, 292 | {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, 293 | ] 294 | 295 | [package.dependencies] 296 | zipp = ">=3.20" 297 | 298 | [package.extras] 299 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] 300 | cover = ["pytest-cov"] 301 | doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 302 | enabler = ["pytest-enabler (>=2.2)"] 303 | perf = ["ipython"] 304 | test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] 305 | type = ["pytest-mypy"] 306 | 307 | [[package]] 308 | name = "importlib-resources" 309 | version = "6.5.2" 310 | description = "Read resources from Python packages" 311 | optional = true 312 | python-versions = ">=3.9" 313 | files = [ 314 | {file = "importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"}, 315 | {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, 316 | ] 317 | 318 | [package.extras] 319 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] 320 | cover = ["pytest-cov"] 321 | doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 322 | enabler = ["pytest-enabler (>=2.2)"] 323 | test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] 324 | type = ["pytest-mypy"] 325 | 326 | [[package]] 327 | name = "iniconfig" 328 | version = "2.0.0" 329 | description = "brain-dead simple config-ini parsing" 330 | optional = false 331 | python-versions = ">=3.7" 332 | files = [ 333 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 334 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 335 | ] 336 | 337 | [[package]] 338 | name = "isort" 339 | version = "5.13.2" 340 | description = "A Python utility / library to sort Python imports." 341 | optional = false 342 | python-versions = ">=3.8.0" 343 | files = [ 344 | {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, 345 | {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, 346 | ] 347 | 348 | [package.extras] 349 | colors = ["colorama (>=0.4.6)"] 350 | 351 | [[package]] 352 | name = "jinja2" 353 | version = "3.1.5" 354 | description = "A very fast and expressive template engine." 355 | optional = true 356 | python-versions = ">=3.7" 357 | files = [ 358 | {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, 359 | {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, 360 | ] 361 | 362 | [package.dependencies] 363 | MarkupSafe = ">=2.0" 364 | 365 | [package.extras] 366 | i18n = ["Babel (>=2.7)"] 367 | 368 | [[package]] 369 | name = "markdown" 370 | version = "3.7" 371 | description = "Python implementation of John Gruber's Markdown." 372 | optional = true 373 | python-versions = ">=3.8" 374 | files = [ 375 | {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, 376 | {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, 377 | ] 378 | 379 | [package.extras] 380 | docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] 381 | testing = ["coverage", "pyyaml"] 382 | 383 | [[package]] 384 | name = "markdown-it-py" 385 | version = "3.0.0" 386 | description = "Python port of markdown-it. Markdown parsing, done right!" 387 | optional = false 388 | python-versions = ">=3.8" 389 | files = [ 390 | {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, 391 | {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, 392 | ] 393 | 394 | [package.dependencies] 395 | mdurl = ">=0.1,<1.0" 396 | 397 | [package.extras] 398 | benchmarking = ["psutil", "pytest", "pytest-benchmark"] 399 | code-style = ["pre-commit (>=3.0,<4.0)"] 400 | compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] 401 | linkify = ["linkify-it-py (>=1,<3)"] 402 | plugins = ["mdit-py-plugins"] 403 | profiling = ["gprof2dot"] 404 | rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] 405 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] 406 | 407 | [[package]] 408 | name = "markupsafe" 409 | version = "3.0.2" 410 | description = "Safely add untrusted strings to HTML/XML markup." 411 | optional = true 412 | python-versions = ">=3.9" 413 | files = [ 414 | {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, 415 | {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, 416 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, 417 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, 418 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, 419 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, 420 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, 421 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, 422 | {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, 423 | {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, 424 | {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, 425 | {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, 426 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, 427 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, 428 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, 429 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, 430 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, 431 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, 432 | {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, 433 | {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, 434 | {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, 435 | {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, 436 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, 437 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, 438 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, 439 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, 440 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, 441 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, 442 | {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, 443 | {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, 444 | {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, 445 | {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, 446 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, 447 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, 448 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, 449 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, 450 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, 451 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, 452 | {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, 453 | {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, 454 | {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, 455 | {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, 456 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, 457 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, 458 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, 459 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, 460 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, 461 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, 462 | {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, 463 | {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, 464 | {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, 465 | {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, 466 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, 467 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, 468 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, 469 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, 470 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, 471 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, 472 | {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, 473 | {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, 474 | {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, 475 | ] 476 | 477 | [[package]] 478 | name = "mccabe" 479 | version = "0.7.0" 480 | description = "McCabe checker, plugin for flake8" 481 | optional = false 482 | python-versions = ">=3.6" 483 | files = [ 484 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 485 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 486 | ] 487 | 488 | [[package]] 489 | name = "mdurl" 490 | version = "0.1.2" 491 | description = "Markdown URL utilities" 492 | optional = false 493 | python-versions = ">=3.7" 494 | files = [ 495 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, 496 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, 497 | ] 498 | 499 | [[package]] 500 | name = "mergedeep" 501 | version = "1.3.4" 502 | description = "A deep merge function for 🐍." 503 | optional = true 504 | python-versions = ">=3.6" 505 | files = [ 506 | {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, 507 | {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, 508 | ] 509 | 510 | [[package]] 511 | name = "mike" 512 | version = "2.1.3" 513 | description = "Manage multiple versions of your MkDocs-powered documentation" 514 | optional = true 515 | python-versions = "*" 516 | files = [ 517 | {file = "mike-2.1.3-py3-none-any.whl", hash = "sha256:d90c64077e84f06272437b464735130d380703a76a5738b152932884c60c062a"}, 518 | {file = "mike-2.1.3.tar.gz", hash = "sha256:abd79b8ea483fb0275b7972825d3082e5ae67a41820f8d8a0dc7a3f49944e810"}, 519 | ] 520 | 521 | [package.dependencies] 522 | importlib-metadata = "*" 523 | importlib-resources = "*" 524 | jinja2 = ">=2.7" 525 | mkdocs = ">=1.0" 526 | pyparsing = ">=3.0" 527 | pyyaml = ">=5.1" 528 | pyyaml-env-tag = "*" 529 | verspec = "*" 530 | 531 | [package.extras] 532 | dev = ["coverage", "flake8 (>=3.0)", "flake8-quotes", "shtab"] 533 | test = ["coverage", "flake8 (>=3.0)", "flake8-quotes", "shtab"] 534 | 535 | [[package]] 536 | name = "mkdocs" 537 | version = "1.6.1" 538 | description = "Project documentation with Markdown." 539 | optional = true 540 | python-versions = ">=3.8" 541 | files = [ 542 | {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, 543 | {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, 544 | ] 545 | 546 | [package.dependencies] 547 | click = ">=7.0" 548 | colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} 549 | ghp-import = ">=1.0" 550 | jinja2 = ">=2.11.1" 551 | markdown = ">=3.3.6" 552 | markupsafe = ">=2.0.1" 553 | mergedeep = ">=1.3.4" 554 | mkdocs-get-deps = ">=0.2.0" 555 | packaging = ">=20.5" 556 | pathspec = ">=0.11.1" 557 | pyyaml = ">=5.1" 558 | pyyaml-env-tag = ">=0.1" 559 | watchdog = ">=2.0" 560 | 561 | [package.extras] 562 | i18n = ["babel (>=2.9.0)"] 563 | min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] 564 | 565 | [[package]] 566 | name = "mkdocs-autorefs" 567 | version = "1.3.0" 568 | description = "Automatically link across pages in MkDocs." 569 | optional = true 570 | python-versions = ">=3.9" 571 | files = [ 572 | {file = "mkdocs_autorefs-1.3.0-py3-none-any.whl", hash = "sha256:d180f9778a04e78b7134e31418f238bba56f56d6a8af97873946ff661befffb3"}, 573 | {file = "mkdocs_autorefs-1.3.0.tar.gz", hash = "sha256:6867764c099ace9025d6ac24fd07b85a98335fbd30107ef01053697c8f46db61"}, 574 | ] 575 | 576 | [package.dependencies] 577 | Markdown = ">=3.3" 578 | markupsafe = ">=2.0.1" 579 | mkdocs = ">=1.1" 580 | 581 | [[package]] 582 | name = "mkdocs-get-deps" 583 | version = "0.2.0" 584 | description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" 585 | optional = true 586 | python-versions = ">=3.8" 587 | files = [ 588 | {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, 589 | {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, 590 | ] 591 | 592 | [package.dependencies] 593 | mergedeep = ">=1.3.4" 594 | platformdirs = ">=2.2.0" 595 | pyyaml = ">=5.1" 596 | 597 | [[package]] 598 | name = "mkdocs-material" 599 | version = "9.5.50" 600 | description = "Documentation that simply works" 601 | optional = true 602 | python-versions = ">=3.8" 603 | files = [ 604 | {file = "mkdocs_material-9.5.50-py3-none-any.whl", hash = "sha256:f24100f234741f4d423a9d672a909d859668a4f404796be3cf035f10d6050385"}, 605 | {file = "mkdocs_material-9.5.50.tar.gz", hash = "sha256:ae5fe16f3d7c9ccd05bb6916a7da7420cf99a9ce5e33debd9d40403a090d5825"}, 606 | ] 607 | 608 | [package.dependencies] 609 | babel = ">=2.10,<3.0" 610 | colorama = ">=0.4,<1.0" 611 | jinja2 = ">=3.0,<4.0" 612 | markdown = ">=3.2,<4.0" 613 | mkdocs = ">=1.6,<2.0" 614 | mkdocs-material-extensions = ">=1.3,<2.0" 615 | paginate = ">=0.5,<1.0" 616 | pygments = ">=2.16,<3.0" 617 | pymdown-extensions = ">=10.2,<11.0" 618 | regex = ">=2022.4" 619 | requests = ">=2.26,<3.0" 620 | 621 | [package.extras] 622 | git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] 623 | imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] 624 | recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] 625 | 626 | [[package]] 627 | name = "mkdocs-material-extensions" 628 | version = "1.3.1" 629 | description = "Extension pack for Python Markdown and MkDocs Material." 630 | optional = true 631 | python-versions = ">=3.8" 632 | files = [ 633 | {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, 634 | {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, 635 | ] 636 | 637 | [[package]] 638 | name = "mkdocstrings" 639 | version = "0.27.0" 640 | description = "Automatic documentation from sources, for MkDocs." 641 | optional = true 642 | python-versions = ">=3.9" 643 | files = [ 644 | {file = "mkdocstrings-0.27.0-py3-none-any.whl", hash = "sha256:6ceaa7ea830770959b55a16203ac63da24badd71325b96af950e59fd37366332"}, 645 | {file = "mkdocstrings-0.27.0.tar.gz", hash = "sha256:16adca6d6b0a1f9e0c07ff0b02ced8e16f228a9d65a37c063ec4c14d7b76a657"}, 646 | ] 647 | 648 | [package.dependencies] 649 | click = ">=7.0" 650 | Jinja2 = ">=2.11.1" 651 | Markdown = ">=3.6" 652 | MarkupSafe = ">=1.1" 653 | mkdocs = ">=1.4" 654 | mkdocs-autorefs = ">=1.2" 655 | mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} 656 | platformdirs = ">=2.2" 657 | pymdown-extensions = ">=6.3" 658 | 659 | [package.extras] 660 | crystal = ["mkdocstrings-crystal (>=0.3.4)"] 661 | python = ["mkdocstrings-python (>=0.5.2)"] 662 | python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] 663 | 664 | [[package]] 665 | name = "mkdocstrings-python" 666 | version = "1.13.0" 667 | description = "A Python handler for mkdocstrings." 668 | optional = true 669 | python-versions = ">=3.9" 670 | files = [ 671 | {file = "mkdocstrings_python-1.13.0-py3-none-any.whl", hash = "sha256:b88bbb207bab4086434743849f8e796788b373bd32e7bfefbf8560ac45d88f97"}, 672 | {file = "mkdocstrings_python-1.13.0.tar.gz", hash = "sha256:2dbd5757e8375b9720e81db16f52f1856bf59905428fd7ef88005d1370e2f64c"}, 673 | ] 674 | 675 | [package.dependencies] 676 | griffe = ">=0.49" 677 | mkdocs-autorefs = ">=1.2" 678 | mkdocstrings = ">=0.26" 679 | 680 | [[package]] 681 | name = "mpmath" 682 | version = "1.3.0" 683 | description = "Python library for arbitrary-precision floating-point arithmetic" 684 | optional = false 685 | python-versions = "*" 686 | files = [ 687 | {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, 688 | {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, 689 | ] 690 | 691 | [package.extras] 692 | develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] 693 | docs = ["sphinx"] 694 | gmpy = ["gmpy2 (>=2.1.0a4)"] 695 | tests = ["pytest (>=4.6)"] 696 | 697 | [[package]] 698 | name = "mypy" 699 | version = "1.14.1" 700 | description = "Optional static typing for Python" 701 | optional = false 702 | python-versions = ">=3.8" 703 | files = [ 704 | {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, 705 | {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, 706 | {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, 707 | {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"}, 708 | {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, 709 | {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, 710 | {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, 711 | {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, 712 | {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, 713 | {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"}, 714 | {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, 715 | {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, 716 | {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, 717 | {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, 718 | {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, 719 | {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"}, 720 | {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, 721 | {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, 722 | {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, 723 | {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, 724 | {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, 725 | {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"}, 726 | {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, 727 | {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, 728 | {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, 729 | {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, 730 | {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, 731 | {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"}, 732 | {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, 733 | {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, 734 | {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, 735 | {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, 736 | {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"}, 737 | {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"}, 738 | {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"}, 739 | {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"}, 740 | {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"}, 741 | {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"}, 742 | ] 743 | 744 | [package.dependencies] 745 | mypy_extensions = ">=1.0.0" 746 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 747 | typing_extensions = ">=4.6.0" 748 | 749 | [package.extras] 750 | dmypy = ["psutil (>=4.0)"] 751 | faster-cache = ["orjson"] 752 | install-types = ["pip"] 753 | mypyc = ["setuptools (>=50)"] 754 | reports = ["lxml"] 755 | 756 | [[package]] 757 | name = "mypy-extensions" 758 | version = "1.0.0" 759 | description = "Type system extensions for programs checked with the mypy type checker." 760 | optional = false 761 | python-versions = ">=3.5" 762 | files = [ 763 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 764 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 765 | ] 766 | 767 | [[package]] 768 | name = "numpy" 769 | version = "2.2.2" 770 | description = "Fundamental package for array computing in Python" 771 | optional = false 772 | python-versions = ">=3.10" 773 | files = [ 774 | {file = "numpy-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e"}, 775 | {file = "numpy-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e"}, 776 | {file = "numpy-2.2.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:40c7ff5da22cd391944a28c6a9c638a5eef77fcf71d6e3a79e1d9d9e82752715"}, 777 | {file = "numpy-2.2.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:995f9e8181723852ca458e22de5d9b7d3ba4da3f11cc1cb113f093b271d7965a"}, 778 | {file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78ea78450fd96a498f50ee096f69c75379af5138f7881a51355ab0e11286c97"}, 779 | {file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbe72d347fbc59f94124125e73fc4976a06927ebc503ec5afbfb35f193cd957"}, 780 | {file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8e6da5cffbbe571f93588f562ed130ea63ee206d12851b60819512dd3e1ba50d"}, 781 | {file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09d6a2032faf25e8d0cadde7fd6145118ac55d2740132c1d845f98721b5ebcfd"}, 782 | {file = "numpy-2.2.2-cp310-cp310-win32.whl", hash = "sha256:159ff6ee4c4a36a23fe01b7c3d07bd8c14cc433d9720f977fcd52c13c0098160"}, 783 | {file = "numpy-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:64bd6e1762cd7f0986a740fee4dff927b9ec2c5e4d9a28d056eb17d332158014"}, 784 | {file = "numpy-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:642199e98af1bd2b6aeb8ecf726972d238c9877b0f6e8221ee5ab945ec8a2189"}, 785 | {file = "numpy-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d9fc9d812c81e6168b6d405bf00b8d6739a7f72ef22a9214c4241e0dc70b323"}, 786 | {file = "numpy-2.2.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c7d1fd447e33ee20c1f33f2c8e6634211124a9aabde3c617687d8b739aa69eac"}, 787 | {file = "numpy-2.2.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:451e854cfae0febe723077bd0cf0a4302a5d84ff25f0bfece8f29206c7bed02e"}, 788 | {file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd249bc894af67cbd8bad2c22e7cbcd46cf87ddfca1f1289d1e7e54868cc785c"}, 789 | {file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02935e2c3c0c6cbe9c7955a8efa8908dd4221d7755644c59d1bba28b94fd334f"}, 790 | {file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a972cec723e0563aa0823ee2ab1df0cb196ed0778f173b381c871a03719d4826"}, 791 | {file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6d6a0910c3b4368d89dde073e630882cdb266755565155bc33520283b2d9df8"}, 792 | {file = "numpy-2.2.2-cp311-cp311-win32.whl", hash = "sha256:860fd59990c37c3ef913c3ae390b3929d005243acca1a86facb0773e2d8d9e50"}, 793 | {file = "numpy-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:da1eeb460ecce8d5b8608826595c777728cdf28ce7b5a5a8c8ac8d949beadcf2"}, 794 | {file = "numpy-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467"}, 795 | {file = "numpy-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a"}, 796 | {file = "numpy-2.2.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825"}, 797 | {file = "numpy-2.2.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37"}, 798 | {file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748"}, 799 | {file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0"}, 800 | {file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278"}, 801 | {file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba"}, 802 | {file = "numpy-2.2.2-cp312-cp312-win32.whl", hash = "sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283"}, 803 | {file = "numpy-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb"}, 804 | {file = "numpy-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc"}, 805 | {file = "numpy-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369"}, 806 | {file = "numpy-2.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd"}, 807 | {file = "numpy-2.2.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be"}, 808 | {file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84"}, 809 | {file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff"}, 810 | {file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0"}, 811 | {file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de"}, 812 | {file = "numpy-2.2.2-cp313-cp313-win32.whl", hash = "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9"}, 813 | {file = "numpy-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369"}, 814 | {file = "numpy-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391"}, 815 | {file = "numpy-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39"}, 816 | {file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317"}, 817 | {file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49"}, 818 | {file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2"}, 819 | {file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7"}, 820 | {file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb"}, 821 | {file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648"}, 822 | {file = "numpy-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4"}, 823 | {file = "numpy-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576"}, 824 | {file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b0531f0b0e07643eb089df4c509d30d72c9ef40defa53e41363eca8a8cc61495"}, 825 | {file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e9e82dcb3f2ebbc8cb5ce1102d5f1c5ed236bf8a11730fb45ba82e2841ec21df"}, 826 | {file = "numpy-2.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d4142eb40ca6f94539e4db929410f2a46052a0fe7a2c1c59f6179c39938d2a"}, 827 | {file = "numpy-2.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:356ca982c188acbfa6af0d694284d8cf20e95b1c3d0aefa8929376fea9146f60"}, 828 | {file = "numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f"}, 829 | ] 830 | 831 | [[package]] 832 | name = "onnxruntime" 833 | version = "1.20.1" 834 | description = "ONNX Runtime is a runtime accelerator for Machine Learning models" 835 | optional = false 836 | python-versions = "*" 837 | files = [ 838 | {file = "onnxruntime-1.20.1-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:e50ba5ff7fed4f7d9253a6baf801ca2883cc08491f9d32d78a80da57256a5439"}, 839 | {file = "onnxruntime-1.20.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b2908b50101a19e99c4d4e97ebb9905561daf61829403061c1adc1b588bc0de"}, 840 | {file = "onnxruntime-1.20.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d82daaec24045a2e87598b8ac2b417b1cce623244e80e663882e9fe1aae86410"}, 841 | {file = "onnxruntime-1.20.1-cp310-cp310-win32.whl", hash = "sha256:4c4b251a725a3b8cf2aab284f7d940c26094ecd9d442f07dd81ab5470e99b83f"}, 842 | {file = "onnxruntime-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:d3b616bb53a77a9463707bb313637223380fc327f5064c9a782e8ec69c22e6a2"}, 843 | {file = "onnxruntime-1.20.1-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:06bfbf02ca9ab5f28946e0f912a562a5f005301d0c419283dc57b3ed7969bb7b"}, 844 | {file = "onnxruntime-1.20.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6243e34d74423bdd1edf0ae9596dd61023b260f546ee17d701723915f06a9f7"}, 845 | {file = "onnxruntime-1.20.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5eec64c0269dcdb8d9a9a53dc4d64f87b9e0c19801d9321246a53b7eb5a7d1bc"}, 846 | {file = "onnxruntime-1.20.1-cp311-cp311-win32.whl", hash = "sha256:a19bc6e8c70e2485a1725b3d517a2319603acc14c1f1a017dda0afe6d4665b41"}, 847 | {file = "onnxruntime-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:8508887eb1c5f9537a4071768723ec7c30c28eb2518a00d0adcd32c89dea3221"}, 848 | {file = "onnxruntime-1.20.1-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:22b0655e2bf4f2161d52706e31f517a0e54939dc393e92577df51808a7edc8c9"}, 849 | {file = "onnxruntime-1.20.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f56e898815963d6dc4ee1c35fc6c36506466eff6d16f3cb9848cea4e8c8172"}, 850 | {file = "onnxruntime-1.20.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb71a814f66517a65628c9e4a2bb530a6edd2cd5d87ffa0af0f6f773a027d99e"}, 851 | {file = "onnxruntime-1.20.1-cp312-cp312-win32.whl", hash = "sha256:bd386cc9ee5f686ee8a75ba74037750aca55183085bf1941da8efcfe12d5b120"}, 852 | {file = "onnxruntime-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:19c2d843eb074f385e8bbb753a40df780511061a63f9def1b216bf53860223fb"}, 853 | {file = "onnxruntime-1.20.1-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:cc01437a32d0042b606f462245c8bbae269e5442797f6213e36ce61d5abdd8cc"}, 854 | {file = "onnxruntime-1.20.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb44b08e017a648924dbe91b82d89b0c105b1adcfe31e90d1dc06b8677ad37be"}, 855 | {file = "onnxruntime-1.20.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bda6aebdf7917c1d811f21d41633df00c58aff2bef2f598f69289c1f1dabc4b3"}, 856 | {file = "onnxruntime-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:d30367df7e70f1d9fc5a6a68106f5961686d39b54d3221f760085524e8d38e16"}, 857 | {file = "onnxruntime-1.20.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9158465745423b2b5d97ed25aa7740c7d38d2993ee2e5c3bfacb0c4145c49d8"}, 858 | {file = "onnxruntime-1.20.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0df6f2df83d61f46e842dbcde610ede27218947c33e994545a22333491e72a3b"}, 859 | ] 860 | 861 | [package.dependencies] 862 | coloredlogs = "*" 863 | flatbuffers = "*" 864 | numpy = ">=1.21.6" 865 | packaging = "*" 866 | protobuf = "*" 867 | sympy = "*" 868 | 869 | [[package]] 870 | name = "onnxruntime-gpu" 871 | version = "1.20.1" 872 | description = "ONNX Runtime is a runtime accelerator for Machine Learning models" 873 | optional = false 874 | python-versions = "*" 875 | files = [ 876 | {file = "onnxruntime_gpu-1.20.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5b4e1641db48752118dda353b8614c6d6570344062b58faea70b5350c41cf68"}, 877 | {file = "onnxruntime_gpu-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:5e2a1d659924f19425e6b2fde1f36c0998b398a8a9b76a2b266d4fa518cfe8ed"}, 878 | {file = "onnxruntime_gpu-1.20.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1795e8bc6f9a1488a4d51d242edc4232a5ae60ec44ab4d4b0a7c65b3d17fcbff"}, 879 | {file = "onnxruntime_gpu-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:1348e6a0af9e8f5e8e1cfc379b70356ea40497932f5bc7f858501fe7940794ff"}, 880 | {file = "onnxruntime_gpu-1.20.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1951f96cd534c6151721e552606d0d792ea6a4c3e57e2f10eed17cca8105e953"}, 881 | {file = "onnxruntime_gpu-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:696de465884480fad1deffd936bee05c6f10fdfe4f5fcef1927a71f5d28ed9ef"}, 882 | {file = "onnxruntime_gpu-1.20.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8337d7badb59b3f4af530ed6437a7471d48ab2e45f7b253a355c8db9cabee67c"}, 883 | {file = "onnxruntime_gpu-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:9f0cc7ccd8e75da510ddab65d891b1384442d1b161863c521d335d1a56e5aa40"}, 884 | {file = "onnxruntime_gpu-1.20.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:965d6ee002c114d310c8d38680f42f1c94603b3547bcc62956ff565d528caa88"}, 885 | ] 886 | 887 | [package.dependencies] 888 | coloredlogs = "*" 889 | flatbuffers = "*" 890 | numpy = ">=1.21.6" 891 | packaging = "*" 892 | protobuf = "*" 893 | sympy = "*" 894 | 895 | [[package]] 896 | name = "opencv-python" 897 | version = "4.11.0.86" 898 | description = "Wrapper package for OpenCV python bindings." 899 | optional = false 900 | python-versions = ">=3.6" 901 | files = [ 902 | {file = "opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4"}, 903 | {file = "opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a"}, 904 | {file = "opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66"}, 905 | {file = "opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202"}, 906 | {file = "opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d"}, 907 | {file = "opencv_python-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b"}, 908 | {file = "opencv_python-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec"}, 909 | ] 910 | 911 | [package.dependencies] 912 | numpy = [ 913 | {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""}, 914 | {version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""}, 915 | {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, 916 | {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, 917 | ] 918 | 919 | [[package]] 920 | name = "packaging" 921 | version = "24.2" 922 | description = "Core utilities for Python packages" 923 | optional = false 924 | python-versions = ">=3.8" 925 | files = [ 926 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, 927 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, 928 | ] 929 | 930 | [[package]] 931 | name = "paginate" 932 | version = "0.5.7" 933 | description = "Divides large result sets into pages for easier browsing" 934 | optional = true 935 | python-versions = "*" 936 | files = [ 937 | {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, 938 | {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, 939 | ] 940 | 941 | [package.extras] 942 | dev = ["pytest", "tox"] 943 | lint = ["black"] 944 | 945 | [[package]] 946 | name = "pathspec" 947 | version = "0.12.1" 948 | description = "Utility library for gitignore style pattern matching of file paths." 949 | optional = true 950 | python-versions = ">=3.8" 951 | files = [ 952 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 953 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 954 | ] 955 | 956 | [[package]] 957 | name = "platformdirs" 958 | version = "4.3.6" 959 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 960 | optional = false 961 | python-versions = ">=3.8" 962 | files = [ 963 | {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, 964 | {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, 965 | ] 966 | 967 | [package.extras] 968 | docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] 969 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] 970 | type = ["mypy (>=1.11.2)"] 971 | 972 | [[package]] 973 | name = "pluggy" 974 | version = "1.5.0" 975 | description = "plugin and hook calling mechanisms for python" 976 | optional = false 977 | python-versions = ">=3.8" 978 | files = [ 979 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 980 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 981 | ] 982 | 983 | [package.extras] 984 | dev = ["pre-commit", "tox"] 985 | testing = ["pytest", "pytest-benchmark"] 986 | 987 | [[package]] 988 | name = "protobuf" 989 | version = "5.29.3" 990 | description = "" 991 | optional = false 992 | python-versions = ">=3.8" 993 | files = [ 994 | {file = "protobuf-5.29.3-cp310-abi3-win32.whl", hash = "sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888"}, 995 | {file = "protobuf-5.29.3-cp310-abi3-win_amd64.whl", hash = "sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a"}, 996 | {file = "protobuf-5.29.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e"}, 997 | {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84"}, 998 | {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f"}, 999 | {file = "protobuf-5.29.3-cp38-cp38-win32.whl", hash = "sha256:84a57163a0ccef3f96e4b6a20516cedcf5bb3a95a657131c5c3ac62200d23252"}, 1000 | {file = "protobuf-5.29.3-cp38-cp38-win_amd64.whl", hash = "sha256:b89c115d877892a512f79a8114564fb435943b59067615894c3b13cd3e1fa107"}, 1001 | {file = "protobuf-5.29.3-cp39-cp39-win32.whl", hash = "sha256:0eb32bfa5219fc8d4111803e9a690658aa2e6366384fd0851064b963b6d1f2a7"}, 1002 | {file = "protobuf-5.29.3-cp39-cp39-win_amd64.whl", hash = "sha256:6ce8cc3389a20693bfde6c6562e03474c40851b44975c9b2bf6df7d8c4f864da"}, 1003 | {file = "protobuf-5.29.3-py3-none-any.whl", hash = "sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f"}, 1004 | {file = "protobuf-5.29.3.tar.gz", hash = "sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620"}, 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "pygments" 1009 | version = "2.19.1" 1010 | description = "Pygments is a syntax highlighting package written in Python." 1011 | optional = false 1012 | python-versions = ">=3.8" 1013 | files = [ 1014 | {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, 1015 | {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, 1016 | ] 1017 | 1018 | [package.extras] 1019 | windows-terminal = ["colorama (>=0.4.6)"] 1020 | 1021 | [[package]] 1022 | name = "pylint" 1023 | version = "3.3.3" 1024 | description = "python code static checker" 1025 | optional = false 1026 | python-versions = ">=3.9.0" 1027 | files = [ 1028 | {file = "pylint-3.3.3-py3-none-any.whl", hash = "sha256:26e271a2bc8bce0fc23833805a9076dd9b4d5194e2a02164942cb3cdc37b4183"}, 1029 | {file = "pylint-3.3.3.tar.gz", hash = "sha256:07c607523b17e6d16e2ae0d7ef59602e332caa762af64203c24b41c27139f36a"}, 1030 | ] 1031 | 1032 | [package.dependencies] 1033 | astroid = ">=3.3.8,<=3.4.0-dev0" 1034 | colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} 1035 | dill = [ 1036 | {version = ">=0.2", markers = "python_version < \"3.11\""}, 1037 | {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, 1038 | {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, 1039 | ] 1040 | isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" 1041 | mccabe = ">=0.6,<0.8" 1042 | platformdirs = ">=2.2.0" 1043 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 1044 | tomlkit = ">=0.10.1" 1045 | 1046 | [package.extras] 1047 | spelling = ["pyenchant (>=3.2,<4.0)"] 1048 | testutils = ["gitpython (>3)"] 1049 | 1050 | [[package]] 1051 | name = "pymdown-extensions" 1052 | version = "10.14.1" 1053 | description = "Extension pack for Python Markdown." 1054 | optional = true 1055 | python-versions = ">=3.8" 1056 | files = [ 1057 | {file = "pymdown_extensions-10.14.1-py3-none-any.whl", hash = "sha256:637951cbfbe9874ba28134fb3ce4b8bcadd6aca89ac4998ec29dcbafd554ae08"}, 1058 | {file = "pymdown_extensions-10.14.1.tar.gz", hash = "sha256:b65801996a0cd4f42a3110810c306c45b7313c09b0610a6f773730f2a9e3c96b"}, 1059 | ] 1060 | 1061 | [package.dependencies] 1062 | markdown = ">=3.6" 1063 | pyyaml = "*" 1064 | 1065 | [package.extras] 1066 | extra = ["pygments (>=2.19.1)"] 1067 | 1068 | [[package]] 1069 | name = "pyparsing" 1070 | version = "3.2.1" 1071 | description = "pyparsing module - Classes and methods to define and execute parsing grammars" 1072 | optional = true 1073 | python-versions = ">=3.9" 1074 | files = [ 1075 | {file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"}, 1076 | {file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"}, 1077 | ] 1078 | 1079 | [package.extras] 1080 | diagrams = ["jinja2", "railroad-diagrams"] 1081 | 1082 | [[package]] 1083 | name = "pyreadline3" 1084 | version = "3.5.4" 1085 | description = "A python implementation of GNU readline." 1086 | optional = false 1087 | python-versions = ">=3.8" 1088 | files = [ 1089 | {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"}, 1090 | {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"}, 1091 | ] 1092 | 1093 | [package.extras] 1094 | dev = ["build", "flake8", "mypy", "pytest", "twine"] 1095 | 1096 | [[package]] 1097 | name = "pytest" 1098 | version = "8.3.4" 1099 | description = "pytest: simple powerful testing with Python" 1100 | optional = false 1101 | python-versions = ">=3.8" 1102 | files = [ 1103 | {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, 1104 | {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, 1105 | ] 1106 | 1107 | [package.dependencies] 1108 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 1109 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 1110 | iniconfig = "*" 1111 | packaging = "*" 1112 | pluggy = ">=1.5,<2" 1113 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 1114 | 1115 | [package.extras] 1116 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 1117 | 1118 | [[package]] 1119 | name = "python-dateutil" 1120 | version = "2.9.0.post0" 1121 | description = "Extensions to the standard Python datetime module" 1122 | optional = true 1123 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 1124 | files = [ 1125 | {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, 1126 | {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, 1127 | ] 1128 | 1129 | [package.dependencies] 1130 | six = ">=1.5" 1131 | 1132 | [[package]] 1133 | name = "pyyaml" 1134 | version = "6.0.2" 1135 | description = "YAML parser and emitter for Python" 1136 | optional = true 1137 | python-versions = ">=3.8" 1138 | files = [ 1139 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, 1140 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, 1141 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, 1142 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, 1143 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, 1144 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, 1145 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, 1146 | {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, 1147 | {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, 1148 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, 1149 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, 1150 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, 1151 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, 1152 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, 1153 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, 1154 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, 1155 | {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, 1156 | {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, 1157 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, 1158 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, 1159 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, 1160 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, 1161 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, 1162 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, 1163 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, 1164 | {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, 1165 | {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, 1166 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, 1167 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, 1168 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, 1169 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, 1170 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, 1171 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, 1172 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, 1173 | {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, 1174 | {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, 1175 | {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, 1176 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, 1177 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, 1178 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, 1179 | {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, 1180 | {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, 1181 | {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, 1182 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, 1183 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, 1184 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, 1185 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, 1186 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, 1187 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, 1188 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, 1189 | {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, 1190 | {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, 1191 | {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, 1192 | ] 1193 | 1194 | [[package]] 1195 | name = "pyyaml-env-tag" 1196 | version = "0.1" 1197 | description = "A custom YAML tag for referencing environment variables in YAML files. " 1198 | optional = true 1199 | python-versions = ">=3.6" 1200 | files = [ 1201 | {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, 1202 | {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, 1203 | ] 1204 | 1205 | [package.dependencies] 1206 | pyyaml = "*" 1207 | 1208 | [[package]] 1209 | name = "regex" 1210 | version = "2024.11.6" 1211 | description = "Alternative regular expression module, to replace re." 1212 | optional = true 1213 | python-versions = ">=3.8" 1214 | files = [ 1215 | {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, 1216 | {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, 1217 | {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, 1218 | {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, 1219 | {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, 1220 | {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, 1221 | {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, 1222 | {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, 1223 | {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, 1224 | {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, 1225 | {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, 1226 | {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, 1227 | {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, 1228 | {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, 1229 | {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, 1230 | {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, 1231 | {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, 1232 | {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, 1233 | {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, 1234 | {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, 1235 | {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, 1236 | {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, 1237 | {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, 1238 | {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, 1239 | {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, 1240 | {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, 1241 | {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, 1242 | {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, 1243 | {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, 1244 | {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, 1245 | {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, 1246 | {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, 1247 | {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, 1248 | {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, 1249 | {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, 1250 | {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, 1251 | {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, 1252 | {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, 1253 | {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, 1254 | {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, 1255 | {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, 1256 | {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, 1257 | {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, 1258 | {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, 1259 | {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, 1260 | {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, 1261 | {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, 1262 | {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, 1263 | {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, 1264 | {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, 1265 | {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, 1266 | {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, 1267 | {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, 1268 | {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, 1269 | {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, 1270 | {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, 1271 | {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, 1272 | {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, 1273 | {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, 1274 | {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, 1275 | {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, 1276 | {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, 1277 | {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, 1278 | {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, 1279 | {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, 1280 | {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, 1281 | {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, 1282 | {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, 1283 | {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, 1284 | {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, 1285 | {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, 1286 | {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, 1287 | {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, 1288 | {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, 1289 | {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, 1290 | {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, 1291 | {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, 1292 | {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, 1293 | {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, 1294 | {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, 1295 | {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, 1296 | {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, 1297 | {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, 1298 | {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, 1299 | {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, 1300 | {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, 1301 | {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, 1302 | {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, 1303 | {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, 1304 | {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, 1305 | {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, 1306 | {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, 1307 | {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, 1308 | {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, 1309 | ] 1310 | 1311 | [[package]] 1312 | name = "requests" 1313 | version = "2.32.3" 1314 | description = "Python HTTP for Humans." 1315 | optional = false 1316 | python-versions = ">=3.8" 1317 | files = [ 1318 | {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, 1319 | {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, 1320 | ] 1321 | 1322 | [package.dependencies] 1323 | certifi = ">=2017.4.17" 1324 | charset-normalizer = ">=2,<4" 1325 | idna = ">=2.5,<4" 1326 | urllib3 = ">=1.21.1,<3" 1327 | 1328 | [package.extras] 1329 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 1330 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 1331 | 1332 | [[package]] 1333 | name = "rich" 1334 | version = "13.9.4" 1335 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 1336 | optional = false 1337 | python-versions = ">=3.8.0" 1338 | files = [ 1339 | {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, 1340 | {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, 1341 | ] 1342 | 1343 | [package.dependencies] 1344 | markdown-it-py = ">=2.2.0" 1345 | pygments = ">=2.13.0,<3.0.0" 1346 | typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} 1347 | 1348 | [package.extras] 1349 | jupyter = ["ipywidgets (>=7.5.1,<9)"] 1350 | 1351 | [[package]] 1352 | name = "ruff" 1353 | version = "0.11.3" 1354 | description = "An extremely fast Python linter and code formatter, written in Rust." 1355 | optional = false 1356 | python-versions = ">=3.7" 1357 | files = [ 1358 | {file = "ruff-0.11.3-py3-none-linux_armv6l.whl", hash = "sha256:cb893a5eedff45071d52565300a20cd4ac088869e156b25e0971cb98c06f5dd7"}, 1359 | {file = "ruff-0.11.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:58edd48af0e201e2f494789de80f5b2f2b46c9a2991a12ea031254865d5f6aa3"}, 1360 | {file = "ruff-0.11.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:520f6ade25cea98b2e5cb29eb0906f6a0339c6b8e28a024583b867f48295f1ed"}, 1361 | {file = "ruff-0.11.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ca4405a93ebbc05e924358f872efceb1498c3d52a989ddf9476712a5480b16"}, 1362 | {file = "ruff-0.11.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4341d38775a6be605ce7cd50e951b89de65cbd40acb0399f95b8e1524d604c8"}, 1363 | {file = "ruff-0.11.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72bf5b49e4b546f4bea6c05448ab71919b09cf75363adf5e3bf5276124afd31c"}, 1364 | {file = "ruff-0.11.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9fa791ee6c3629ba7f9ba2c8f2e76178b03f3eaefb920e426302115259819237"}, 1365 | {file = "ruff-0.11.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c81d3fe718f4d303aaa4ccdcd0f43e23bb2127da3353635f718394ca9b26721"}, 1366 | {file = "ruff-0.11.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4c38e9b6c01caaba46b6d8e732791f4c78389a9923319991d55b298017ce02"}, 1367 | {file = "ruff-0.11.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9686f5d1a2b4c918b5a6e9876bfe7f47498a990076624d41f57d17aadd02a4dd"}, 1368 | {file = "ruff-0.11.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4800ddc4764d42d8961ce4cb972bcf5cc2730d11cca3f11f240d9f7360460408"}, 1369 | {file = "ruff-0.11.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e63a2808879361aa9597d88d86380d8fb934953ef91f5ff3dafe18d9cb0b1e14"}, 1370 | {file = "ruff-0.11.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8f8b1c4ae62638cc220df440140c21469232d8f2cb7f5059f395f7f48dcdb59e"}, 1371 | {file = "ruff-0.11.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3ea2026be50f6b1fbedd2d1757d004e1e58bd0f414efa2a6fa01235468d4c82a"}, 1372 | {file = "ruff-0.11.3-py3-none-win32.whl", hash = "sha256:73d8b90d12674a0c6e98cd9e235f2dcad09d1a80e559a585eac994bb536917a3"}, 1373 | {file = "ruff-0.11.3-py3-none-win_amd64.whl", hash = "sha256:faf1bfb0a51fb3a82aa1112cb03658796acef978e37c7f807d3ecc50b52ecbf6"}, 1374 | {file = "ruff-0.11.3-py3-none-win_arm64.whl", hash = "sha256:67f8b68d7ab909f08af1fb601696925a89d65083ae2bb3ab286e572b5dc456aa"}, 1375 | {file = "ruff-0.11.3.tar.gz", hash = "sha256:8d5fcdb3bb359adc12b757ed832ee743993e7474b9de714bb9ea13c4a8458bf9"}, 1376 | ] 1377 | 1378 | [[package]] 1379 | name = "six" 1380 | version = "1.17.0" 1381 | description = "Python 2 and 3 compatibility utilities" 1382 | optional = true 1383 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 1384 | files = [ 1385 | {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, 1386 | {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, 1387 | ] 1388 | 1389 | [[package]] 1390 | name = "sympy" 1391 | version = "1.13.3" 1392 | description = "Computer algebra system (CAS) in Python" 1393 | optional = false 1394 | python-versions = ">=3.8" 1395 | files = [ 1396 | {file = "sympy-1.13.3-py3-none-any.whl", hash = "sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73"}, 1397 | {file = "sympy-1.13.3.tar.gz", hash = "sha256:b27fd2c6530e0ab39e275fc9b683895367e51d5da91baa8d3d64db2565fec4d9"}, 1398 | ] 1399 | 1400 | [package.dependencies] 1401 | mpmath = ">=1.1.0,<1.4" 1402 | 1403 | [package.extras] 1404 | dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] 1405 | 1406 | [[package]] 1407 | name = "tomli" 1408 | version = "2.2.1" 1409 | description = "A lil' TOML parser" 1410 | optional = false 1411 | python-versions = ">=3.8" 1412 | files = [ 1413 | {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, 1414 | {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, 1415 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, 1416 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, 1417 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, 1418 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, 1419 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, 1420 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, 1421 | {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, 1422 | {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, 1423 | {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, 1424 | {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, 1425 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, 1426 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, 1427 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, 1428 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, 1429 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, 1430 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, 1431 | {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, 1432 | {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, 1433 | {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, 1434 | {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, 1435 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, 1436 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, 1437 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, 1438 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, 1439 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, 1440 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, 1441 | {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, 1442 | {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, 1443 | {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, 1444 | {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, 1445 | ] 1446 | 1447 | [[package]] 1448 | name = "tomlkit" 1449 | version = "0.13.2" 1450 | description = "Style preserving TOML library" 1451 | optional = false 1452 | python-versions = ">=3.8" 1453 | files = [ 1454 | {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, 1455 | {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, 1456 | ] 1457 | 1458 | [[package]] 1459 | name = "tqdm" 1460 | version = "4.67.1" 1461 | description = "Fast, Extensible Progress Meter" 1462 | optional = false 1463 | python-versions = ">=3.7" 1464 | files = [ 1465 | {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, 1466 | {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, 1467 | ] 1468 | 1469 | [package.dependencies] 1470 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 1471 | 1472 | [package.extras] 1473 | dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] 1474 | discord = ["requests"] 1475 | notebook = ["ipywidgets (>=6)"] 1476 | slack = ["slack-sdk"] 1477 | telegram = ["requests"] 1478 | 1479 | [[package]] 1480 | name = "typing-extensions" 1481 | version = "4.12.2" 1482 | description = "Backported and Experimental Type Hints for Python 3.8+" 1483 | optional = false 1484 | python-versions = ">=3.8" 1485 | files = [ 1486 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 1487 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 1488 | ] 1489 | 1490 | [[package]] 1491 | name = "urllib3" 1492 | version = "2.3.0" 1493 | description = "HTTP library with thread-safe connection pooling, file post, and more." 1494 | optional = false 1495 | python-versions = ">=3.9" 1496 | files = [ 1497 | {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, 1498 | {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, 1499 | ] 1500 | 1501 | [package.extras] 1502 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 1503 | h2 = ["h2 (>=4,<5)"] 1504 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 1505 | zstd = ["zstandard (>=0.18.0)"] 1506 | 1507 | [[package]] 1508 | name = "verspec" 1509 | version = "0.1.0" 1510 | description = "Flexible version handling" 1511 | optional = true 1512 | python-versions = "*" 1513 | files = [ 1514 | {file = "verspec-0.1.0-py3-none-any.whl", hash = "sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31"}, 1515 | {file = "verspec-0.1.0.tar.gz", hash = "sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e"}, 1516 | ] 1517 | 1518 | [package.extras] 1519 | test = ["coverage", "flake8 (>=3.7)", "mypy", "pretend", "pytest"] 1520 | 1521 | [[package]] 1522 | name = "watchdog" 1523 | version = "6.0.0" 1524 | description = "Filesystem events monitoring" 1525 | optional = true 1526 | python-versions = ">=3.9" 1527 | files = [ 1528 | {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, 1529 | {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, 1530 | {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, 1531 | {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, 1532 | {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, 1533 | {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, 1534 | {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, 1535 | {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, 1536 | {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, 1537 | {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, 1538 | {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, 1539 | {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, 1540 | {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, 1541 | {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, 1542 | {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, 1543 | {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, 1544 | {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, 1545 | {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, 1546 | {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, 1547 | {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, 1548 | {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, 1549 | {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, 1550 | {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, 1551 | {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, 1552 | {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, 1553 | {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, 1554 | {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, 1555 | {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, 1556 | {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, 1557 | {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, 1558 | ] 1559 | 1560 | [package.extras] 1561 | watchmedo = ["PyYAML (>=3.10)"] 1562 | 1563 | [[package]] 1564 | name = "zipp" 1565 | version = "3.21.0" 1566 | description = "Backport of pathlib-compatible object wrapper for zip files" 1567 | optional = true 1568 | python-versions = ">=3.9" 1569 | files = [ 1570 | {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, 1571 | {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, 1572 | ] 1573 | 1574 | [package.extras] 1575 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] 1576 | cover = ["pytest-cov"] 1577 | doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 1578 | enabler = ["pytest-enabler (>=2.2)"] 1579 | test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] 1580 | type = ["pytest-mypy"] 1581 | 1582 | [extras] 1583 | docs = ["mike", "mkdocs-material", "mkdocstrings"] 1584 | 1585 | [metadata] 1586 | lock-version = "2.0" 1587 | python-versions = "^3.10" 1588 | content-hash = "43e6fabb461803715afe7cf3bebd2b59d85d5c464209a77729f2e554f47d4b66" 1589 | -------------------------------------------------------------------------------- /poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true 3 | prefer-active-python = true 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "open-image-models" 3 | version = "0.2.2" 4 | description = "Pre-trained image models using ONNX for fast, out-of-the-box inference." 5 | authors = ["ankandrew <61120139+ankandrew@users.noreply.github.com>"] 6 | readme = "README.md" 7 | repository = "https://github.com/ankandrew/open-image-models" 8 | license = "MIT" 9 | keywords = [ 10 | "image-processing", 11 | "computer-vision", 12 | "deep-learning", 13 | "image-classification", 14 | "object-detection", 15 | "open-source-models", 16 | "onnx", 17 | ] 18 | classifiers = [ 19 | "Typing :: Typed", 20 | "Intended Audience :: Developers", 21 | "Intended Audience :: Education", 22 | "Intended Audience :: Science/Research", 23 | "Operating System :: OS Independent", 24 | "Topic :: Software Development", 25 | "Topic :: Scientific/Engineering", 26 | "Topic :: Software Development :: Libraries", 27 | "Topic :: Software Development :: Build Tools", 28 | "Topic :: Scientific/Engineering :: Artificial Intelligence", 29 | "Topic :: Software Development :: Libraries :: Python Modules", 30 | ] 31 | 32 | [tool.poetry.dependencies] 33 | python = "^3.10" 34 | numpy = ">=1.20" 35 | # Install onnxruntime-gpu only on systems other than macOS or Raspberry Pi 36 | onnxruntime-gpu = { version = ">=1.19.2", markers = "sys_platform != 'darwin' and platform_machine != 'armv7l' and platform_machine != 'aarch64' and (platform_system == 'Linux' or platform_system == 'Windows')" } 37 | # Fallback to onnxruntime for macOS, Raspberry Pi, and other unsupported platforms 38 | onnxruntime = { version = ">=1.19.2", markers = "sys_platform == 'darwin' or platform_machine == 'armv7l' or platform_machine == 'aarch64'" } 39 | opencv-python = "*" 40 | tqdm = "*" 41 | rich = "*" 42 | 43 | 44 | # Optional packages for creating the docs 45 | mkdocs-material = { version = "*", optional = true } 46 | mkdocstrings = { version = "*", extras = ["python"], optional = true } 47 | mike = { version = "*", optional = true } 48 | 49 | [tool.poetry.extras] 50 | docs = ["mkdocs-material", "mkdocstrings", "mike"] 51 | 52 | [tool.poetry.group.test.dependencies] 53 | pytest = "*" 54 | requests = "^2.32.3" 55 | 56 | [tool.poetry.group.dev.dependencies] 57 | mypy = "*" 58 | ruff = "*" 59 | pylint = "*" 60 | 61 | [tool.ruff] 62 | line-length = 120 63 | target-version = "py310" 64 | 65 | [tool.ruff.lint] 66 | select = [ 67 | # pycodestyle 68 | "E", 69 | "W", 70 | # Pyflakes 71 | "F", 72 | # pep8-naming 73 | "N", 74 | # pyupgrade 75 | "UP", 76 | # flake8-bugbear 77 | "B", 78 | # flake8-simplify 79 | "SIM", 80 | # flake8-unused-arguments 81 | "ARG", 82 | # Pylint 83 | "PL", 84 | # Perflint 85 | "PERF", 86 | # Ruff-specific rules 87 | "RUF", 88 | # pandas-vet 89 | "PD", 90 | ] 91 | ignore = ["N812", "PLR2004", "PD011", "RUF046"] 92 | fixable = ["ALL"] 93 | unfixable = [] 94 | 95 | [tool.ruff.lint.pylint] 96 | max-args = 8 97 | 98 | [tool.ruff.format] 99 | line-ending = "lf" 100 | 101 | [tool.mypy] 102 | disable_error_code = "import-untyped" 103 | 104 | [tool.pylint.typecheck] 105 | generated-members = ["cv2.*"] 106 | signature-mutators = [ 107 | "click.decorators.option", 108 | "click.decorators.argument", 109 | "click.decorators.version_option", 110 | "click.decorators.help_option", 111 | "click.decorators.pass_context", 112 | "click.decorators.confirmation_option" 113 | ] 114 | 115 | [tool.pylint.format] 116 | max-line-length = 120 117 | 118 | [tool.pylint."messages control"] 119 | disable = ["missing-class-docstring", "missing-function-docstring", "wrong-import-order", "missing-module-docstring"] 120 | 121 | [tool.pylint.design] 122 | max-args = 8 123 | min-public-methods = 1 124 | max-attributes = 8 125 | max-positional-arguments = 6 126 | 127 | [tool.pylint.miscellaneous] 128 | notes = ["FIXME", "XXX"] 129 | 130 | [tool.pylint.basic] 131 | no-docstring-rgx = "^__|^test_" 132 | 133 | [build-system] 134 | requires = ["poetry-core"] 135 | build-backend = "poetry.core.masonry.api" 136 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test package. 3 | """ 4 | 5 | from pathlib import Path 6 | 7 | PROJECT_ROOT_DIR = Path(__file__).resolve().parent.parent 8 | -------------------------------------------------------------------------------- /test/assets/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Assets package used in tests. 3 | """ 4 | 5 | import pathlib 6 | 7 | ASSETS_DIR = pathlib.Path(__file__).resolve().parent 8 | """Path pointing to test/assets directory""" 9 | -------------------------------------------------------------------------------- /test/assets/car_image.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankandrew/open-image-models/69007bba53eef71f19fecc083e412d8c4a7b6b88/test/assets/car_image.webp -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | """ 2 | The `conftest.py` file serves as a means of providing fixtures for an entire directory. Fixtures 3 | defined in a `conftest.py` can be used by any test in that package without needing to import them 4 | (pytest will automatically discover them). 5 | """ 6 | 7 | import pytest 8 | 9 | 10 | @pytest.fixture(scope="function") 11 | def temp_directory(tmpdir): 12 | """ 13 | Example fixture to create a temporary directory for testing. 14 | """ 15 | temp_dir = tmpdir.mkdir("temp") 16 | yield temp_dir 17 | temp_dir.remove() 18 | -------------------------------------------------------------------------------- /test/open_image_models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankandrew/open-image-models/69007bba53eef71f19fecc083e412d8c4a7b6b88/test/open_image_models/__init__.py -------------------------------------------------------------------------------- /test/open_image_models/detection/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankandrew/open-image-models/69007bba53eef71f19fecc083e412d8c4a7b6b88/test/open_image_models/detection/__init__.py -------------------------------------------------------------------------------- /test/open_image_models/detection/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankandrew/open-image-models/69007bba53eef71f19fecc083e412d8c4a7b6b88/test/open_image_models/detection/core/__init__.py -------------------------------------------------------------------------------- /test/open_image_models/detection/core/test_base.py: -------------------------------------------------------------------------------- 1 | from math import isclose 2 | 3 | import pytest 4 | 5 | from open_image_models.detection.core.base import BoundingBox 6 | 7 | # pylint: disable=too-many-positional-arguments 8 | 9 | 10 | @pytest.mark.parametrize( 11 | "x1, y1, x2, y2, expected_width, expected_height, expected_area, expected_center", 12 | [ 13 | (0, 0, 10, 10, 10, 10, 100, (5.0, 5.0)), 14 | (2, 3, 5, 7, 3, 4, 12, (3.5, 5.0)), 15 | (10, 10, 15, 13, 5, 3, 15, (12.5, 11.5)), 16 | ], 17 | ) 18 | def test_bounding_box_properties(x1, y1, x2, y2, expected_width, expected_height, expected_area, expected_center): 19 | bbox = BoundingBox(x1, y1, x2, y2) 20 | assert bbox.width == expected_width 21 | assert bbox.height == expected_height 22 | assert bbox.area == expected_area 23 | 24 | actual_center = bbox.center 25 | assert isclose(actual_center[0], expected_center[0], rel_tol=1e-6) 26 | assert isclose(actual_center[1], expected_center[1], rel_tol=1e-6) 27 | 28 | 29 | @pytest.mark.parametrize( 30 | "bbox, expected_xywh", 31 | [ 32 | (BoundingBox(0, 0, 10, 10), (0, 0, 10, 10)), 33 | (BoundingBox(2, 3, 5, 7), (2, 3, 3, 4)), 34 | (BoundingBox(10, 10, 15, 13), (10, 10, 5, 3)), 35 | ], 36 | ) 37 | def test_to_xywh(bbox, expected_xywh): 38 | xywh = bbox.to_xywh() 39 | assert xywh == expected_xywh 40 | 41 | 42 | @pytest.mark.parametrize( 43 | "bbox1, bbox2, expected_intersection", 44 | [ 45 | # Overlapping case 46 | ( 47 | BoundingBox(0, 0, 10, 10), 48 | BoundingBox(5, 5, 15, 15), 49 | BoundingBox(5, 5, 10, 10), 50 | ), 51 | # One box completely inside another 52 | ( 53 | BoundingBox(0, 0, 10, 10), 54 | BoundingBox(2, 2, 5, 5), 55 | BoundingBox(2, 2, 5, 5), 56 | ), 57 | # Touching edges (should return None if there's no positive overlap) 58 | ( 59 | BoundingBox(0, 0, 10, 10), 60 | BoundingBox(10, 10, 12, 12), 61 | None, 62 | ), 63 | # No overlap at all 64 | ( 65 | BoundingBox(0, 0, 5, 5), 66 | BoundingBox(6, 6, 10, 10), 67 | None, 68 | ), 69 | ], 70 | ) 71 | def test_intersection(bbox1, bbox2, expected_intersection): 72 | inter = bbox1.intersection(bbox2) 73 | assert inter == expected_intersection 74 | 75 | 76 | @pytest.mark.parametrize( 77 | "bbox1, bbox2, expected_iou", 78 | [ 79 | # Same box, IoU should equal 1.0 80 | ( 81 | BoundingBox(0, 0, 10, 10), 82 | BoundingBox(0, 0, 10, 10), 83 | 1.0, 84 | ), 85 | # Partial overlap 86 | ( 87 | BoundingBox(0, 0, 10, 10), 88 | BoundingBox(5, 5, 15, 15), 89 | 25 / 175, # intersection=25, union=175, so IoU should equal 25/175 90 | ), 91 | # No overlap, IoU should equal 0.0 92 | ( 93 | BoundingBox(0, 0, 5, 5), 94 | BoundingBox(6, 6, 10, 10), 95 | 0.0, 96 | ), 97 | # One box inside another, ratio of areas 98 | ( 99 | BoundingBox(0, 0, 10, 10), 100 | BoundingBox(2, 2, 8, 8), 101 | 36 / 100, 102 | ), 103 | ], 104 | ) 105 | def test_iou(bbox1, bbox2, expected_iou): 106 | iou_value = bbox1.iou(bbox2) 107 | assert isclose(iou_value, expected_iou, rel_tol=1e-5) 108 | 109 | 110 | def test_bounding_box_iter(): 111 | bbox = BoundingBox(10, 20, 30, 40) 112 | assert tuple(bbox) == (10, 20, 30, 40) 113 | 114 | 115 | @pytest.mark.parametrize( 116 | "bbox, max_width, max_height, expected", 117 | [ 118 | # Some coords are below 0, should be clamped to 0 119 | (BoundingBox(-10, -10, 50, 50), 100, 100, BoundingBox(0, 0, 50, 50)), 120 | # Coords (x2, y2) exceed the frame size, should be clamped to the frame dimension 121 | (BoundingBox(10, 10, 150, 150), 100, 100, BoundingBox(10, 10, 100, 100)), 122 | # Both previous cases 123 | (BoundingBox(-10, 10, 150, 90), 120, 100, BoundingBox(0, 10, 120, 90)), 124 | # Valid bbox, should remain unchanged 125 | (BoundingBox(10, 10, 90, 90), 100, 100, BoundingBox(10, 10, 90, 90)), 126 | ], 127 | ) 128 | def test_bounding_box_clamp(bbox, max_width, max_height, expected): 129 | assert bbox.clamp(max_width, max_height) == expected 130 | 131 | 132 | @pytest.mark.parametrize( 133 | "bbox, frame_width, frame_height, expected_valid", 134 | [ 135 | # Valid bbox inside frame 136 | (BoundingBox(10, 20, 30, 40), 50, 50, True), 137 | # x1 >= x2 138 | (BoundingBox(10, 20, 5, 40), 50, 50, False), 139 | # x2 exceeds frame width 140 | (BoundingBox(10, 20, 30, 40), 25, 50, False), 141 | # y2 exceeds frame height 142 | (BoundingBox(10, 20, 30, 40), 50, 35, False), 143 | # Exactly fills the frame 144 | (BoundingBox(0, 0, 50, 50), 50, 50, True), 145 | # Negative coord 146 | (BoundingBox(-1, 0, 30, 40), 50, 50, False), 147 | # x1 equals x2 148 | (BoundingBox(10, 20, 10, 40), 50, 50, False), 149 | # y1 equals y2 150 | (BoundingBox(10, 20, 30, 20), 50, 50, False), 151 | ], 152 | ) 153 | def test_bounding_box_is_valid(bbox, frame_width, frame_height, expected_valid): 154 | assert bbox.is_valid(frame_width, frame_height) == expected_valid 155 | -------------------------------------------------------------------------------- /test/open_image_models/detection/core/test_hub.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for ONNX hub module. 3 | """ 4 | 5 | from http import HTTPStatus 6 | 7 | import pytest 8 | import requests 9 | 10 | from open_image_models.detection.core.hub import AVAILABLE_ONNX_MODELS 11 | 12 | 13 | @pytest.mark.parametrize("model_name", AVAILABLE_ONNX_MODELS.keys()) 14 | def test_model_and_config_urls(model_name): 15 | """ 16 | Test to check if the model URLs of AVAILABLE_ONNX_MODELS are valid. 17 | """ 18 | model_url = AVAILABLE_ONNX_MODELS[model_name] 19 | response = requests.head(model_url, timeout=5, allow_redirects=True) 20 | assert response.status_code == HTTPStatus.OK, f"URL {model_url} is not accessible, got {response.status_code}" 21 | -------------------------------------------------------------------------------- /test/open_image_models/detection/core/yolo_v9/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankandrew/open-image-models/69007bba53eef71f19fecc083e412d8c4a7b6b88/test/open_image_models/detection/core/yolo_v9/__init__.py -------------------------------------------------------------------------------- /test/open_image_models/detection/core/yolo_v9/test_inference.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | from collections.abc import Iterator 3 | from pathlib import Path 4 | from typing import no_type_check 5 | 6 | import cv2 7 | import numpy as np 8 | import pytest 9 | 10 | from open_image_models.detection.core.base import DetectionResult 11 | from open_image_models.detection.core.hub import download_model 12 | from open_image_models.detection.core.yolo_v9.inference import YoloV9ObjectDetector 13 | from test.assets import ASSETS_DIR 14 | 15 | 16 | @pytest.fixture(name="model_path", scope="module") 17 | def _model_path() -> Iterator[Path]: 18 | """ 19 | Fixture for downloading and providing a valid ONNX model path using the `download_model` function. 20 | """ 21 | with tempfile.TemporaryDirectory() as tmp_dir: 22 | # Use real detector, can be any (i.e. plate detector) 23 | model_path = download_model( 24 | model_name="yolo-v9-t-384-license-plate-end2end", 25 | save_directory=Path(tmp_dir), 26 | force_download=False, 27 | ) 28 | # Yield the path to the model in the temporary directory 29 | yield model_path 30 | 31 | 32 | @pytest.fixture(name="mock_class_labels", scope="module") 33 | def _mock_class_labels() -> list[str]: 34 | """Fixture for class labels.""" 35 | return ["License Plate"] 36 | 37 | 38 | @pytest.fixture(name="mock_image", scope="module") 39 | def _mock_image() -> np.ndarray: 40 | """Fixture to provide a sample image loaded from a real image file (BGR format).""" 41 | # Define the path to the real image 42 | image_path = ASSETS_DIR / "car_image.webp" 43 | # Load the image using OpenCV 44 | image = cv2.imread(str(image_path), cv2.IMREAD_COLOR) 45 | # Check if the image was loaded successfully 46 | if image is None: 47 | raise FileNotFoundError(f"Image not found at {image_path}") 48 | return image 49 | 50 | 51 | @pytest.fixture(name="mock_image_path", scope="module") 52 | def _mock_image_path() -> Iterator[Path]: 53 | """Fixture to create and return a valid image file path.""" 54 | yield ASSETS_DIR / "car_image.webp" 55 | 56 | 57 | @pytest.fixture(name="yolo_detector", scope="module") 58 | def _yolo_detector(model_path: Path, mock_class_labels: list[str]) -> YoloV9ObjectDetector: 59 | """Fixture to initialize the YoloV9ObjectDetector with a mock model path and class labels.""" 60 | return YoloV9ObjectDetector( 61 | model_path=model_path, class_labels=mock_class_labels, conf_thresh=0.25, providers=["CPUExecutionProvider"] 62 | ) 63 | 64 | 65 | def test_predict_with_image_array(yolo_detector: YoloV9ObjectDetector, mock_image: np.ndarray) -> None: 66 | """Test the predict method with a single image array.""" 67 | result: list[DetectionResult] = yolo_detector.predict(mock_image) 68 | assert isinstance(result, list), "Expected the result to be a list" 69 | assert all(isinstance(det, DetectionResult) for det in result), "All items should be DetectionResult instances" 70 | 71 | 72 | def test_predict_with_image_path(yolo_detector: YoloV9ObjectDetector, mock_image_path: Path) -> None: 73 | """Test the predict method with a single image path.""" 74 | result: list[DetectionResult] = yolo_detector.predict(str(mock_image_path)) 75 | assert isinstance(result, list), "Expected the result to be a list" 76 | assert all(isinstance(det, DetectionResult) for det in result), "All items should be DetectionResult instances" 77 | 78 | 79 | def test_predict_with_list_of_image_arrays(yolo_detector: YoloV9ObjectDetector, mock_image: np.ndarray) -> None: 80 | """Test the predict method with a list of image arrays.""" 81 | # list of two identical image arrays 82 | images: list[np.ndarray] = [mock_image, mock_image] 83 | results: list[list[DetectionResult]] = yolo_detector.predict(images) 84 | assert isinstance(results, list), "Expected the result to be a list of lists" 85 | assert all(isinstance(res, list) for res in results), "Each result should be a list of DetectionResult" 86 | assert all(isinstance(det, DetectionResult) for res in results for det in res), ( 87 | "All items should be DetectionResult instances" 88 | ) 89 | 90 | 91 | def test_predict_with_list_of_image_paths(yolo_detector: YoloV9ObjectDetector, mock_image_path: Path) -> None: 92 | """Test the predict method with a list of image paths.""" 93 | # list of two identical image paths 94 | image_paths: list[str] = [str(mock_image_path), str(mock_image_path)] 95 | results: list[list[DetectionResult]] = yolo_detector.predict(image_paths) 96 | assert isinstance(results, list), "Expected the result to be a list of lists" 97 | assert all(isinstance(res, list) for res in results), "Each result should be a list of DetectionResult" 98 | assert all(isinstance(det, DetectionResult) for res in results for det in res), ( 99 | "All items should be DetectionResult instances" 100 | ) 101 | 102 | 103 | def test_predict_with_invalid_image_path(yolo_detector: YoloV9ObjectDetector) -> None: 104 | """Test the predict method with an invalid image path.""" 105 | invalid_path: str = "non_existent_image.jpg" 106 | with pytest.raises(ValueError, match=f"Failed to load image at path: {invalid_path}"): 107 | yolo_detector.predict(invalid_path) 108 | 109 | 110 | @no_type_check 111 | def test_predict_with_mixed_list( 112 | yolo_detector: YoloV9ObjectDetector, mock_image: np.ndarray, mock_image_path: Path 113 | ) -> None: 114 | """Test the predict method with a mixed list of image arrays and paths (should raise TypeError).""" 115 | mixed_input: list[object] = [mock_image, str(mock_image_path)] 116 | with pytest.raises(TypeError): 117 | yolo_detector.predict(mixed_input) 118 | 119 | 120 | def test_predict_with_empty_list(yolo_detector: YoloV9ObjectDetector) -> None: 121 | """Test the predict method with an empty list.""" 122 | results: list = yolo_detector.predict([]) 123 | assert results == [], "Expected an empty list when input is empty" 124 | -------------------------------------------------------------------------------- /test/open_image_models/detection/core/yolo_v9/test_postprocess.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | from open_image_models.detection.core.base import BoundingBox 5 | from open_image_models.detection.core.yolo_v9.postprocess import convert_to_detection_result 6 | 7 | 8 | @pytest.fixture(name="sample_predictions") 9 | def _sample_predictions() -> np.ndarray: 10 | """ 11 | Provides a sample prediction array for testing. 12 | 13 | Predictions format: [batch_id, x1, y1, x2, y2, class_id, score] 14 | """ 15 | return np.array( 16 | [ 17 | [0, 50, 60, 200, 220, 0, 0.9], # High confidence detection for class 0 18 | [0, 150, 160, 300, 320, 1, 0.7], # High confidence detection for class 1 19 | [0, 250, 260, 400, 420, 2, 0.4], # Low confidence detection for class 2 (should be filtered out) 20 | ] 21 | ) 22 | 23 | 24 | @pytest.fixture(name="class_labels") 25 | def _class_labels() -> list[str]: 26 | """Provides a list of class labels.""" 27 | return ["person", "car", "dog"] 28 | 29 | 30 | @pytest.fixture(name="scaling_ratio") 31 | def _scaling_ratio() -> tuple[float, float]: 32 | """Provides a sample scaling ratio used during preprocessing.""" 33 | return 0.5, 0.5 # Scaling applied by a factor of 0.5 on both axes 34 | 35 | 36 | @pytest.fixture(name="padding_values") 37 | def _padding_values() -> tuple[int, int]: 38 | """Provides a sample padding tuple used during preprocessing.""" 39 | return 10, 10 # Padding added on both axes 40 | 41 | 42 | @pytest.mark.parametrize( 43 | "score_threshold, expected_labels", 44 | [ 45 | # Threshold at 0.5, only two detections should pass 46 | (0.5, ["person", "car"]), 47 | # Threshold at 0.8, only one detection should pass 48 | (0.8, ["person"]), 49 | # Threshold at 0.3, all detections should pass 50 | (0.3, ["person", "car", "dog"]), 51 | ], 52 | ) 53 | def test_convert_to_detection_result( 54 | sample_predictions: np.ndarray, 55 | class_labels: list[str], 56 | scaling_ratio: tuple[float, float], 57 | padding_values: tuple[int, int], 58 | score_threshold: float, 59 | expected_labels: list[str], 60 | ) -> None: 61 | """Test the conversion of predictions to DetectionResult with different score thresholds.""" 62 | results = convert_to_detection_result( 63 | predictions=sample_predictions, 64 | class_labels=class_labels, 65 | ratio=scaling_ratio, 66 | padding=padding_values, 67 | score_threshold=score_threshold, 68 | ) 69 | # Check that the number of results matches the expected number of labels 70 | assert len(results) == len(expected_labels), f"Expected {len(expected_labels)} results, but got {len(results)}" 71 | # Check that labels match the expected labels 72 | for result, expected_label in zip(results, expected_labels, strict=False): 73 | assert result.label == expected_label, f"Expected label {expected_label}, but got {result.label}" 74 | # Check the confidence scores 75 | for result in results: 76 | assert result.confidence >= score_threshold, f"Expected score >= {score_threshold}, but got {result.confidence}" 77 | 78 | 79 | def test_bounding_box_adjustment( 80 | sample_predictions: np.ndarray, 81 | class_labels: list[str], 82 | scaling_ratio: tuple[float, float], 83 | padding_values: tuple[int, int], 84 | ) -> None: 85 | """Test if bounding boxes are correctly adjusted based on ratio and padding.""" 86 | results = convert_to_detection_result( 87 | predictions=sample_predictions, 88 | class_labels=class_labels, 89 | ratio=scaling_ratio, 90 | padding=padding_values, 91 | score_threshold=0.5, 92 | ) 93 | # Expected adjusted bounding boxes (unpadding and scaling to original size) 94 | expected_bboxes = [ 95 | BoundingBox(x1=80, y1=100, x2=380, y2=420), 96 | BoundingBox(x1=280, y1=300, x2=580, y2=620), 97 | ] 98 | for result, expected_bbox in zip(results, expected_bboxes, strict=False): 99 | bbox = result.bounding_box 100 | assert bbox.x1 == expected_bbox.x1 and bbox.y1 == expected_bbox.y1, ( 101 | f"Expected bbox x1={expected_bbox.x1}, y1={expected_bbox.y1}, but got x1={bbox.x1}, y1={bbox.y1}" 102 | ) 103 | assert bbox.x2 == expected_bbox.x2 and bbox.y2 == expected_bbox.y2, ( 104 | f"Expected bbox x2={expected_bbox.x2}, y2={expected_bbox.y2}, but got x2={bbox.x2}, y2={bbox.y2}" 105 | ) 106 | 107 | 108 | @pytest.mark.parametrize( 109 | "class_labels, expected_label", 110 | [ 111 | # Known class label 112 | (["person", "car", "dog"], "dog"), 113 | # Out of bounds class label should return class_id as string 114 | (["person", "car"], "2"), 115 | ], 116 | ) 117 | def test_class_label_mapping( 118 | class_labels: list[str], 119 | scaling_ratio: tuple[float, float], 120 | padding_values: tuple[int, int], 121 | expected_label: str, 122 | ) -> None: 123 | """Test if the correct label is mapped or class_id returned when label is out of bounds.""" 124 | predictions = np.array( 125 | [ 126 | [0, 50, 60, 200, 220, 2, 0.9], # High confidence detection for class_id 2 127 | ] 128 | ) 129 | results = convert_to_detection_result( 130 | predictions=predictions, 131 | class_labels=class_labels, 132 | ratio=scaling_ratio, 133 | padding=padding_values, 134 | score_threshold=0.5, 135 | ) 136 | assert results[0].label == expected_label, f"Expected label {expected_label}, but got {results[0].label}" 137 | 138 | 139 | def test_no_results_below_threshold( 140 | sample_predictions: np.ndarray, 141 | class_labels: list[str], 142 | scaling_ratio: tuple[float, float], 143 | padding_values: tuple[int, int], 144 | ) -> None: 145 | """Test that no DetectionResult is returned if all scores are below the threshold.""" 146 | results = convert_to_detection_result( 147 | predictions=sample_predictions, 148 | class_labels=class_labels, 149 | ratio=scaling_ratio, 150 | padding=padding_values, 151 | score_threshold=1.0, # Threshold is higher than all scores 152 | ) 153 | # No results should be returned 154 | assert isinstance(results, list), "Expected a list!" 155 | assert len(results) == 0, "Expected no results, but got some results" 156 | -------------------------------------------------------------------------------- /test/open_image_models/detection/core/yolo_v9/test_preprocess.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | from open_image_models.detection.core.yolo_v9.preprocess import preprocess 5 | 6 | 7 | @pytest.fixture(name="sample_image") 8 | def _sample_image() -> np.ndarray: 9 | """Provides a sample image in BGR format for testing.""" 10 | # Create a dummy image (BGR format) of size 640x480 11 | img = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8) 12 | return img 13 | 14 | 15 | @pytest.mark.parametrize( 16 | "img_size, expected_shape", 17 | [ 18 | ((640, 640), (1, 3, 640, 640)), 19 | ((320, 320), (1, 3, 320, 320)), 20 | ((800, 800), (1, 3, 800, 800)), 21 | ], 22 | ) 23 | def test_preprocess(sample_image: np.ndarray, img_size, expected_shape: tuple[int, int, int, int]) -> None: 24 | """Test preprocess function with different image sizes.""" 25 | img, ratio, _ = preprocess(sample_image, img_size) 26 | assert img.shape == expected_shape, f"Expected shape {expected_shape}, but got {img.shape}" 27 | assert isinstance(ratio, tuple) and len(ratio) == 2, "Ratio must be a tuple of length 2." 28 | 29 | 30 | def test_preprocess_output_range(sample_image: np.ndarray) -> None: 31 | """Ensure that the output image tensor is correctly normalized between 0 and 1.""" 32 | img, _, _ = preprocess(sample_image, (640, 640)) 33 | # The preprocessed image should be normalized to the range [0, 1] 34 | assert img.min() >= 0.0 and img.max() <= 1.0, "Preprocessed image values should be in the range [0, 1]" 35 | --------------------------------------------------------------------------------