├── .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 | [](https://github.com/ankandrew/open-image-models/actions)
4 | [](https://github.com/ankandrew/open-image-models/releases)
5 | [](https://pypi.python.org/pypi/open-image-models)
6 | [](https://github.com/astral-sh/ruff)
7 | [](https://ankandrew.github.io/open-image-models/)
8 | [](https://github.com/pylint-dev/pylint)
9 | [](http://mypy-lang.org/)
10 | [](https://onnx.ai/)
11 | [](https://huggingface.co/spaces/ankandrew/open-image-models)
12 | 
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 | 
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 |
--------------------------------------------------------------------------------