diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..0f31025a --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,67 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [main, master, develop, dev, 2.0.0] + pull_request: + # The branches below must be a subset of the branches above + branches: [develop, dev, 2.0.0] + schedule: + - cron: "26 13 * * 6" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: ["python"] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/lint-and-build.yml b/.github/workflows/lint-and-build.yml new file mode 100644 index 00000000..dfcbdc49 --- /dev/null +++ b/.github/workflows/lint-and-build.yml @@ -0,0 +1,126 @@ +# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions +name: Lint and build +on: + workflow_dispatch: # Allows manual builds + inputs: + excludeBuildNumber: + description: "Exclude build number" + required: true + default: false + type: boolean + push: + branches: + - main + - master + - dev* + paths: + - "**.py" + - "**.ui" + - ".github/workflows/lint-and-build.yml" + - "**/requirements.txt" + pull_request: + branches: + - main + - master + - dev* + - 2.0.0 + paths: + - "**.py" + - "**.pyi" + - "**.ui" + - ".github/workflows/lint-and-build.yml" + - "**/requirements*.txt" + +env: + GITHUB_HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }} + GITHUB_EXCLUDE_BUILD_NUMBER: ${{ inputs.excludeBuildNumber }} + +jobs: + ruff: + runs-on: windows-latest + strategy: + fail-fast: false + # Ruff is version sensible + matrix: + python-version: ["3.9", "3.10", "3.11"] + steps: + - name: Checkout ${{ github.repository }}/${{ github.ref }} + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + cache-dependency-path: "scripts/requirements*.txt" + - run: scripts/install.ps1 + shell: pwsh + - run: ruff check . + add-trailing-comma: + runs-on: windows-latest + steps: + - name: Checkout ${{ github.repository }}/${{ github.ref }} + uses: actions/checkout@v3 + - name: Set up Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: "3.11" + - run: pip install add-trailing-comma + - name: Analysing the code with add-trailing-comma + run: add-trailing-comma $(git ls-files '**.py*') --py36-plus + Pyright: + runs-on: windows-latest + strategy: + fail-fast: false + # Pyright is version and platform sensible + matrix: + python-version: ["3.9", "3.10", "3.11"] + steps: + - name: Checkout ${{ github.repository }}/${{ github.ref }} + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + cache-dependency-path: "scripts/requirements*.txt" + - run: scripts/install.ps1 + shell: pwsh + - name: Analysing the code with Pyright + uses: jakebailey/pyright-action@v1 + with: + working-directory: src/ + Build: + runs-on: windows-latest + strategy: + fail-fast: false + # Only the Python version we plan on shipping matters. + matrix: + python-version: ["3.11"] + steps: + - name: Checkout ${{ github.repository }}/${{ github.ref }} + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + cache-dependency-path: "scripts/requirements.txt" + - run: scripts/install.ps1 + shell: pwsh + - run: scripts/build.ps1 + shell: pwsh + - name: Upload Build Artifact + uses: actions/upload-artifact@v3 + with: + name: AutoSplit (Python ${{ matrix.python-version }}) + path: dist/AutoSplit* + if-no-files-found: error + - name: Upload Build logs + uses: actions/upload-artifact@v3 + with: + name: Build logs (Python ${{ matrix.python-version }}) + path: | + build/AutoSplit/*.toc + build/AutoSplit/*.txt + build/AutoSplit/*.html + if-no-files-found: error diff --git a/.gitignore b/.gitignore index 6596996f..40e3ff25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# Caches +.*cache/ + # Byte-compiled / optimized / DLL files __pycache__/ @@ -7,6 +10,10 @@ __pycache__/ env/ build/ dist/ +*.prof +# Generated +**/gen/*.py +!**/gen/*.pyi # PyInstaller # Usually these files are written by a python script from a template @@ -16,3 +23,4 @@ dist/ # Dev settings *.pkl +settings.toml diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 00000000..9ecf9f17 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,6 @@ +{ + "default": true, + "MD001": false, + "MD013": false, + "MD025": false +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..b141985b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,38 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + # Workaround for https://github.com/adamchainz/pre-commit-dprint/issues/4 + - id: pretty-format-json + exclude: ".vscode/.*|dprint.json" # Exclude jsonc + args: [--autofix, --no-sort-keys] + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=crlf] + - id: check-case-conflict + - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.8.0 + hooks: + - id: pretty-format-ini + args: [--autofix] + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: "v0.0.262" # Must match requirements-dev.txt + hooks: + - id: ruff + args: [--fix] + - repo: https://github.com/pre-commit/mirrors-autopep8 + rev: "v2.0.2" # Must match requirements-dev.txt + hooks: + - id: autopep8 + - repo: https://github.com/asottile/add-trailing-comma + rev: v2.4.0 # Must match requirements-dev.txt + hooks: + - id: add-trailing-comma + +ci: + skip: + # Ignore until Linux support. We don't want lf everywhere yet + # And crlf fails on CI because pre-commit runs on linux + - "mixed-line-ending" diff --git a/.sonarcloud.properties b/.sonarcloud.properties new file mode 100644 index 00000000..ff239edf --- /dev/null +++ b/.sonarcloud.properties @@ -0,0 +1 @@ +sonar.python.version=3.9, 3.10, 3.11 diff --git a/.vscode/extensions.json b/.vscode/extensions.json index ca9b77ca..6e71f39c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,14 +1,42 @@ +// Keep in alphabetical order { "recommendations": [ - "ms-python.vscode-pylance", - "ms-python.python", - "sonarsource.sonarlint-vscode", + "bungcip.better-toml", "davidanson.vscode-markdownlint", - "shardulm94.trailing-spaces", - "eamodio.gitlens" + "eamodio.gitlens", + "emeraldwalk.runonsave", + "ms-python.autopep8", + "ms-python.python", + "ms-python.vscode-pylance", + "ms-vscode.powershell", + "pkief.material-icon-theme", + "redhat.vscode-xml", + "redhat.vscode-yaml", ], "unwantedRecommendations": [ + // Must disable in this workspace // + // https://github.com/microsoft/vscode/issues/40239 // + // + // VSCode has implemented an optimized version + "coenraads.bracket-pair-colorizer", + "coenraads.bracket-pair-colorizer-2", + "shardulm94.trailing-spaces", + // Obsoleted by Pylance "ms-pyright.pyright", - "esbenp.prettier-vscode" - ] + // Not configurable per workspace, tends to conflict with other linters + "sonarsource.sonarlint-vscode", + // + // Don't recommend to autoinstall // + // + // Use Ruff instead + "ms-python.flake8", + "ms-python.isort", + "ms-python.pylint", + // We use autopep8 + "ms-python.black-formatter", + // This is a Git project + "johnstoncode.svn-scm", + // Prefer using VSCode itself as a text editor + "vscodevim.vim", + ], } diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..957b8d50 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,38 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: AutoSplit (debug non-user code)", + "type": "python", + "request": "launch", + "preLaunchTask": "Compile resources", + "program": "src/AutoSplit.py", + "console": "integratedTerminal", + "justMyCode": false + }, + { + "name": "Python: AutoSplit", + "type": "python", + "request": "launch", + "preLaunchTask": "Compile resources", + "program": "src/AutoSplit.py", + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "Python: AutoSplit --auto-controlled", + "type": "python", + "request": "launch", + "preLaunchTask": "Compile resources", + "program": "src/AutoSplit.py", + "args": [ + "--auto-controlled" + ], + "console": "integratedTerminal", + "justMyCode": true + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index d8155e25..8405b12c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,61 +8,106 @@ 72 ] }, + "[markdown]": { + "files.trimTrailingWhitespace": false, + }, + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "editor.comments.insertSpace": true, + "editor.insertSpaces": true, "editor.detectIndentation": false, "editor.tabSize": 2, - "[python]": { - "editor.tabSize": 4, - "editor.rulers": [ - 79, - 100, - 120, - ] - }, - // Keeping autoformat to false for now to keep minimal changes - "editor.formatOnSave": false, + "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll": false, + "source.fixAll": true, + "source.fixAll.unusedImports": false, + "source.fixAll.convertImportFormat": true, + "source.organizeImports": false, }, - "python.linting.enabled": true, - "python.analysis.typeCheckingMode": "basic", - "python.analysis.diagnosticSeverityOverrides": { - // Too many false positives with pywin32 (all functions have 0 arguments in type stub) - "reportGeneralTypeIssues":"none" + "emeraldwalk.runonsave": { + "commands": [ + { + "match": "\\.pyi?", + "cmd": "add-trailing-comma ${file} --py36-plus" + }, + ] }, - // https://code.visualstudio.com/docs/python/linting#_specific-linters - // Maybe consider PyLint once all Flake8 linting is fixed - "python.linting.pylintEnabled": false, - "python.linting.flake8Enabled": true, - "python.linting.mypyEnabled": true, - // Flake8 is already a pycodestyle wrapper - "python.linting.pycodestyleEnabled": false, - "python.linting.pylintArgs": [ - "--disable=no-member", - "--max-line-length=120" - ], - "python.linting.flake8CategorySeverity.E": "Warning", - "python.linting.flake8Args": [ - "--max-line-length=120" - ], - "python.linting.mypyArgs": [ - "--max-line-length=120" - ], - "python.formatting.autopep8Args": [ - "--max-line-length=120" - ], - "files.insertFinalNewline": true, - "trailing-spaces.deleteModifiedLinesOnly": true, - "trailing-spaces.includeEmptyLines": true, - "trailing-spaces.trimOnSave": true, - "trailing-spaces.syntaxIgnore": [ - "markdown" - ], "files.associations": { + ".flake8": "properties", "*.qrc": "xml", "*.ui": "xml" }, - "markdownlint.config": { - "default": true, - "MD025": false, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + "build": true, + "**/.mypy_cache": true, + "**/__pycache__": true, }, + "search.exclude": { + "**/*.code-search": true, + "*.lock": true, + }, + // Set the default formatter to help avoid Prettier + "[json][jsonc]": { + "editor.defaultFormatter": "vscode.json-language-features", + }, + "[python]": { + // Ruff is not yet a formatter: https://github.com/charliermarsh/ruff/issues/1904 + "editor.defaultFormatter": "ms-python.autopep8", + "editor.tabSize": 4, + "editor.rulers": [ + 72, // PEP8-17 docstrings + // 79, // PEP8-17 default max + // 88, // Black default + // 99, // PEP8-17 acceptable max + 120, // Our hard rule + ], + }, + // Important to follow the config in pyrightconfig.json + "python.analysis.useLibraryCodeForTypes": false, + "python.analysis.diagnosticMode": "workspace", + "python.linting.enabled": true, + "ruff.importStrategy": "fromEnvironment", + // Use the Ruff extension instead + "isort.check": false, + "python.linting.banditEnabled": false, + "python.linting.flake8Enabled": false, + "python.linting.prospectorEnabled": false, + "python.linting.pycodestyleEnabled": false, + "python.linting.pylamaEnabled": false, + "python.linting.pylintEnabled": false, + // Use the autopep8 extension instead + "python.formatting.provider": "none", + // Use Pyright/Pylance instead + "python.linting.mypyEnabled": false, + "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationForFirstPipeline", + "powershell.codeFormatting.autoCorrectAliases": true, + "powershell.codeFormatting.trimWhitespaceAroundPipe": true, + "powershell.codeFormatting.useConstantStrings": true, + "powershell.codeFormatting.useCorrectCasing": true, + "powershell.codeFormatting.whitespaceBetweenParameters": true, + "powershell.integratedConsole.showOnStartup": false, + "terminal.integrated.defaultProfile.windows": "PowerShell", + "xml.codeLens.enabled": true, + "xml.format.spaceBeforeEmptyCloseTag": false, + "xml.format.preserveSpace": [ + // Default + "xsl:text", + "xsl:comment", + "xsl:processing-instruction", + "literallayout", + "programlisting", + "screen", + "synopsis", + "pre", + "xd:pre", + // Custom + "string" + ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..39a29147 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,32 @@ +{ + "version": "2.0.0", + "windows": { + "options": { + "shell": { + "executable": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command" + ] + } + } + }, + "tasks": [ + { + "label": "Compile resources", + "type": "shell", + "command": "scripts/compile_resources.ps1" + }, + { + "label": "Build AutoSplit", + "type": "shell", + "command": "scripts/build.ps1", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/D3DDD-Note-Laptops.md b/D3DDD-Note-Laptops.md new file mode 100644 index 00000000..bbf456f3 --- /dev/null +++ b/D3DDD-Note-Laptops.md @@ -0,0 +1,47 @@ +# Installation Note: D3D Desktop Duplication on Laptops + +Windows has a little quirk when running Desktop Duplication on laptops with hybrid GPU systems (integrated + dedicated). You will need to perform an additional tweak to get _D3D Desktop Duplication_ to work correctly on your system. + +## Problem + +The problem is fully documented in [this article](https://support.microsoft.com/en-us/help/3019314/error-generated-when-desktop-duplication-api-capable-application-is-ru) + +## Solution + +The solution is presented as such: + +> Run the application on the integrated GPU instead of on the discrete GPU + +Therefore, to be able to use _D3D Desktop Duplication_ on hybrid GPU laptops, we need to force Python to run on the integrated GPU. + +## Approach 1: Windows 10 Settings + +_You must be running Windows 10 1809 or later for this to work._ + +1. Press the Windows Key, type `Graphics settings` and press enter +2. You should see the following window: +![image](https://user-images.githubusercontent.com/35039/84433008-a3b65d00-abfb-11ea-8343-81b8f265afc4.png) +3. Make sure the dropdown is set to `Desktop App` and click `Browse` +4. Find the `python.exe` used by your _D3D Desktop Duplication_ project. Example: +![image](https://user-images.githubusercontent.com/35039/84433419-3d7e0a00-abfc-11ea-99a4-b5176535b0e5.png) +5. Click on `Options` +6. Select `Power saving` and click `Save` +![image](https://user-images.githubusercontent.com/35039/84433562-7918d400-abfc-11ea-807a-e3c0b15d9fb2.png) +7. If you did everything right it should look like this: +![image](https://user-images.githubusercontent.com/35039/84433706-bda46f80-abfc-11ea-9c64-a702b96095b8.png) +8. Repeat the process for other potentially relevant executables for your project: `ipython.exe`, `jupyter-kernel.exe` etc. + +## Approach 2: Nvidia Control Panel + +Need help to fill in this section. See issue [SerpentAI/D3DShot#27](https://github.com/SerpentAI/D3DShot/issues/27) + +## Approach 3: AMD Catalyst Control Center + +Need help to fill in this section. See issue [SerpentAI/D3DShot#28](https://github.com/SerpentAI/D3DShot/issues/28) + +## Question: Won't this impede on my ability to use CUDA, OpenCL etc? + +Preliminary answer: No. This is telling Windows how to _render_ Python processes with the Desktop Window Manager. Most Python applications are console applications that don't have a window. Even if you have a GUI application with one or more windows, this should only affect the rendering aspect (i.e. your windows won't be rendered through the dedicated GPU) and shouldn't limit hardware access in any way. + +--- +(copied and adapted from ) diff --git a/PyInstaller/hooks/hook-requests.py b/PyInstaller/hooks/hook-requests.py new file mode 100644 index 00000000..e1a554d0 --- /dev/null +++ b/PyInstaller/hooks/hook-requests.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +from PyInstaller.utils.hooks import collect_data_files + +# Get the cacert.pem +datas = collect_data_files("certifi") diff --git a/README.md b/README.md index 823d8c03..babdf7f6 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,40 @@ -# LiveSplit AutoSplit +# LiveSplit AutoSplit [![CodeQL](/../../actions/workflows/codeql-analysis.yml/badge.svg)](/../../actions/workflows/codeql-analysis.yml) [![Lint and build](/../../actions/workflows/lint-and-build.yml/badge.svg)](/../../actions/workflows/lint-and-build.yml) + +[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=Avasam_AutoSplit&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=Avasam_AutoSplit) +[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=Avasam_AutoSplit&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=Avasam_AutoSplit) +[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=Avasam_AutoSplit&metric=security_rating)](https://sonarcloud.io/dashboard?id=Avasam_AutoSplit) +[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=Avasam_AutoSplit&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=Avasam_AutoSplit) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Avasam_AutoSplit&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Avasam_AutoSplit) +[![SemVer](https://badgen.net/badge/_/SemVer%20compliant/grey?label)](https://semver.org/) Easy to use image comparison based auto splitter for speedrunning on console or PC. This program can be used to automatically start, split, and reset your preferred speedrun timer by comparing images to a capture region. This allows you to focus more on your speedrun and less on managing your timer. It also improves the accuracy of your splits. It can be used in tandem with any speedrun timer that accepts hotkeys (LiveSplit, wsplit, etc.), and can be integrated with LiveSplit. -![Example](res/example1.6.0.gif) +![Example](res/2.0.0_gif.gif) # TUTORIAL ## DOWNLOAD AND OPEN -### Compatibility - -- Windows 7, 10, and 11. +- Download the [latest version](/../../releases/latest) +- You can also check out the [latest dev builds](/../../actions/workflows/lint-and-build.yml?query=event%3Apush+is%3Asuccess) (requires a GitHub account) + -### Opening the program +### Compatibility -- Download the [latest version](/../../releases/latest) -- Extract the file and open AutoSplit.exe. +- Python 3.9+ +- Windows 10 and 11. ### Building -(This is not required for normal use) +(This is not required for normal use) +Refer to the [build instructions](build%20instructions.md) if you'd like to build the application yourself or run it directly in Python. -- Microsoft Visual C++ 14.0 or greater may be required. Get it with [Microsoft C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) -- Read [requirements.txt](/scripts/requirements.txt) for information on how to install, run and build the python code - - Run `.\scripts\install.bat` to install all dependencies - - Run the app directly with `py .\src\AutoSplit.py [--auto-controlled]` - - Run `.\scripts\build.bat` to build an executable -- Recompile resources after modifications by running `.\scripts\compile_resources.bat` +## OPTIONS -## Split Image Folder +#### Split Image Folder - Supported image file types: .png, .jpg, .jpeg, .bmp, and [more](https://docs.opencv.org/3.0-beta/modules/imgcodecs/doc/reading_and_writing_images.html#imread). - Images can be any size. @@ -40,58 +43,114 @@ This program can be used to automatically start, split, and reset your preferred - Custom split image settings are handled in the filename. See how [here](#custom-split-image-settings). - To create split images, it is recommended to use AutoSplit's Take Screenshot button for accuracy. However, images can be created using any method including Print Screen and [Snipping Tool](https://support.microsoft.com/en-us/help/4027213/windows-10-open-snipping-tool-and-take-a-screenshot). -## Capture Region +#### Capture Region - This is the region that your split images are compared to. Usually, this is going to be the full game screen. -- Click "Select Region" +- Click "Select Region". - Click and drag to form a rectangle over the region you want to capture. - Adjust the x, y, width, and height of the capture region manually to make adjustments as needed. -- If you want to align your capture region by using a reference image, click "Align Region" +- If you want to align your capture region by using a reference image, click "Align Region". - You can freely move the window that the program is capturing, but resizing the window will cause the capture region to change. - Once you are happy with your capture region, you may unselect Live Capture Region to decrease CPU usage if you wish. - You can save a screenshot of the capture region to your split image folder using the Take Screenshot button. -## Max FPS +#### Avg. FPS -- Calculates the maximum comparison rate of the capture region to split images. This value will likely be much higher than needed, so it is highly recommended to limit your FPS depending on the frame rate of the game you are capturing. +- Calculates the average comparison rate of the capture region to split images. This value will likely be much higher than needed, so it is highly recommended to limit your FPS depending on the frame rate of the game you are capturing. -## OPTIONS +### Settings -### Comparison Method +#### Comparison Method -- There are three comparison methods to choose from: L2 Norm, Histograms, and pHash. - - L2 Norm: This method should be fine to use for most cases. it finds the difference between each pixel, squares it, and sums it over the entire image and takes the square root. This is very fast but is a problem if your image is high frequency. Any translational movement or rotation can cause similarity to be very different. +- There are three comparison methods to choose from: L2 Norm, Histograms, and Perceptual Hash (or pHash). + - L2 Norm: This method should be fine to use for most cases. It finds the difference between each pixel, squares it, sums it over the entire image and takes the square root. This is very fast but is a problem if your image is high frequency. Any translational movement or rotation can cause similarity to be very different. - Histograms: An explanation on Histograms comparison can be found [here](https://mpatacchiola.github.io/blog/2016/11/12/the-simplest-classifier-histogram-intersection.html). This is a great method to use if you are using several masked images. - - pHash: An explanation on pHash comparison can be found [here](http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html). It is highly recommended to NOT use pHash if you use masked images. It is very inaccurate. - -### Show Live Similarity + > This algorithm is particular reliable when the colour is a strong predictor of the object identity. The histogram intersection [...] is robust to occluding objects in the foreground. + - Perceptual Hash: An explanation on pHash comparison can be found [here](http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html). It is highly recommended to NOT use pHash if you use masked images, or it'll be very inaccurate. + +#### Capture Method + + +- **Windows Graphics Capture** (fast, most compatible, capped at 60fps) + Only available in Windows 10.0.17134 and up. + Due to current technical limitations, Windows versions below 10.0.0.17763 require having at least one audio or video Capture Device connected and enabled. + Allows recording UWP apps, Hardware Accelerated and Exclusive Fullscreen windows. + Adds a yellow border on Windows 10 (not on Windows 11). + Caps at around 60 FPS. +- **BitBlt** (fastest, least compatible) + The best option when compatible. But it cannot properly record OpenGL, Hardware Accelerated or Exclusive Fullscreen windows. + The smaller the selected region, the more efficient it is. +- **Direct3D Desktop Duplication** (slower, bound to display) + Duplicates the desktop using Direct3D. + It can record OpenGL and Hardware Accelerated windows. + About 10-15x slower than BitBlt. Not affected by window size. + overlapping windows will show up and can't record across displays. + This option may not be available for hybrid GPU laptops, see [D3DDD-Note-Laptops.md](/D3DDD-Note-Laptops.md) for a solution. +- **Force Full Content Rendering** (very slow, can affect rendering) + Uses BitBlt behind the scene, but passes a special flag to PrintWindow to force rendering the entire desktop. + About 10-15x slower than BitBlt based on original window size and can mess up some applications' rendering pipelines. +- **Video Capture Device** + Uses a Video Capture Device, like a webcam, virtual cam, or capture card. + If you want to use this with OBS' Virtual Camera, use the [Virtualcam plugin](https://github.com/Avasam/obs-virtual-cam/releases) instead. + +#### Capture Device + +Select the Video Capture Device that you wanna use if selecting the `Video Capture Device` Capture Method. + + +#### Show Live Similarity - Displays the live similarity between the capture region and the current split image. This number is between 0 and 1, with 1 being a perfect match. -### Show Highest Similarity +#### Show Highest Similarity - Shows the highest similarity between the capture region and current split image. -### Current Similarity Threshold +#### Current Similarity Threshold - When the live similarity goes above this value, the program hits your split hotkey and moves to the next split image. -### Default Similarity Threshold +#### Default Similarity Threshold - This value will be set as the threshold for an image if there is no custom threshold set for that image. -### Pause Time +#### Pause Time - Time in seconds that the program stops comparison after a split. Useful for if you have two of the same split images in a row and want to avoid double-splitting. Also useful for reducing CPU usage. -### Default Pause Time +#### Default Pause Time - This value will be set as the Pause Time for an image if there is no custom Pause Time set for that image. -### Delay Time +#### Delay Time - Time in milliseconds that the program waits before hitting the split hotkey for that specific split. +#### Dummy splits when undoing / skipping + +AutoSplit will group dummy splits together with a real split when undoing/skipping. This basically allows you to tie one or more dummy splits to a real split to keep it as in sync as possible with the real splits in LiveSplit/wsplit. If they are out of sync, you can always use "Previous Image" and "Next Image". + +Examples: +Given these splits: 1 dummy, 2 normal, 3 dummy, 4 dummy, 5 normal, 6 normal. + +In this situation you would have only 3 splits in LiveSplit/wsplit (even though there are 6 split images, only 3 are "real" splits). This basically results in 3 groups of splits: 1st split is images 1 and 2. 2nd split is images 3, 4 and 5. 3rd split is image 6. + +- If you are in the 1st or 2nd image and press the skip key, it will end up on the 3rd image +- If you are in the 3rd, 4th or 5th image and press the undo key, it will end up on the 2nd image +- If you are in the 3rd, 4th or 5th image and press the skip key, it will end up on the 6th image +- If you are in the 6th image and press the undo key, it will end up on the 5th image + +#### Loop Split Images + +If this option is enabled, when the last split meets the threshold and splits, AutoSplit will loop back to the first split image and continue comparisons. +If this option is disabled, when the last split meets the threshold and splits, AutoSplit will stop running comparisons. +This option does not loop single, specific images. See the Custom Split Image Settings section above for this feature. + +#### Auto Start On Reset + +If this option is enabled, when the reset hotkey is hit, the reset button is pressed, or the reset split image meets its threshold, AutoSplit will reset and automatically start again back at the first split image. +If this option is disabled, when the reset hotkey is hit, the reset button is pressed, or the reset split image meets its threshold, AutoSplit will stop running comparisons. + ### Custom Split Image Settings - Each split image can have different thresholds, pause times, delay split times, loop amounts, and can be flagged. @@ -99,21 +158,26 @@ This program can be used to automatically start, split, and reset your preferred - Custom thresholds are place between parenthesis `()` in the filename. This value will override the default threshold. - Custom pause times are placed between square brackets `[]` in the filename. This value will override the default pause time. - Custom delay times are placed between hash signs `##` in the filename. Note that these are in milliseconds. For example, a 10 second split delay would be `#10000#`. You cannot skip or undo splits during split delays. +- A different comparison method can be specified with their 0-base index between carets `^^`: + - `^0^`: L2 Norm + - `^1^`: Histogram + - `^2^`: Perceptual Hash - Image loop amounts are placed between at symbols `@@` in the filename. For example, a specific image that you want to split 5 times in a row would be `@5@`. The current loop # is conveniently located beneath the current split image. - Flags are placed between curly brackets `{}` in the filename. Multiple flags are placed in the same set of curly brackets. Current available flags: - - {d} dummy split image. When matched, it moves to the next image without hitting your split hotkey. - - {b} split when similarity goes below the threshold rather than above. When a split image filename has this flag, the split image similarity will go above the threshold, do nothing, and then split the next time the similarity goes below the threshold. - - {p} pause flag. When a split image filename has this flag, it will hit your pause hotkey rather than your split hokey. - - A pause flag and a dummy flag `{pd}` cannot be used together + - `{d}` dummy split image. When matched, it moves to the next image without hitting your split hotkey. + - `{b}` split when similarity goes below the threshold rather than above. When a split image filename has this flag, the split image similarity will go above the threshold, do nothing, and then split the next time the similarity goes below the threshold. + - `{p}` pause flag. When a split image filename has this flag, it will hit your pause hotkey rather than your split hokey. - Filename examples: - `001_SplitName_(0.9)_[10].png` is a split image with a threshold of 0.9 and a pause time of 10 seconds. - `002_SplitName_(0.9)_[10]_{d}.png` is the second split image with a threshold of 0.9, pause time of 10, and is a dummy split. - `003_SplitName_(0.85)_[20]_#3500#.png` is the third split image with a threshold of 0.85, pause time of 20 and has a delay split time of 3.5 seconds. - `004_SplitName_(0.9)_[10]_#3500#_@3@_{b}.png` is the fourth split image with a threshold of 0.9, pause time of 10 seconds, delay split time of 3.5 seconds, will loop 3 times, and will split when similarity is below the threshold rather than above. - + +## Special images + ### How to Create a Masked Image -Masked images are very useful if only a certain part of the capture region is consistent (for example, consistent text on the screen, but the background is always different). Histogram or L2 norm comparison is recommended if you use any masked images. It is highly recommended that you do NOT use pHash comparison if you use any masked images, as it is very inaccurate. +Masked images are very useful if only a certain part of the capture region is consistent (for example, consistent text on the screen, but the background is always different). Histogram or L2 norm comparison is recommended if you use any masked images. It is highly recommended that you do NOT use pHash comparison if you use any masked images, or it'll be very inaccurate. The best way to create a masked image is to set your capture region as the entire game screen, take a screenshot, and use a program like [paint.net](https://www.getpaint.net/) to "erase" (make transparent) everything you don't want the program to compare. More on creating images with transparency using paint.net can be found in [this tutorial](https://www.youtube.com/watch?v=v53kkUYFVn8). For visualization, here is what the capture region compared to a masked split image looks like if you would want to split on "Shine Get!" text in Super Mario Sunshine: @@ -125,7 +189,16 @@ You can have one (and only one) image with the keyword `reset` in its name. Auto ### Start image -The start image is similar to the reset image. You can only have one start image with the keyword `start_auto_splitter`.You can reload the image using the "`Reload Start Image`" button. The pause time is the amount of seconds AutoSplit will wait before checking for the start image once a run ends/is reset. Delay times will be used to delay starting your timer after the threshold is met. +The start image is similar to the reset image. You can only have one start image with the keyword `start_auto_splitter`.You can reload the image using the "`Reload Start Image`" button. The pause time is the amount of seconds AutoSplit will wait before checking for the start image once a run ends/is reset. Delay times will be used to delay starting your timer after the threshold is met. If you need to pause comparison for any amount of time after the Start Image, the best option right now is to use a dummy split image `{d}` with a similarity threshold of `(0.00)` and a pause threshold `[]` of however long you need to pause comparison as your first split image. This will trigger the pause immediately after your timer is started. + +### Profiles + +- Profiles are saved under `%appdata%\AutoSplit\profiles` and use the extension `.toml`. Profiles can be saved and loaded by using File -> Save Profile As... and File -> Load Profile. +- The profile contains all of your settings, including information about the capture region. +- You can save multiple profiles, which is useful if you speedrun multiple games. +- If you change your display setup (like using a new monitor, or upgrading to Windows 11), you may need to readjust or reselect your Capture Region. + +## Timer Integration ### Timer Global Hotkeys @@ -134,76 +207,42 @@ The start image is similar to the reset image. You can only have one start image - All of these actions can also be handled by their corresponding buttons. - Note that pressing your Pause Hotkey does not serve any function in AutoSplit itself and is strictly used for the Pause flag. -### Group dummy splits when undoing / skipping - -If this option is disabled, AutoSplit will not account for dummy splits when undoing/skipping. Meaning it will cycle through the images normally even if they have the dummy flag `{d}` applied to them. - -If it is enabled, AutoSplit will group dummy splits together with a real split when undoing/skipping. This basically allows you to tie one or more dummy splits to a real split to keep it as in sync as possible with the real splits in LiveSplit/wsplit. - -Examples: -Given these splits: 1 dummy, 2 normal, 3 dummy, 4 dummy, 5 normal, 6 normal. - -In this situation you would have only 3 splits in LiveSplit/wsplit (even though there are 6 split images, only 3 are "real" splits). This basically results in 3 groups of splits: 1st split is images 1 and 2. 2nd split is images 3, 4 and 5. 3rd split is image 6. - -- If you are in the 1st or 2nd image and press the skip key, it will end up on the 3rd image -- If you are in the 3rd, 4th or 5th image and press the undo key, it will end up on the 1st image -- If you are in the 3rd, 4th or 5th image and press the skip key, it will end up on the 6th image -- If you are in the 6th image and press the undo key, it will end up on the 3rd image - -### Loop Split Images - -If this option is enabled, when the last split meets the threshold and splits, AutoSplit will loop back to the first split image and continue comparisons. -If this option is disabled, when the last split meets the threshold and splits, AutoSplit will stop running comparisons. -This option does not loop single, specific images. See the Custom Split Image Settings section above for this feature. - -### Auto Start On Reset - -If this option is enabled, when the reset hotkey is hit, the reset button is pressed, or the reset split image meets its threshold, AutoSplit will reset and automatically start again back at the first split image. -If this option is disabled, when the reset hotkey is hit, the reset button is pressed, or the reset split image meets its threshold, AutoSplit will stop running comparisons. - -### Settings - -- Settings files use the extension `.pkl`. Settings files can be saved and opened by using File -> Save Settings As... and File -> Load Settings. A settings file can be loaded upon opening AutoSplit if placed in the same directory as AutoSplit.exe. -- The settings in the settings file include split image directory, capture region, capture region dimensions, fps limit, threshold and pause time settings, all hotkeys, "Group dummy splits when undoing/skipping" check box, "Loop Split Images" check box, and "Auto Start On Reset" check box. -- You can save multiple settings files, which is useful if you speedrun multiple games. -- If you are upgrading to Windows 11, it's possible that save files may not transfer perfectly. You may need to readjust or reselect your Capture Region, for example. - -## LiveSplit Integration +### LiveSplit Integration The AutoSplit LiveSplit Component will directly connect AutoSplit with LiveSplit. LiveSplit integration is only supported in AutoSplit v1.6.0 or higher. This integration will allow you to: - Use hotkeys directly from LiveSplit to control AutoSplit and LiveSplit together -- Load AutoSplit and any AutoSplit settings automatically when opening a LiveSplit layout. +- Load AutoSplit and any AutoSplit profile automatically when opening a LiveSplit layout. -### LiveSplit Integration Tutorial +#### LiveSplit Integration Tutorial - Click [here](https://github.com/Toufool/LiveSplit.AutoSplitIntegration/raw/main/update/Components/LiveSplit.AutoSplitIntegration.dll) to download the latest component. - Place the .dll file into your `[...]\LiveSplit\Components` folder. - Open LiveSplit -> Right Click -> Edit Layout -> Plus Button -> Control -> AutoSplit Integration. - Click Layout Settings -> AutoSplit Integration -- Click the Browse buttons to locate your AutoSplit Path (path to AutoSplit.exe) and Settings Path (path to your AutoSplit `.pkl` settings file) respectively. - - If you have not yet set saved a settings file, you can do so using AutoSplit, and then go back and set your Settings Path. -- Once set, click OK, and then OK again to close the Layout Editor. Right click LiveSplit -> Save Layout to save your layout. AutoSplit and its settings will now open automatically when opening that LiveSplit Layout `.lsl` file. -- If you are using any dummy splits, it is recommended that you check the "Group dummy splits when undoing / skipping" checkbox when using LiveSplit integration so that your LiveSplit splits stay in sync with AutoSplit. +- Click the Browse buttons to locate your AutoSplit Path (path to AutoSplit executable) and Profile Path (path to your AutoSplit `.toml` profile file) respectively. + - If you have not yet set saved a profile, you can do so using AutoSplit, and then go back and set your Settings Path. +- Once set, click OK, and then OK again to close the Layout Editor. Right click LiveSplit -> Save Layout to save your layout. AutoSplit and your selected profile will now open automatically when opening that LiveSplit Layout `.lsl` file. ## Known Limitations - For many games, it will be difficult to find a split image for the last split of the run. - The window of the capture region cannot be minimized. -- Capturing a hardware accelerated window or using Windows 11 altogether will cause performance drops. But as long as the window you are capturing (not the selected region, but rather the actual window size) is not too large, you should still be able to obtain a Max FPS of over 60. ## Resources Still need help? + - [Open an issue](../../issues) -- Join the [AutoSplit Discord](https://discord.gg/Qcbxv9y) +- Join the [AutoSplit Discord +![AutoSplit Discord](https://badgen.net/discord/members/Qcbxv9y)](https://discord.gg/Qcbxv9y) ## Credits - Created by [Toufool](https://twitter.com/Toufool) and [Faschz](https://twitter.com/faschz). - [Harutaka Kawamura](https://github.com/harupy/) for the snipping tool code that I used to integrate into the autosplitter. - [amaringos](https://twitter.com/amaringos) for the icon. -- [ZanasoBayncuh](https://twitter.com/ZanasoBayncuh) for motivating me to start this project back up and for all of the time spent testing and suggesting improvements. +- [Zana_G](https://www.twitch.tv/zana_g) for motivating me to start this project back up and for all of the time spent testing and suggesting improvements. - [Avasam](https://twitter.com/Avasam06) for their continued work on making an incredible amount of improvements and changes to AutoSplit while I have not had the time/motivation to do so. - [KaDiWa](https://github.com/KaDiWa4) for the LiveSplit integration. - [Tyron18](https://twitter.com/Tyron18_) for assisting with Windows 11 testing. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..ee4fd8d7 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | :-------: | +| main | :white_check_mark: | +| dev* | :white_check_mark: | +| everything else | WIP branches, security support may be lacking | + +## Reporting a Vulnerability + +This is a small project, maintained by a few volunteers of the community. Just open up an issue, be clear, explain why it's a vulnerability as well as your recommendations to fix it. Please provide sources if possible. diff --git a/build instructions.md b/build instructions.md new file mode 100644 index 00000000..d40c8317 --- /dev/null +++ b/build instructions.md @@ -0,0 +1,25 @@ +# Install and Build instructions + +## Requirements + +### Windows + +- Microsoft Visual C++ 14.0 or greater may be required to build the executable. Get it with [Microsoft C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/). + +### All platforms + +- [Python](https://www.python.org/downloads/) 3.9+. +- [Node](https://nodejs.org) is optional, but required for complete linting. + - Alternatively you can install the [pyright python wrapper](https://pypi.org/project/pyright/) which has a bit of an overhead delay. +- [VSCode](https://code.visualstudio.com/Download) is not required, but highly recommended. + - Everything already configured in the workspace, including Run (F5) and Build (Ctrl+Shift+B) commands, default shell, and recommended extensions. + - [PyCharm](https://www.jetbrains.com/pycharm/) is also a good Python IDE, but nothing is configured. If you are a PyCharm user, feel free to open a PR with all necessary workspace configurations! + +## Install and Build steps + +- Read [requirements.txt](/scripts/requirements.txt) for more information on how to install, run and build the python code. + - Run `./scripts/install.ps1` to install all dependencies. + - Run the app directly with `./scripts/start.ps1 [--auto-controlled]`. + - Or debug by pressing `F5` in VSCode + - Run `./scripts/build.ps1` or press `CTRL+Shift+B` in VSCode to build an executable. +- Recompile resources after modifications by running `./scripts/compile_resources.ps1`. diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..a49a7797 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,21 @@ +; We don't run mypy in the CI. This is just to help anyone who would like to use it manually. +; Namely, the mypy_primer tool. +[mypy] +strict = true +; Implicit return types ! +disallow_untyped_calls = false +disallow_untyped_defs = false +disallow_incomplete_defs = false + +; Of course my stubs are going to be incomplete. Otherwise they'd be on typeshed! +; Mypy becomes really whack with its errors inside these stubs though +mypy_path = typings,src +; exclude doesn't work with strict=true Why? +exclude = .*(typings|gen)/.* + +[mypy-gen.*,cv2.*,] +; strict=false ; Doesn't work in overrides +follow_imports = skip +implicit_reexport = true +strict_optional = false +disable_error_code = attr-defined, misc, name-defined diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..5e66b414 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,142 @@ +# https://beta.ruff.rs/docs/configuration +[tool.ruff] +target-version = "py39" +line-length = 120 +select = ["ALL"] +# https://beta.ruff.rs/docs/rules +ignore = [ + ### + # Not needed or wanted + ### + "D1", # pydocstyle Missing doctring + "D401", # pydocstyle: non-imperative-mood + "EM", # flake8-errmsg + "FBT", # flake8-boolean-trap + "INP", # flake8-no-pep420 + "ISC003", # flake8-implicit-str-concat: explicit-string-concatenation + # Short messages are still considered "long" messages + "TRY003", # tryceratops : raise-vanilla-args + # Don't remove commented code, also too inconsistant + "ERA001", # eradicate: commented-out-code + # contextlib.suppress is roughly 3x slower than try/except + "SIM105", # flake8-simplify: use-contextlib-suppress + # Checked by type-checker (pyright) + "ANN", # flake-annotations + "TCH", # flake8-type-checking + # Already shown by Pylance, checked by pyright, and can be caused by overloads. + "ARG002", # Unused method argument + # We want D213: multi-line-summary-second-line and D211: no-blank-line-before-class + "D203", # pydocstyle: one-blank-line-before-class + "D212", # pydocstyle: multi-line-summary-first-line + + ### + # Specific to this project + ### + # We have some Pascal case module names + "N999", # pep8-naming: Invalid module name + # Print are used as debug logs + "T20", # flake8-print + "D205", # Not all docstrings have a short description + desrciption + + ### FIXME (no warnings in Ruff yet: https://github.com/charliermarsh/ruff/issues/1256): + "PTH", + # Ignore until linux support + "EXE", +] + +[tool.ruff.per-file-ignores] +"typings/**/*.pyi" = [ + "F821", # https://github.com/charliermarsh/ruff/issues/3011 + # The following can't be controlled for external libraries: + "N8", # Naming conventions + "A", # Shadowing builtin names + "PLR0913", # Argument count +] + +# https://beta.ruff.rs/docs/settings/#flake8-implicit-str-concat +[tool.ruff.flake8-implicit-str-concat] +allow-multiline = false + +# https://beta.ruff.rs/docs/settings/#isort +[tool.ruff.isort] +combine-as-imports = true +split-on-trailing-comma = false +required-imports = ["from __future__ import annotations"] +# Unlike isort, Ruff only counts relative imports as local-folder by default for know. +# https://github.com/charliermarsh/ruff/issues/2419 +# https://github.com/charliermarsh/ruff/issues/3115 +known-local-folder = [ + "capture_method", + "gen", + "AutoControlledWorker", + "AutoSplit", + "AutoSplitImage", + "compare", + "error_messages", + "error_messages", + "hotkeys", + "menu_bar", + "region_selection", + "split_parser", + "user_profile", + "utils", +] + +# https://beta.ruff.rs/docs/settings/#mccabe +[tool.ruff.mccabe] +# Hard limit, arbitrary to 4 bytes +max-complexity = 31 +# Arbitrary to 2 bytes, same as SonarLint +# max-complexity = 15 + +[tool.ruff.pylint] +# Arbitrary to 1 byte, same as SonarLint +max-args = 7 +# At least same as max-complexity +max-branches = 15 + +# https://github.com/hhatto/autopep8#usage +# https://github.com/hhatto/autopep8#more-advanced-usage +[tool.autopep8] +max_line_length = 120 +aggressive = 3 +ignore = [ + "E124", # Closing bracket may not match multi-line method invocation style (enforced by add-trailing-comma) + "E70", # Allow ... on same line as def +] + +# https://github.com/microsoft/pyright/blob/main/docs/configuration.md#sample-pyprojecttoml-file +[tool.pyright] +typeCheckingMode = "strict" +# Prefer `pyright: ignore` +enableTypeIgnoreComments = false +# Extra strict +reportImplicitStringConcatenation = "error" +reportCallInDefaultInitializer = "error" +reportMissingSuperCall = "none" # False positives on base classes +reportPropertyTypeMismatch = "error" +reportUninitializedInstanceVariable = "error" +reportUnnecessaryTypeIgnoreComment = "error" +# Exclude from scanning when running pyright +exclude = [ + # Auto generated, fails some strict pyright checks + "src/gen/", +] +# Ignore must be specified for Pylance to stop displaying errors +ignore = [ + # We expect stub files to be incomplete or contain useless statements + "**/*.pyi", +] +reportUnusedCallResult = "none" +# Type stubs may not be completable +reportMissingTypeStubs = "warning" +# False positives with TYPE_CHECKING +reportImportCycles = "information" +# False positives with PySide .connect +reportFunctionMemberAccess = "none" +# Extra runtime safety +reportUnnecessaryComparison = "warning" +# Using Flake8/Ruff instead +reportUnusedImport = "none" +# numpy has way too many complex types that triggers this +reportUnknownMemberType = "none" diff --git a/res/2.0.0_gif.gif b/res/2.0.0_gif.gif new file mode 100644 index 00000000..b06242dc Binary files /dev/null and b/res/2.0.0_gif.gif differ diff --git a/res/about.ui b/res/about.ui index 1d833949..329dac8c 100644 --- a/res/about.ui +++ b/res/about.ui @@ -1,7 +1,7 @@ - aboutAutoSplitWidget - + AboutAutoSplitWidget + 0 @@ -34,7 +34,7 @@ :/resources/icon.ico:/resources/icon.ico - + 180 @@ -47,12 +47,12 @@ OK - + 10 44 - 161 + 241 32 @@ -60,12 +60,12 @@ <html><head/><body><p>Created by <a href="https://twitter.com/toufool"><span style=" text-decoration: underline; color:#0000ff;">Toufool</span></a> and <a href="https://twitter.com/faschz"><span style=" text-decoration: underline; color:#0000ff;">Faschz</span></a><br/>Maintained by <a href="https://twitter.com/Avasam06"><span style=" text-decoration: underline; color:#0000ff;">Avasam</span></a></p></body></html> - + 10 21 - 161 + 241 16 @@ -73,24 +73,25 @@ Version: - + - 30 - 95 - 204 - 32 + 10 + 90 + 241 + 41 - If you enjoy using this program, please -consider donating. Thank you! + If you enjoy using this program, +please consider donating. +Thank you! Qt::AlignCenter - + 60 @@ -102,32 +103,47 @@ consider donating. Thank you! <html><head/><body><p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&amp;business=BYRHQG69YRHBA&amp;item_name=AutoSplit+development&amp;currency_code=USD&amp;source=url"><img src=":/resources/btn_donateCC_LG.png"/></a></p></body></html> + + :/resources/btn_donateCC_LG.png + Qt::AlignCenter - + 190 17 - 62 - 71 + 64 + 64 - <html><head/><body><p><img src=":/resources/icon.ico"/></p></body></html> + + + + :/resources/icon.ico + + + true + ok_button + donate_text_label + donate_button_label + icon_label + version_label + created_by_label - okButton + ok_button clicked() - aboutAutoSplitWidget + AboutAutoSplitWidget close() diff --git a/res/design.ui b/res/design.ui index 1e74b9d8..34f92d5a 100644 --- a/res/design.ui +++ b/res/design.ui @@ -6,26 +6,20 @@ 0 0 - 632 - 490 + 786 + 426 - - - 0 - 0 - - - 632 - 490 + 786 + 426 - 632 - 490 + 786 + 426 @@ -40,96 +34,29 @@ :/resources/icon.ico:/resources/icon.ico - - - - - Qt::LeftToRight - - - - - - 20 - 12 - 98 - 16 - - - - Split Image Folder: - - - - - - 130 - 10 - 412 - 22 - - - - true - - - + + - 540 - 9 - 75 - 24 - - - - Qt::NoFocus - - - Browse... - - - - - - 25 - 145 - 7 - 16 - - - - X - - - - - true - - - - 120 - 252 - 129 + 11 + 140 + 49 20 - Live Capture Region - - - true + X - - false + + Qt::AlignCenter - + - 5 - 70 - 101 + 10 + 67 + 107 23 @@ -140,45 +67,13 @@ Select Region - + - 7 - 410 - 151 - 16 - - - - Default similarity threshold: - - - - - - 155 - 408 - 61 - 22 - - - - 1.000000000000000 - - - 0.010000000000000 - - - 0.900000000000000 - - - - - - 500 - 425 + 657 + 369 121 - 31 + 27 @@ -188,13 +83,16 @@ Start Auto Splitter - + + + false + - 500 - 390 + 657 + 339 121 - 31 + 27 @@ -204,386 +102,96 @@ Reset - - - - 494 - 250 - 64 - 24 - - - - Qt::NoFocus - - - Undo Split - - - - - - 560 - 250 - 61 - 24 - - - - Qt::NoFocus - - - Skip Split - - - - - - 7 - 439 - 131 - 16 - - - - Default pause time (sec): + + + false - - - 5 - 225 - 53 - 21 + 657 + 310 + 59 + 27 Qt::NoFocus - Max FPS - - - - - - 87 - 225 - 20 - 20 - - - - FPS + Undo - + - true - - - - 7 - 323 - 124 - 20 - - - - Show live similarity - - - true - - false - - - - true - - - - 7 - 350 - 145 - 20 - - - - Show highest similarity - - - true - - - false - - - - - - 171 - 326 - 46 - 16 - - - - - - - - - - 171 - 352 - 46 - 16 - - - - - - - - - - 230 - 317 - 58 - 16 - - - - Start / Split - - - - 230 - 341 - 28 - 16 - - - - Reset - - - - - - 230 - 367 - 48 - 16 - - - - Skip Split - - - - - - 230 - 393 - 55 - 16 - - - - Undo Split - - - - - - 300 - 314 - 81 - 20 - - - - true - - - - - - 300 - 391 - 81 - 20 - - - - Qt::StrongFocus - - - true - - - - - - 300 - 365 - 81 - 20 - - - - true - - - - - - 300 - 339 - 81 - 20 - - - - true - - - - - - 390 - 314 - 81 - 21 + 719 + 310 + 59 + 27 Qt::NoFocus - Set Hotkey + Skip - + - 390 - 339 - 81 - 21 - - - - Qt::NoFocus - - - Set Hotkey - - - - - - 390 - 365 - 81 - 21 + 10 + 270 + 53 + 23 Qt::NoFocus - - Set Hotkey - - - - - - 390 - 391 - 81 - 21 - - - - Qt::NoFocus + + calculate the average max FPS of the set capture region - Set Hotkey - - - - - - 220 - 296 - 2 - 163 - - - - QFrame::Plain - - - 1 - - - Qt::Vertical + Max FPS - + - 230 - 291 - 251 + 92 + 272 + 21 20 - Timer Global Hotkeys - - - Qt::AlignCenter + FPS - + - 490 - 296 - 2 - 163 + 127 + 67 + 320 + 240 + + QFrame::Box + QFrame::Plain 1 - - Qt::Vertical - - - - - - 120 - 70 - 240 - 180 - - - - QFrame::Box + + 0 @@ -592,13 +200,13 @@ Qt::AlignCenter - + - 380 - 70 - 240 - 180 + 456 + 67 + 320 + 240 @@ -611,50 +219,59 @@ Qt::AlignCenter - + - 450 - 50 - 102 - 16 + 456 + 31 + 318 + 20 - Current Split Image + Current Image + + + Qt::AlignCenter - + - 12 - 185 - 33 - 16 + 11 + 190 + 49 + 20 Width + + Qt::AlignCenter + - + 66 - 185 - 41 - 16 + 190 + 49 + 20 Height + + Qt::AlignCenter + - + - 58 - 225 + 65 + 272 26 20 @@ -663,15 +280,18 @@ - + - 6 - 200 - 44 - 22 + 11 + 210 + 51 + 24 + + QAbstractSpinBox::CorrectToNearestValue + 1 @@ -682,15 +302,18 @@ 640 - + - 62 - 200 - 44 - 22 + 66 + 210 + 51 + 24 + + QAbstractSpinBox::CorrectToNearestValue + 1 @@ -701,83 +324,45 @@ 480 - + - 200 - 50 - 82 - 16 + 127 + 31 + 318 + 20 Capture Region - - - - - 8 - 252 - 51 - 16 - - - - FPS Limit: - - - - - - 62 - 250 - 44 - 22 - - - - - - - 0 - - - 30.000000000000000 - - - 5000.000000000000000 - - - 1.000000000000000 - - - 60.000000000000000 + + Qt::AlignCenter - + - 380 - 270 - 241 + 484 + 49 + 264 20 - + - Qt::AlignCenter - + - 260 - 250 - 101 - 24 + 10 + 240 + 107 + 23 @@ -787,21 +372,21 @@ Take Screenshot - + - 6 + 11 160 - 44 - 22 + 51 + 24 - - false - QAbstractSpinBox::UpDownArrows + + QAbstractSpinBox::CorrectToNearestValue + 0 @@ -815,20 +400,17 @@ 0 - + - 62 + 66 160 - 44 - 22 + 51 + 24 - - false - - - QAbstractSpinBox::UpDownArrows + + QAbstractSpinBox::CorrectToNearestValue 0 @@ -840,442 +422,654 @@ 0 - + - 81 - 145 - 7 - 16 + 66 + 140 + 49 + 20 Y - - - - - 125 - 296 - 91 - 22 - + + Qt::AlignCenter - - - L2 Norm - - - - - Histograms - - - - - pHash - - - + - 155 - 437 - 61 - 22 + 10 + 119 + 107 + 23 - - 999999999.000000000000000 + + Qt::NoFocus - - 1.000000000000000 + + Align the capture region using a reference image - - 10.000000000000000 + + Align Region - + - 7 - 298 - 110 - 16 + 10 + 93 + 107 + 23 + + Qt::NoFocus + - Comparison Method + Select Window - + - 5 - 95 - 101 - 23 + 696 + 5 + 81 + 24 Qt::NoFocus - Align Region + Browse... - + - 230 - 442 - 261 - 20 + 10 + 9 + 111 + 16 - Group dummy splits when undoing/skipping - - - false + Split Image Folder: - + - 5 - 120 - 101 - 23 + 127 + 6 + 561 + 22 - - Qt::NoFocus - - - Select Window + + true - + - 379 - 252 - 113 + 127 + 49 + 318 20 - Image Loop: + - + + + Qt::AlignCenter - + - 230 - 418 - 31 - 16 + 458 + 313 + 81 + 20 - Pause + Image Loop: - + - 300 - 416 - 81 - 20 + 127 + 312 + 321 + 84 - - Qt::StrongFocus + + Similarity Viewer - - true + + Qt::AlignCenter + + + + 133 + 22 + 57 + 16 + + + + Live + + + Qt::AlignCenter + + + + + + 197 + 22 + 57 + 16 + + + + Highest + + + Qt::AlignCenter + + + + + + 261 + 22 + 56 + 16 + + + + Threshold + + + Qt::AlignCenter + + + + + + 0 + 39 + 322 + 3 + + + + Qt::Horizontal + + + + + + 10 + 42 + 111 + 16 + + + + Current Image + + + + + + 10 + 64 + 111 + 16 + + + + Reset Image + + + + + + 0 + 61 + 322 + 3 + + + + Qt::Horizontal + + + + + + 129 + 20 + 3 + 62 + + + + Qt::Vertical + + + + + + 193 + 20 + 3 + 95 + + + + Qt::Vertical + + + + + + 257 + 20 + 3 + 95 + + + + Qt::Vertical + + + + + + 133 + 43 + 57 + 16 + + + + - + + + Qt::AlignCenter + + + + + + 197 + 43 + 57 + 16 + + + + - + + + Qt::AlignCenter + + + + + + 260 + 43 + 57 + 16 + + + + - + + + Qt::AlignCenter + + + + + + 133 + 64 + 57 + 16 + + + + - + + + Qt::AlignCenter + + + + + + 197 + 64 + 57 + 16 + + + + - + + + Qt::AlignCenter + + + + + + 260 + 64 + 57 + 16 + + + + - + + + Qt::AlignCenter + + - + - 390 - 416 - 81 - 21 + 457 + 369 + 131 + 27 Qt::NoFocus - Set Hotkey + Reload Start Image - - - true - + - 500 - 340 - 117 + 458 + 344 + 121 20 - Loop Split Images - - - false - - - false + Start Image Status: - + - 500 - 360 - 126 + 577 + 344 + 81 20 - Auto Start On Reset - - - false - - - false + status - + - 500 - 302 + 537 + 313 121 - 31 + 20 - - Qt::NoFocus - - Reload Start Image + N/A - + + + false + - 120 - 270 - 241 - 16 + 455 + 49 + 27 + 18 - - Start image: + + Qt::NoFocus - - - - - 7 - 380 - 151 - 16 - + + Previous image - Current similarity threshold: + < - + + + false + - 171 - 380 - 46 - 16 + 750 + 49 + 27 + 18 - - + + Qt::NoFocus - - splitimagefolderLabel - splitimagefolderLineEdit - browseButton - xLabel - liveimageCheckBox - selectregionButton - similaritythresholdLabel - similaritythresholdDoubleSpinBox - startautosplitterButton - resetButton - undosplitButton - skipsplitButton - pauseLabel - checkfpsButton - fpsLabel - showlivesimilarityCheckBox - showhighestsimilarityCheckBox - livesimilarityLabel - highestsimilarityLabel - splitLabel - resetLabel - skiptsplitLabel - undosplitLabel - splitLineEdit - undosplitLineEdit - skipsplitLineEdit - resetLineEdit - setsplithotkeyButton - setresethotkeyButton - setskipsplithotkeyButton - setundosplithotkeyButton - line_left - timerglobalhotkeysLabel - line_right - currentsplitimageLabel - liveImage - currentSplitImage - widthLabel - heightLabel - fpsvalueLabel - widthSpinBox - heightSpinBox - captureregionLabel - fpslimitLabel - fpslimitSpinBox - currentsplitimagefileLabel - takescreenshotButton - xSpinBox - ySpinBox - yLabel - comparisonmethodComboBox - pauseDoubleSpinBox - comparisonmethodLabel - alignregionButton - groupDummySplitsCheckBox - selectwindowButton - imageloopLabel - pausehotkeyLabel - pausehotkeyLineEdit - setpausehotkeyButton - loopCheckBox - autostartonresetCheckBox - startImageReloadButton - startImageLabel - currentsimilaritythresholdLabel - currentsimilaritythresholdnumberLabel + + Next image + + + > + + + x_label + select_region_button + start_auto_splitter_button + reset_button + undo_split_button + skip_split_button + check_fps_button + fps_label + current_image_label + live_image + current_split_image + width_label + height_label + fps_value_label + width_spinbox + height_spinbox + capture_region_label + current_image_file_label + take_screenshot_button + x_spinbox + y_spinbox + y_label + align_region_button + select_window_button + browse_button + split_image_folder_label + split_image_folder_input + capture_region_window_label + image_loop_label + similarity_viewer_groupbox + reload_start_image_button + start_image_status_label + start_image_status_value_label + image_loop_value_label + previous_image_button + next_image_button - + 0 0 - 632 + 786 22 - + Help - - - - + + + + + + + - + File - - - + + + + + - - + + - + View Help + + F1 + + + Qt::ApplicationShortcut + - + - About + About AutoSplit + + + QAction::AboutRole - + - Split Image Settings + Save Profile + + + Ctrl+S + + + Qt::ApplicationShortcut - + - Save Settings + Load Profile + + + Ctrl+O + + + Qt::ApplicationShortcut - + - Load Settings + Save Profile As... + + + Ctrl+Shift+S + + + Qt::ApplicationShortcut - + - Save Settings As... + Check For Updates - + - Check for Updates... + Settings + + + Ctrl+, + + + Qt::ApplicationShortcut + + + QAction::PreferencesRole - + true true - - true + + Check For Updates On Open + + + + + About Qt + + + QAction::AboutQtRole + + - Check for Updates on Open + About Qt for Python + + + QAction::AboutQtRole - splitimagefolderLineEdit - xSpinBox - ySpinBox - widthSpinBox - heightSpinBox - fpslimitSpinBox - liveimageCheckBox - comparisonmethodComboBox - showlivesimilarityCheckBox - showhighestsimilarityCheckBox - similaritythresholdDoubleSpinBox - pauseDoubleSpinBox - splitLineEdit - resetLineEdit - skipsplitLineEdit - undosplitLineEdit - groupDummySplitsCheckBox + split_image_folder_input + x_spinbox + y_spinbox + width_spinbox + height_spinbox diff --git a/res/example1.6.0.gif b/res/example1.6.0.gif deleted file mode 100644 index 7e4854c6..00000000 Binary files a/res/example1.6.0.gif and /dev/null differ diff --git a/res/icon.ico b/res/icon.ico index fae0f994..485824af 100644 Binary files a/res/icon.ico and b/res/icon.ico differ diff --git a/res/settings.ui b/res/settings.ui new file mode 100644 index 00000000..8681dc44 --- /dev/null +++ b/res/settings.ui @@ -0,0 +1,747 @@ + + + SettingsWidget + + + + 0 + 0 + 290 + 664 + + + + + 290 + 664 + + + + + 290 + 664 + + + + + 9 + + + + Settings + + + + :/resources/icon.ico:/resources/icon.ico + + + + + 10 + 200 + 270 + 181 + + + + Capture Settings + + + + + 150 + 24 + 51 + 24 + + + + QAbstractSpinBox::CorrectToNearestValue + + + 20 + + + 240 + + + 60 + + + + + + 6 + 27 + 141 + 16 + + + + This value will limit the amount of frames per second that AutoSplit will run comparisons + + + Comparison FPS Limit: + + + + + + 6 + 49 + 141 + 20 + + + + Live Capture Region + + + true + + + false + + + + + + 6 + 97 + 251 + 22 + + + + + + + 6 + 75 + 151 + 16 + + + + Capture method: + + + + + + 6 + 126 + 151 + 16 + + + + Capture device: + + + + + false + + + + 6 + 148 + 251 + 22 + + + + Scanning for existing devices... + + + + + + + 10 + 390 + 270 + 266 + + + + Image Settings + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + false + + + false + + + + + 170 + 25 + 91 + 22 + + + + L2 Norm: +This method should be fine to use for most cases. +It finds the difference between each pixel, squares it, sums it over the entire image and takes the square root. +This is very fast but is a problem if your image is high frequency. +Any translational movement or rotation can cause similarity to be very different. + +Histograms: +An explanation on Histograms comparison can be found here +https://mpatacchiola.github.io/blog/2016/11/12/the-simplest-classifier-histogram-intersection.html +This is a great method to use if you are using several masked images. +> This algorithm is particular reliable when the colour is a strong predictor of the object identity. +> The histogram intersection [...] is robust to occluding objects in the foreground. + +Perceptual Hash: +An explanation on pHash comparison can be found here +http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html +It is highly recommended to NOT use pHash if you use masked images, or it'll be very inaccurate. + + + + L2 Norm + + + + + Histograms + + + + + pHash + + + + + + + 6 + 28 + 171 + 16 + + + + Default Comparison Method: + + + + + + 6 + 118 + 171 + 16 + + + + Default Pause Time (sec): + + + + + + 170 + 115 + 91 + 24 + + + + The amount of time in seconds that comparison will be paused before moving to the next image. + + + QAbstractSpinBox::CorrectToNearestValue + + + 2 + + + 999999999.000000000000000 + + + 1.000000000000000 + + + 10.000000000000000 + + + + + + 6 + 58 + 171 + 16 + + + + Default Similarity Threshold: + + + + + + 170 + 55 + 51 + 24 + + + + Threshold that the live similarity will need to go above to consider the image a match. + + + QAbstractSpinBox::CorrectToNearestValue + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.900000000000000 + + + + + + 6 + 143 + 261 + 20 + + + + Loop Last Split Image to First Split Image + + + false + + + false + + + + + + 6 + 193 + 261 + 71 + + + + + 8 + true + + + + <html><head/><body><p>Image settings and flags can be set per image through the image file name. These will override the default values. View the <a href="https://github.com/{GITHUB_REPOSITORY}#readme"><span style="text-decoration: underline; color:#0000ff;">README</span></a> for full details on all available custom image settings.</p></body></html> + + + true + + + + + + 6 + 88 + 171 + 16 + + + + Default Delay Time (ms): + + + + + + 170 + 85 + 91 + 24 + + + + After an image is matched, this is the amount of time in millseconds that will be delayed before splitting. + + + QAbstractSpinBox::CorrectToNearestValue + + + 999999999 + + + + + + 140 + 218 + 71 + 31 + + + + + Segoe UI + 8 + true + + + + README + + + + 0 + 0 + + + + This is a workaround because custom_image_settings_info_label simply will not open links with a left click no matter what we tried. + + + + + + 6 + 168 + 171 + 20 + + + + Enable auto reset image + + + true + + + + + + + 10 + 10 + 270 + 191 + + + + Hotkeys + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + false + + + false + + + + + 180 + 130 + 81 + 22 + + + + Qt::NoFocus + + + Set Hotkey + + + + + + 80 + 30 + 94 + 22 + + + + + + + true + + + + + + 80 + 80 + 94 + 22 + + + + + + + true + + + + + + 6 + 32 + 71 + 16 + + + + Start / Split: + + + + + + 80 + 55 + 94 + 22 + + + + + + + true + + + + + + 180 + 80 + 81 + 22 + + + + Qt::NoFocus + + + Set Hotkey + + + + + + 6 + 57 + 41 + 16 + + + + Reset: + + + + + + 180 + 55 + 81 + 21 + + + + Qt::NoFocus + + + Set Hotkey + + + + + + 180 + 30 + 81 + 22 + + + + Qt::NoFocus + + + Set Hotkey + + + + + + 6 + 132 + 41 + 16 + + + + Pause: + + + + + + 80 + 130 + 94 + 22 + + + + + + + true + + + + + + 6 + 82 + 71 + 16 + + + + Undo Split: + + + + + + 180 + 105 + 81 + 22 + + + + Qt::NoFocus + + + Set Hotkey + + + + + + 6 + 107 + 61 + 16 + + + + Skip Split: + + + + + + 80 + 105 + 94 + 22 + + + + + + + true + + + + + + 180 + 155 + 81 + 22 + + + + Qt::NoFocus + + + Set Hotkey + + + + + + 6 + 154 + 71 + 31 + + + + Toggle auto +reset image + + + + + + 80 + 155 + 94 + 22 + + + + + + + true + + + + + + set_split_hotkey_button + set_reset_hotkey_button + set_undo_split_hotkey_button + set_skip_split_hotkey_button + set_pause_hotkey_button + fps_limit_spinbox + live_capture_region_checkbox + capture_method_combobox + capture_device_combobox + default_comparison_method + default_similarity_threshold_spinbox + default_delay_time_spinbox + default_pause_time_spinbox + loop_splits_checkbox + + + + diff --git a/res/splash.png b/res/splash.png new file mode 100644 index 00000000..2858bdb9 Binary files /dev/null and b/res/splash.png differ diff --git a/res/update_checker.ui b/res/update_checker.ui index 844d6d4f..6ff6ea85 100644 --- a/res/update_checker.ui +++ b/res/update_checker.ui @@ -9,26 +9,20 @@ 0 0 - 313 - 133 + 318 + 132 - - - 0 - 0 - - - 313 - 133 + 318 + 132 - 313 - 133 + 318 + 132 @@ -42,12 +36,16 @@ Update Checker - + + + :/resources/icon.ico:/resources/icon.ico + + - 17 + 10 10 - 281 + 251 16 @@ -58,15 +56,15 @@ - + There is an update available for AutoSplit. - + - 17 + 10 30 - 91 + 101 16 @@ -74,12 +72,12 @@ Current Version: - + - 17 + 10 50 - 81 + 101 16 @@ -87,25 +85,25 @@ Latest Version: - + - 17 - 76 - 241 + 10 + 80 + 141 16 - + Open download page? - + - 150 + 160 100 - 75 + 71 24 @@ -113,28 +111,28 @@ Qt::NoFocus - + Open - + - 230 + 240 100 - 75 + 71 24 - + Later - + - 120 + 110 30 - 181 + 191 16 @@ -142,12 +140,12 @@ - + - 120 + 110 50 - 181 + 191 16 @@ -155,12 +153,12 @@ - + - 17 - 100 - 141 + 10 + 102 + 151 20 @@ -169,10 +167,12 @@ - + + + - pushButtonRight + right_button clicked() UpdateChecker close() diff --git a/scripts/Qt Designer.lnk b/scripts/Qt Designer.lnk deleted file mode 100644 index 32ad191e..00000000 Binary files a/scripts/Qt Designer.lnk and /dev/null differ diff --git a/scripts/build.bat b/scripts/build.bat deleted file mode 100644 index d7ccc27f..00000000 --- a/scripts/build.bat +++ /dev/null @@ -1 +0,0 @@ -pyinstaller -w -F --icon=res\icon.ico "%~p0..\src\AutoSplit.py" diff --git a/scripts/build.ps1 b/scripts/build.ps1 new file mode 100644 index 00000000..f9825aaf --- /dev/null +++ b/scripts/build.ps1 @@ -0,0 +1,11 @@ +& "$PSScriptRoot/compile_resources.ps1" + +$arguments = @( + "$PSScriptRoot/../src/AutoSplit.py", + '--onefile', + '--windowed', + '--additional-hooks-dir=Pyinstaller/hooks', + '--icon=res/icon.ico', + '--splash=res/splash.png') + +Start-Process -Wait -NoNewWindow pyinstaller -ArgumentList $arguments diff --git a/scripts/compile_resources.bat b/scripts/compile_resources.bat deleted file mode 100644 index 91f134e4..00000000 --- a/scripts/compile_resources.bat +++ /dev/null @@ -1,5 +0,0 @@ -cd "%~dp0.." -pyuic6 ".\res\about.ui" -o ".\src\about.py" -pyuic6 ".\res\design.ui" -o ".\src\design.py" -pyuic6 ".\res\update_checker.ui" -o ".\src\update_checker.py" -pyside6-rcc ".\res\resources.qrc" -o ".\src\resources_rc.py" diff --git a/scripts/compile_resources.ps1 b/scripts/compile_resources.ps1 new file mode 100644 index 00000000..c7eba061 --- /dev/null +++ b/scripts/compile_resources.ps1 @@ -0,0 +1,35 @@ +$originalDirectory = $pwd +Set-Location "$PSScriptRoot/.." + +New-Item -Force -ItemType directory ./src/gen | Out-Null +pyside6-uic './res/about.ui' -o './src/gen/about.py' +pyside6-uic './res/design.ui' -o './src/gen/design.py' +pyside6-uic './res/settings.ui' -o './src/gen/settings.py' +pyside6-uic './res/update_checker.ui' -o './src/gen/update_checker.py' +pyside6-rcc './res/resources.qrc' -o './src/gen/resources_rc.py' +$files = Get-ChildItem ./src/gen/ *.py +foreach ($file in $files) { + (Get-Content $file.PSPath) | + ForEach-Object { $_ -replace 'import resources_rc', 'from . import resources_rc' } | + Set-Content $file.PSPath +} +Write-Host 'Generated code from .ui files' + +$build_vars_path = "$PSScriptRoot/../src/gen/build_vars.py" +$BUILD_NUMBER = If ($Env:GITHUB_EXCLUDE_BUILD_NUMBER -eq $true) { '' } Else { Get-Date -Format yyMMddHHmm } +$GITHUB_REPOSITORY = $Env:GITHUB_HEAD_REPOSITORY +If (-not $GITHUB_REPOSITORY) { + $repo_url = git config --get remote.origin.url + $GITHUB_REPOSITORY = $repo_url.substring(19, $repo_url.length - 19) -replace '\.git', '' +} +If (-not $GITHUB_REPOSITORY) { + $GITHUB_REPOSITORY = 'Toufool/AutoSplit' +} + +New-Item $build_vars_path -ItemType File -Force | Out-Null +Add-Content $build_vars_path "AUTOSPLIT_BUILD_NUMBER = `"$BUILD_NUMBER`"" +Add-Content $build_vars_path "AUTOSPLIT_GITHUB_REPOSITORY = `"$GITHUB_REPOSITORY`"" +Write-Host "Generated build number: `"$BUILD_NUMBER`"" +Write-Host "Set repository to `"$GITHUB_REPOSITORY`"" + +Set-Location $originalDirectory diff --git a/scripts/designer.bat b/scripts/designer.bat deleted file mode 100644 index 3b7af65e..00000000 --- a/scripts/designer.bat +++ /dev/null @@ -1,14 +0,0 @@ -@ECHO OFF - -IF NOT DEFINED PYTHONPATH ( - setlocal EnableDelayedExpansion - - SET n=0 - FOR /f "delims=" %%p in ('where python') do ( - SET pythonFiles[!n!]=%%p - SET /A n+=1 - ) - SET PYTHONPATH=!pythonFiles[0]! -) - -START "Qt Designer" "%PYTHONPATH:~0,-11%\Lib\site-packages\qt6_applications\Qt\bin\designer.exe" "%~d0%~p0..\res\design.ui" "%~d0%~p0..\res\about.ui" "%~d0%~p0..\res\update_checker.ui" diff --git a/scripts/designer.ps1 b/scripts/designer.ps1 new file mode 100644 index 00000000..a6a159f6 --- /dev/null +++ b/scripts/designer.ps1 @@ -0,0 +1,12 @@ +$qt6_applications_import = 'import qt6_applications; print(qt6_applications.__path__[0])' +$qt6_applications_path = python -c $qt6_applications_import +if ($null -eq $qt6_applications_path) { + Write-Host 'Designer not found, installing qt6_applications' + python -m pip install qt6_applications +} +$qt6_applications_path = python -c $qt6_applications_import +& "$qt6_applications_path/Qt/bin/designer" ` + "$PSScriptRoot/../res/design.ui" ` + "$PSScriptRoot/../res/about.ui" ` + "$PSScriptRoot/../res/settings.ui" ` + "$PSScriptRoot/../res/update_checker.ui" diff --git a/scripts/install.bat b/scripts/install.bat deleted file mode 100644 index 8b34f808..00000000 --- a/scripts/install.bat +++ /dev/null @@ -1,2 +0,0 @@ -py -3.9 -m pip install wheel -py -3.9 -m pip install -r "%~p0requirements.txt" diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100644 index 00000000..1a513d35 --- /dev/null +++ b/scripts/install.ps1 @@ -0,0 +1,10 @@ +# Installing Python dependencies +$dev = If ($Env:GITHUB_JOB -eq 'Build') { '' } Else { '-dev' } +# Ensures installation tools are up to date. This also aliases pip to pip3 on MacOS. +python -m pip install wheel pip setuptools --upgrade +pip install -r "$PSScriptRoot/requirements$dev.txt" --upgrade + +# Don't compile resources on the Build CI job as it'll do so in build script +If ($dev) { + & "$PSScriptRoot/compile_resources.ps1" +} diff --git a/scripts/lint.ps1 b/scripts/lint.ps1 new file mode 100644 index 00000000..b59e502e --- /dev/null +++ b/scripts/lint.ps1 @@ -0,0 +1,37 @@ +$originalDirectory = $pwd +Set-Location "$PSScriptRoot/.." +$exitCodes = 0 + +Write-Host "`nRunning formatting..." +autopep8 $(git ls-files '**.py*') --in-place +add-trailing-comma $(git ls-files '**.py*') --py36-plus + +Write-Host "`nRunning Ruff..." +ruff check . --fix +$exitCodes += $LastExitCode +if ($LastExitCode -gt 0) { + Write-Host "`Ruff failed ($LastExitCode)" -ForegroundColor Red +} +else { + Write-Host "`Ruff passed" -ForegroundColor Green +} + +Write-Host "`nRunning Pyright..." +$Env:PYRIGHT_PYTHON_FORCE_VERSION = 'latest' +npx pyright@latest src/ +$exitCodes += $LastExitCode +if ($LastExitCode -gt 0) { + Write-Host "`Pyright failed ($LastExitCode)" -ForegroundColor Red +} +else { + Write-Host "`Pyright passed" -ForegroundColor Green +} + +if ($exitCodes -gt 0) { + Write-Host "`nLinting failed ($exitCodes)" -ForegroundColor Red +} +else { + Write-Host "`nLinting passed" -ForegroundColor Green +} + +Set-Location $originalDirectory diff --git a/scripts/requirements-dev.txt b/scripts/requirements-dev.txt new file mode 100644 index 00000000..3f8d5b76 --- /dev/null +++ b/scripts/requirements-dev.txt @@ -0,0 +1,26 @@ +# Usage: ./scripts/install.ps1 +# +# If you're having issues with the libraries, you might want to first run: +# pip uninstall -y -r ./scripts/requirements-dev.txt +# +# Dependencies +-r requirements.txt +# +# Linters & Formatters +add-trailing-comma>=2.3.0 # Added support for with statement +autopep8>=2.0.0 # New checks +ruff>=0.0.262 # Avoid N802 violations for @override + Avoid adding required imports to stub files +# +# Run `./scripts/designer.ps1` to quickly open the bundled Qt Designer. +# Can also be downloaded externally as a non-python package +# qt6-applications +# Types +types-D3DShot ; sys_platform == 'win32' +types-keyboard +types-Pillow +types-psutil +types-PyAutoGUI +types-pyinstaller +types-pywin32 ; sys_platform == 'win32' +types-requests +types-toml diff --git a/scripts/requirements.txt b/scripts/requirements.txt index fde89aff..80c9c89a 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -2,29 +2,40 @@ # # Python: CPython 3.9+ # -# Usage: .\scripts\install.bat +# Usage: ./scripts/install.ps1 # # If you're having issues with the libraries, you might want to first run: -# pip3.9 uninstall -r requirements.txt +# pip uninstall -y -r ./scripts/requirements-dev.txt PySide6 shiboken6 # -# Creating AutoSplit.exe with PyInstaller: .\scripts\build.bat +# Creating an AutoSplit executable with PyInstaller: ./scripts/build.ps1 +# +# PySide6 dev wheels: https://download.qt.io/snapshots/ci/pyside/dev/?C=M;O=D # # Dependencies: -PyQt6 -opencv-python<=4.5.3.56 # https://github.com/pyinstaller/pyinstaller-hooks-contrib/issues/110 -Pillow -ImageHash -pywin32 -keyboard +certifi +ImageHash>=4.3.1 # Contains type information + setup as package not module +git+https://github.com/boppreh/keyboard.git#egg=keyboard # Fix install on macos and linux-ci https://github.com/boppreh/keyboard/pull/568 +numpy>=1.23.2 # Python 3.11 wheels +opencv-python-headless>=4.6 # Breaking changes importing cv2.cv2 packaging -pyautogui -PySide6 -flake8 -mypy -requests +Pillow>=9.2 # gnome-screeshot checks +psutil +PyAutoGUI +PyWinCtl>=0.0.42 # py.typed +PySide6-Essentials>=6.5 # fixes https://bugreports.qt.io/browse/PYSIDE-2189 and https://bugreports.qt.io/browse/PYSIDE-1603 +requests<=2.28.1 # 2.28.2 has issues with PyInstaller https://github.com/pyinstaller/pyinstaller-hooks-contrib/issues/534 +toml +typing-extensions>=4.4.0 # @override decorator support +# +# Build and compile resources +pyinstaller>=5.5 # Python 3.11 support +pyinstaller-hooks-contrib>=2022.9 # opencv-python 4.6 support. Changes for pywintypes and comtypes # -# Comment this out if you don't want to build AutoSplit.exe: -PyInstaller +# https://peps.python.org/pep-0508/#environment-markers # -# Comment this out if you don't want to install the PyQt designer -PyQt6-tools +# Windows-only dependencies: +git+https://github.com/andreaschiavinato/python_grabber.git#egg=pygrabber ; sys_platform == 'win32' # Completed types +pywin32>=301 ; sys_platform == 'win32' +winsdk>=v1.0.0b7 ; sys_platform == 'win32' # Python 3.11 support +git+https://github.com/ranchen421/D3DShot.git#egg=D3DShot ; sys_platform == 'win32' # D3DShot from PyPI with Pillow>=7.2.0 will install 0.1.3 instead of 0.1.5 +dxcam diff --git a/scripts/start.ps1 b/scripts/start.ps1 new file mode 100644 index 00000000..70d6fd8b --- /dev/null +++ b/scripts/start.ps1 @@ -0,0 +1,3 @@ +param ([string]$p1) +& "$PSScriptRoot/compile_resources.ps1" +python "$PSScriptRoot/../src/AutoSplit.py" $p1 diff --git a/src/AutoControlledThread.py b/src/AutoControlledThread.py new file mode 100644 index 00000000..f5e518a8 --- /dev/null +++ b/src/AutoControlledThread.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from PySide6 import QtCore + +import error_messages +import user_profile + +if TYPE_CHECKING: + from AutoSplit import AutoSplit + + +class AutoControlledThread(QtCore.QThread): + def __init__(self, autosplit: AutoSplit): + self.autosplit = autosplit + super().__init__() + + @QtCore.Slot() + def run(self): + while True: + try: + line = input() + except RuntimeError: + self.autosplit.show_error_signal.emit(error_messages.stdin_lost) + break + except EOFError: + continue + # This is for use in a Development environment + if line == "kill": + self.autosplit.closeEvent() + break + if line == "start": + self.autosplit.start_auto_splitter() + elif line in {"split", "skip"}: + self.autosplit.skip_split_signal.emit() + elif line == "undo": + self.autosplit.undo_split_signal.emit() + elif line == "reset": + self.autosplit.reset_signal.emit() + elif line.startswith("settings"): + # Allow for any split character between "settings" and the path + user_profile.load_settings(self.autosplit, line[9:]) + # TODO: Not yet implemented in AutoSplit Integration + # elif line == 'pause': + # self.pause_signal.emit() diff --git a/src/AutoSplit.py b/src/AutoSplit.py index c39b101e..728523f3 100644 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -1,1161 +1,858 @@ -#!/usr/bin/python3.9 -# -*- coding: utf-8 -*- -from typing import Callable, List, Optional +#!/usr/bin/python3 +from __future__ import annotations -from copy import copy -from PyQt6 import QtCore, QtGui, QtTest, QtWidgets -from win32 import win32gui -import sys -import os -import cv2 -import ctypes.wintypes import ctypes -import numpy as np +import os import signal -import time - -from menu_bar import about, VERSION, viewHelp, checkForUpdates -from settings_file import auto_split_directory -from split_parser import BELOW_FLAG, DUMMY_FLAG, PAUSE_FLAG -import capture_windows -import compare -import design -import error_messages -import split_parser - - -# Resize to these width and height so that FPS performance increases -COMPARISON_RESIZE_WIDTH = 320 -COMPARISON_RESIZE_HEIGHT = 240 -COMPARISON_RESIZE = (COMPARISON_RESIZE_WIDTH, COMPARISON_RESIZE_HEIGHT) -DISPLAY_RESIZE_WIDTH = 240 -DISPLAY_RESIZE_HEIGHT = 180 -DISPLAY_RESIZE = (DISPLAY_RESIZE_WIDTH, DISPLAY_RESIZE_HEIGHT) - - -class AutoSplit(QtWidgets.QMainWindow, design.Ui_MainWindow): - from hotkeys import send_command - from settings_file import saveSettings, saveSettingsAs, loadSettings, haveSettingsChanged, getSaveSettingsValues - from screen_region import selectRegion, selectWindow, alignRegion, validateBeforeComparison - from hotkeys import afterSettingHotkey, beforeSettingHotkey, setSplitHotkey, setResetHotkey, setSkipSplitHotkey, \ - setUndoSplitHotkey, setPauseHotkey - - myappid = f'Toufool.AutoSplit.v{VERSION}' - ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) - - # signals - updateCurrentSplitImage = QtCore.pyqtSignal(QtGui.QImage) - startAutoSplitterSignal = QtCore.pyqtSignal() - resetSignal = QtCore.pyqtSignal() - skipSplitSignal = QtCore.pyqtSignal() - undoSplitSignal = QtCore.pyqtSignal() - pauseSignal = QtCore.pyqtSignal() - afterSettingHotkeySignal = QtCore.pyqtSignal() - - def __init__(self, parent=None): - super(AutoSplit, self).__init__(parent) - self.setupUi(self) +import sys +from collections.abc import Callable +from time import time +from types import FunctionType +from typing import NoReturn, Optional - #These are only global settings values. They are not *pkl settings values. - self.getGlobalSettingsValues() - check_for_updates_on_open = self.setting_check_for_updates_on_open.value('check_for_updates_on_open', True, type=bool) - self.actionCheck_for_Updates_on_Open.setChecked(check_for_updates_on_open) +import certifi +import cv2 +from AutoControlledThread import AutoControlledThread +from psutil import process_iter +from PySide6 import QtCore, QtGui +from PySide6.QtTest import QTest +from PySide6.QtWidgets import QApplication, QFileDialog, QLabel, QMainWindow, QMessageBox +from typing_extensions import override - # Parse command line args - self.is_auto_controlled = ('--auto-controlled' in sys.argv) +import error_messages +import user_profile +from AutoSplitImage import START_KEYWORD, AutoSplitImage, ImageType +from capture_method import CaptureMethodBase, CaptureMethodEnum +from gen import about, design, settings, update_checker +from hotkeys import HOTKEYS, after_setting_hotkey, send_command +from menu_bar import ( + about_qt, + about_qt_for_python, + check_for_updates, + get_default_settings_from_ui, + open_about, + open_settings, + open_update_checker, + view_help, +) +from region_selection import align_region, select_region, select_window, validate_before_parsing +from split_parser import BELOW_FLAG, DUMMY_FLAG, PAUSE_FLAG, parse_and_validate_images +from user_profile import DEFAULT_PROFILE +from utils import AUTOSPLIT_VERSION, FROZEN, auto_split_directory, decimal, is_valid_image, open_file + +CHECK_FPS_ITERATIONS = 10 +DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 2 + +# Needed when compiled, along with the custom hook-requests PyInstaller hook +os.environ["REQUESTS_CA_BUNDLE"] = certifi.where() +myappid = f"Toufool.AutoSplit.v{AUTOSPLIT_VERSION}" +ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) +# qt.qpa.window: SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) failed: COM error 0x5: Access is denied. # noqa: E501 +# ctypes.windll.user32.SetProcessDpiAwarenessContext(2) + + +class AutoSplit(QMainWindow, design.Ui_MainWindow): + # Parse command line args + is_auto_controlled = "--auto-controlled" in sys.argv + + # Signals + start_auto_splitter_signal = QtCore.Signal() + reset_signal = QtCore.Signal() + skip_split_signal = QtCore.Signal() + undo_split_signal = QtCore.Signal() + pause_signal = QtCore.Signal() + after_setting_hotkey_signal = QtCore.Signal() + update_checker_widget_signal = QtCore.Signal(str, bool) + load_start_image_signal = QtCore.Signal(type[Optional[bool]], type[Optional[bool]]) + # Use this signal when trying to show an error from outside the main thread + show_error_signal = QtCore.Signal(FunctionType) + + # Timers + timer_live_image = QtCore.QTimer() + timer_start_image = QtCore.QTimer() + + # Widgets + AboutWidget: about.Ui_AboutAutoSplitWidget | None = None + UpdateCheckerWidget: update_checker.Ui_UpdateChecker | None = None + CheckForUpdatesThread: QtCore.QThread | None = None + SettingsWidget: settings.Ui_SettingsWidget | None = None + + # Initialize a few attributes + hwnd = 0 + """Window Handle used for Capture Region""" + last_saved_settings = DEFAULT_PROFILE + similarity = 0.0 + split_image_number = 0 + split_images_and_loop_number: list[tuple[AutoSplitImage, int]] = [] + split_groups: list[list[int]] = [] + capture_method = CaptureMethodBase() + is_running = False + + # Last loaded settings empty and last successful loaded settings file path to None until we try to load them + last_loaded_settings = DEFAULT_PROFILE + last_successfully_loaded_settings_file_path: str | None = None + """For when a file has never loaded, but you successfully "Save File As".""" + + # Automatic timer start + highest_similarity = 0.0 + reset_highest_similarity = 0.0 + + # Ensure all other attributes are defined + waiting_for_split_delay = False + split_below_threshold = False + run_start_time = 0.0 + start_image: AutoSplitImage | None = None + reset_image: AutoSplitImage | None = None + split_images: list[AutoSplitImage] = [] + split_image: AutoSplitImage | None = None + update_auto_control: AutoControlledThread | None = None + + def __init__(self): # noqa: PLR0915 + super().__init__() + + # Setup global error handling + def _show_error_signal_slot(error_message_box: Callable[..., object]): + return error_message_box() + self.show_error_signal.connect(_show_error_signal_slot) + sys.excepthook = error_messages.make_excepthook(self) - # close all processes when closing window - self.actionView_Help.triggered.connect(viewHelp) - self.actionAbout.triggered.connect(lambda: about(self)) - self.actionCheck_for_Updates.triggered.connect(lambda: checkForUpdates(self)) - self.actionSave_Settings.triggered.connect(self.saveSettings) - self.actionSave_Settings_As.triggered.connect(self.saveSettingsAs) - self.actionLoad_Settings.triggered.connect(self.loadSettings) + self.setupUi(self) + self.setWindowTitle(f"AutoSplit v{AUTOSPLIT_VERSION}") - # disable buttons upon open - self.undosplitButton.setEnabled(False) - self.skipsplitButton.setEnabled(False) - self.resetButton.setEnabled(False) + # Hotkeys need to be initialized to be passed as thread arguments in hotkeys.py + for hotkey in HOTKEYS: + setattr(self, f"{hotkey}_hotkey", None) + + # Get default values defined in SettingsDialog + self.settings_dict = get_default_settings_from_ui(self) + user_profile.load_check_for_updates_on_open(self) if self.is_auto_controlled: - self.setsplithotkeyButton.setEnabled(False) - self.setresethotkeyButton.setEnabled(False) - self.setskipsplithotkeyButton.setEnabled(False) - self.setundosplithotkeyButton.setEnabled(False) - self.setpausehotkeyButton.setEnabled(False) - self.startautosplitterButton.setEnabled(False) - self.splitLineEdit.setEnabled(False) - self.resetLineEdit.setEnabled(False) - self.skipsplitLineEdit.setEnabled(False) - self.undosplitLineEdit.setEnabled(False) - self.pausehotkeyLineEdit.setEnabled(False) - self.timerglobalhotkeysLabel.setText("Hotkeys Inactive - Use LiveSplit Hotkeys") + self.start_auto_splitter_button.setEnabled(False) # Send version and process ID to stdout - print(f"{VERSION}\n{os.getpid()}", flush=True) - - class Worker(QtCore.QObject): - def __init__(self, autosplit: AutoSplit): - self.autosplit = autosplit - super().__init__() - - def run(self): - while True: - try: - line = input() - except RuntimeError: - # stdin not supported or lost, stop looking for inputs - break - # TODO: "AutoSplit Integration" needs to call this and wait instead of outright killing the app. - # TODO: See if we can also get LiveSplit to wait on Exit in "AutoSplit Integration" - # For now this can only used in a Development environment - if line == 'kill': - self.autosplit.closeEvent() - break - elif line == 'start': - self.autosplit.startAutoSplitter() - elif line == 'split' or line == 'skip': - self.autosplit.startSkipSplit() - elif line == 'undo': - self.autosplit.startUndoSplit() - elif line == 'reset': - self.autosplit.startReset() - elif line.startswith('settings'): - # Allow for any split character between "settings" and the path - self.autosplit.load_settings_file_path = line[9:] - self.autosplit.loadSettings(load_settings_from_livesplit=True) - # TODO: Not yet implemented in AutoSplit Integration - # elif line == 'pause': - # self.startPause() + # THIS HAS TO BE THE FIRST TWO LINES SENT + print(f"{AUTOSPLIT_VERSION}\n{os.getpid()}", flush=True) # Use and Start the thread that checks for updates from LiveSplit - self.update_auto_control = QtCore.QThread() - worker = Worker(self) - worker.moveToThread(self.update_auto_control) - self.update_auto_control.started.connect(worker.run) + self.update_auto_control = AutoControlledThread(self) self.update_auto_control.start() # split image folder line edit text - self.splitimagefolderLineEdit.setText('No Folder Selected') + self.split_image_folder_input.setText("No Folder Selected") + + # Connecting menu actions + self.action_view_help.triggered.connect(view_help) + self.action_about.triggered.connect(lambda: open_about(self)) + self.action_about_qt.triggered.connect(about_qt) + self.action_about_qt_for_python.triggered.connect(about_qt_for_python) + self.action_check_for_updates.triggered.connect(lambda: check_for_updates(self)) + self.action_settings.triggered.connect(lambda: open_settings(self)) + self.action_save_profile.triggered.connect(lambda: user_profile.save_settings(self)) + self.action_save_profile_as.triggered.connect(lambda: user_profile.save_settings_as(self)) + self.action_load_profile.triggered.connect(lambda: user_profile.load_settings(self)) # Connecting button clicks to functions - self.browseButton.clicked.connect(self.browse) - self.selectregionButton.clicked.connect(self.selectRegion) - self.takescreenshotButton.clicked.connect(self.takeScreenshot) - self.startautosplitterButton.clicked.connect(self.autoSplitter) - self.checkfpsButton.clicked.connect(self.checkFPS) - self.resetButton.clicked.connect(self.reset) - self.skipsplitButton.clicked.connect(self.skipSplit) - self.undosplitButton.clicked.connect(self.undoSplit) - self.setsplithotkeyButton.clicked.connect(self.setSplitHotkey) - self.setresethotkeyButton.clicked.connect(self.setResetHotkey) - self.setskipsplithotkeyButton.clicked.connect(self.setSkipSplitHotkey) - self.setundosplithotkeyButton.clicked.connect(self.setUndoSplitHotkey) - self.setpausehotkeyButton.clicked.connect(self.setPauseHotkey) - self.alignregionButton.clicked.connect(self.alignRegion) - self.selectwindowButton.clicked.connect(self.selectWindow) - self.startImageReloadButton.clicked.connect(lambda: self.loadStartImage(True, True)) + self.browse_button.clicked.connect(self.__browse) + self.select_region_button.clicked.connect(lambda: select_region(self)) + self.take_screenshot_button.clicked.connect(self.__take_screenshot) + self.start_auto_splitter_button.clicked.connect(self.__auto_splitter) + self.check_fps_button.clicked.connect(self.__check_fps) + self.reset_button.clicked.connect(self.reset) + self.skip_split_button.clicked.connect(self.skip_split) + self.undo_split_button.clicked.connect(self.undo_split) + self.next_image_button.clicked.connect(lambda: self.skip_split(True)) + self.previous_image_button.clicked.connect(lambda: self.undo_split(True)) + self.align_region_button.clicked.connect(lambda: align_region(self)) + self.select_window_button.clicked.connect(lambda: select_window(self)) + self.reload_start_image_button.clicked.connect(lambda: self.__load_start_image(True, True)) + self.action_check_for_updates_on_open.changed.connect( + lambda: user_profile.set_check_for_updates_on_open(self, self.action_check_for_updates_on_open.isChecked()), + ) # update x, y, width, and height when changing the value of these spinbox's are changed - self.xSpinBox.valueChanged.connect(self.updateX) - self.ySpinBox.valueChanged.connect(self.updateY) - self.widthSpinBox.valueChanged.connect(self.updateWidth) - self.heightSpinBox.valueChanged.connect(self.updateHeight) + self.x_spinbox.valueChanged.connect(self.__update_x) + self.y_spinbox.valueChanged.connect(self.__update_y) + self.width_spinbox.valueChanged.connect(self.__update_width) + self.height_spinbox.valueChanged.connect(self.__update_height) # connect signals to functions - self.updateCurrentSplitImage.connect(self.updateSplitImageGUI) - self.afterSettingHotkeySignal.connect(self.afterSettingHotkey) - self.startAutoSplitterSignal.connect(self.autoSplitter) - self.resetSignal.connect(self.reset) - self.skipSplitSignal.connect(self.skipSplit) - self.undoSplitSignal.connect(self.undoSplit) + self.after_setting_hotkey_signal.connect(lambda: after_setting_hotkey(self)) + self.start_auto_splitter_signal.connect(self.__auto_splitter) - # live image checkbox - self.liveimageCheckBox.clicked.connect(self.checkLiveImage) - self.timerLiveImage = QtCore.QTimer() - self.timerLiveImage.timeout.connect(self.liveImageFunction) + def _update_checker_widget_signal_slot(latest_version: str, check_on_open: bool): + return open_update_checker(self, latest_version, check_on_open) + self.update_checker_widget_signal.connect(_update_checker_widget_signal_slot) - # Initialize a few attributes - self.last_saved_settings = None - self.live_image_function_on_open = True - self.split_image_loop_amount = [] - self.split_image_number = 0 - self.split_image_directory = "" - - # hotkeys need to be initialized to be passed as thread arguments in hotkeys.py - # and for type safety in both hotkeys.py and settings_file.py - self.split_hotkey: Optional[Callable[[], None]] = None - self.reset_hotkey: Optional[Callable[[], None]] = None - self.skip_split_hotkey: Optional[Callable[[], None]] = None - self.undo_split_hotkey: Optional[Callable[[], None]] = None - self.pause_hotkey: Optional[Callable[[], None]] = None - self.split_key = "" - self.reset_key = "" - self.skip_split_key = "" - self.undo_split_key = "" - self.undo_split_key = "" - - # Default Settings for the region capture - self.hwnd = 0 - self.hwnd_title = '' - self.rect = ctypes.wintypes.RECT() - - # Last loaded settings and last successful loaded settings file path to None until we try to load them - self.last_loaded_settings = None - self.last_successfully_loaded_settings_file_path = None + self.load_start_image_signal.connect(self.__load_start_image) + self.reset_signal.connect(self.reset) + self.skip_split_signal.connect(self.skip_split) + self.undo_split_signal.connect(self.undo_split) + self.pause_signal.connect(self.pause) - if not self.is_auto_controlled: - self.loadSettings(load_settings_on_open=True) + # live image checkbox + self.timer_live_image.timeout.connect(self.__live_image_function) + self.timer_live_image.start(int(1000 / 60)) # Automatic timer start - self.timerStartImage = QtCore.QTimer() - self.timerStartImage.timeout.connect(self.startImageFunction) - self.timerStartImage_is_running = False - self.start_image = None - self.highest_similarity = 0.0 - self.check_start_image_timestamp = 0.0 + self.timer_start_image.timeout.connect(self.__start_image_function) - # Try to load start image - self.loadStartImage() + self.show() - # FUNCTIONS + try: + import pyi_splash # pyright: ignore[reportMissingModuleSource] + pyi_splash.close() + except ModuleNotFoundError: + pass - def getGlobalSettingsValues(self): - self.setting_check_for_updates_on_open = QtCore.QSettings('AutoSplit', 'Check For Updates On Open') + # Needs to be after Ui_MainWindow.show() to be shown on top + if not self.is_auto_controlled: + # Must also be done later to help load the saved capture window + user_profile.load_settings_on_open(self) + if self.action_check_for_updates_on_open.isChecked(): + check_for_updates(self, check_on_open=True) - # TODO add checkbox for going back to image 1 when resetting. - def browse(self): + # FUNCTIONS + + def __browse(self): # User selects the file with the split images in it. - new_split_image_directory = QtWidgets.QFileDialog.getExistingDirectory( - self, - 'Select Split Image Directory', - os.path.join(self.split_image_directory or auto_split_directory, "..")) + new_split_image_directory = QFileDialog.getExistingDirectory( + self, + "Select Split Image Directory", + os.path.join(self.settings_dict["split_image_directory"] or auto_split_directory, ".."), + ) # If the user doesn't select a folder, it defaults to "". if new_split_image_directory: # set the split image folder line to the directory text - self.split_image_directory = new_split_image_directory - self.splitimagefolderLineEdit.setText(f"{new_split_image_directory}/") - - def checkLiveImage(self): - if self.liveimageCheckBox.isChecked(): - self.timerLiveImage.start(int(1000 / 60)) - else: - self.timerLiveImage.stop() - self.liveImageFunction() - - def liveImageFunction(self): - try: - if win32gui.GetWindowText(self.hwnd) == '': - self.timerLiveImage.stop() - if self.live_image_function_on_open: - self.live_image_function_on_open = False - else: - self.liveImage.clear() - error_messages.regionError() - return - - capture = capture_windows.capture_region(self.hwnd, self.rect) - capture = cv2.resize(capture, DISPLAY_RESIZE) - capture = cv2.cvtColor(capture, cv2.COLOR_BGRA2RGB) - - # Convert to set it on the label - qImage = QtGui.QImage(capture, - capture.shape[1], - capture.shape[0], - capture.shape[1] * 3, - QtGui.QImage.Format.Format_RGB888) - pix = QtGui.QPixmap(qImage) - self.liveImage.setPixmap(pix) - - except AttributeError: - pass - - def loadStartImage(self, started_by_button=False, wait_for_delay=True): - self.timerStartImage.stop() - self.currentsplitimagefileLabel.setText(' ') - self.startImageLabel.setText("Start image: not found") - QtWidgets.QApplication.processEvents() - - if not self.validateBeforeComparison(started_by_button): + self.settings_dict["split_image_directory"] = new_split_image_directory + self.split_image_folder_input.setText(f"{new_split_image_directory}/") + self.load_start_image_signal.emit() + + def __live_image_function(self): + capture_region_window_label = self.settings_dict["capture_device_name"] \ + if self.settings_dict["capture_method"] == CaptureMethodEnum.VIDEO_CAPTURE_DEVICE \ + else self.settings_dict["captured_window_title"] + self.capture_region_window_label.setText(capture_region_window_label) + if not (self.settings_dict["live_capture_region"] and capture_region_window_label): + self.live_image.clear() + return + # Set live image in UI + capture, _ = self.capture_method.get_frame(self) + set_preview_image(self.live_image, capture, False) + + def __load_start_image(self, started_by_button: bool = False, wait_for_delay: bool = True): + """Not thread safe (if triggered by LiveSplit for example). Use `load_start_image_signal.emit` instead.""" + self.timer_start_image.stop() + self.current_image_file_label.setText("-") + self.start_image_status_value_label.setText("not found") + set_preview_image(self.current_split_image, None, True) + + if not (validate_before_parsing(self, started_by_button) and parse_and_validate_images(self)): + QApplication.processEvents() return - self.start_image_name = None - for image in os.listdir(self.split_image_directory): - if 'start_auto_splitter' in image.lower(): - if self.start_image_name is None: - self.start_image_name = image - else: - if started_by_button: - error_messages.multipleKeywordImagesError('start_auto_splitter') - return - - if self.start_image_name is None: + if self.start_image: + if not self.is_auto_controlled \ + and ( + not self.settings_dict["split_hotkey"] + or not self.settings_dict["reset_hotkey"] + or not self.settings_dict["pause_hotkey"] + ): + error_messages.load_start_image() + QApplication.processEvents() + return + else: if started_by_button: - error_messages.noKeywordImageError('start_auto_splitter') + error_messages.no_keyword_image(START_KEYWORD) + QApplication.processEvents() return - self.split_image_filenames = os.listdir(self.split_image_directory) self.split_image_number = 0 - self.start_image_mask = None - path = os.path.join(self.split_image_directory, self.start_image_name) - self.start_image = cv2.imread(path, cv2.IMREAD_UNCHANGED) - if self.start_image is None: - error_messages.imageTypeError(path) - return - # if image has transparency, create a mask - self.imageHasTransparency = compare.checkIfImageHasTransparency(self.start_image) - if self.imageHasTransparency: - self.start_image = cv2.resize(self.start_image, COMPARISON_RESIZE, interpolation=cv2.INTER_NEAREST) - # Create mask based on resized, nearest neighbor interpolated split image - lower = np.array([0, 0, 0, 1], dtype="uint8") - upper = np.array([255, 255, 255, 255], dtype="uint8") - self.start_image_mask = cv2.inRange(self.start_image, lower, upper) - - # set split image as BGR - self.start_image = cv2.cvtColor(self.start_image, cv2.COLOR_BGRA2BGR) - - # otherwise, open image normally. Capture with nearest neighbor interpolation only if the split image has transparency to support older setups. - else: - self.start_image = cv2.imread(path, cv2.IMREAD_COLOR) - self.start_image = cv2.resize(self.start_image, COMPARISON_RESIZE) - - start_image_pause = split_parser.pause_from_filename(self.start_image_name) - if not wait_for_delay and start_image_pause is not None and start_image_pause > 0: - self.check_start_image_timestamp = time.time() + start_image_pause - self.startImageLabel.setText("Start image: paused") - self.highestsimilarityLabel.setText(' ') - self.currentsimilaritythresholdnumberLabel.setText(' ') + if not wait_for_delay and self.start_image.get_pause_time(self) > 0: + self.start_image_status_value_label.setText("paused") + self.table_current_image_highest_label.setText("-") + self.table_current_image_threshold_label.setText("-") else: - self.check_start_image_timestamp = 0.0 - self.startImageLabel.setText("Start image: ready") - self.updateSplitImage(self.start_image_name, from_start_image=True) + self.start_image_status_value_label.setText("ready") + self.__update_split_image(self.start_image) self.highest_similarity = 0.0 - self.start_image_split_below_threshold = False - self.timerStartImage.start(int(1000 / self.fpslimitSpinBox.value())) + self.reset_highest_similarity = 0.0 + self.split_below_threshold = False + self.timer_start_image.start(int(1000 / self.settings_dict["fps_limit"])) - QtWidgets.QApplication.processEvents() + QApplication.processEvents() - def startImageFunction(self): - if time.time() < self.check_start_image_timestamp \ - or (not self.splitLineEdit.text() and not self.is_auto_controlled): - pause_time_left = "{:.1f}".format(self.check_start_image_timestamp - time.time()) - self.currentSplitImage.setText(f'None\n (Paused before loading Start Image).\n {pause_time_left} sec remaining') + def __start_image_function(self): + if not self.start_image: return - if self.check_start_image_timestamp > 0: - self.check_start_image_timestamp = 0.0 - self.startImageLabel.setText("Start image: ready") - self.updateSplitImage(self.start_image_name, from_start_image=True) - - capture = self.getCaptureForComparison() - start_image_similarity = self.compareImage(self.start_image, self.start_image_mask, capture) - start_image_threshold = split_parser.threshold_from_filename(self.start_image_name) \ - or self.similaritythresholdDoubleSpinBox.value() - self.currentsimilaritythresholdnumberLabel.setText("{:.2f}".format(start_image_threshold)) - start_image_flags = split_parser.flags_from_filename(self.start_image_name) - start_image_delay = split_parser.delay_from_filename(self.start_image_name) + self.start_image_status_value_label.setText("ready") + self.__update_split_image(self.start_image) - # Show live similarity if the checkbox is checked - self.livesimilarityLabel.setText(str(start_image_similarity)[:4] - if self.showlivesimilarityCheckBox.isChecked() - else ' ') + capture, _ = self.__get_capture_for_comparison() + start_image_threshold = self.start_image.get_similarity_threshold(self) + start_image_similarity = self.start_image.compare_with_capture(self, capture) # If the similarity becomes higher than highest similarity, set it as such. if start_image_similarity > self.highest_similarity: self.highest_similarity = start_image_similarity - # Show live highest similarity if the checkbox is checked - self.highestsimilarityLabel.setText(str(self.highest_similarity)[:4] - if self.showlivesimilarityCheckBox.isChecked() - else ' ') + self.table_current_image_live_label.setText(decimal(start_image_similarity)) + self.table_current_image_highest_label.setText(decimal(self.highest_similarity)) + self.table_current_image_threshold_label.setText(decimal(start_image_threshold)) # If the {b} flag is set, let similarity go above threshold first, then split on similarity below threshold # Otherwise just split when similarity goes above threshold - if start_image_flags & BELOW_FLAG == BELOW_FLAG \ - and not self.start_image_split_below_threshold \ - and start_image_similarity >= start_image_threshold: - self.start_image_split_below_threshold = True + # TODO: Abstract with similar check in split image + below_flag = self.start_image.check_flag(BELOW_FLAG) + + # Negative means belove threshold, positive means above + similarity_diff = start_image_similarity - start_image_threshold + if below_flag and not self.split_below_threshold and similarity_diff >= 0: + self.split_below_threshold = True return - if (start_image_flags & BELOW_FLAG == BELOW_FLAG - and self.start_image_split_below_threshold - and start_image_similarity < start_image_threshold) \ - or (start_image_similarity >= start_image_threshold and start_image_flags & BELOW_FLAG == 0): - def split(): - self.hasSentStart = False - self.send_command("start") - time.sleep(1 / self.fpslimitSpinBox.value()) - self.startAutoSplitter() - - self.timerStartImage.stop() - self.start_image_split_below_threshold = False + if ( + (below_flag and self.split_below_threshold and similarity_diff < 0 and is_valid_image(capture)) + or (not below_flag and similarity_diff >= 0) + ): - # delay start image if needed - if start_image_delay > 0: - self.startImageLabel.setText("Start image: delaying start...") - delay_start_time = time.time() - while time.time() - delay_start_time < (start_image_delay / 1000): - delay_time_left = round((start_image_delay / 1000) - (time.time() - delay_start_time), 1) - self.currentSplitImage.setText(f'Delayed Before Starting:\n {delay_time_left} sec remaining') - QtTest.QTest.qWait(1) + self.timer_start_image.stop() + self.split_below_threshold = False - self.startImageLabel.setText("Start image: started") - split() + # delay start image if needed + if self.start_image.get_delay_time(self) > 0: + self.start_image_status_value_label.setText("delaying start...") + delay_start_time = time() + start_delay = self.start_image.get_delay_time(self) / 1000 + time_delta = 0 + while time_delta < start_delay: + delay_time_left = start_delay - time_delta + self.current_split_image.setText( + f"Delayed Before Starting:\n {seconds_remaining_text(delay_time_left)}", + ) + # Wait 0.1s. Doesn't need to be shorter as we only show 1 decimal + QTest.qWait(100) + time_delta = time() - delay_start_time + + self.start_image_status_value_label.setText("started") + send_command(self, "start") + self.start_auto_splitter() # update x, y, width, height when spinbox values are changed - def updateX(self): - try: - self.rect.left = self.xSpinBox.value() - self.rect.right = self.rect.left + self.widthSpinBox.value() - self.checkLiveImage() - except AttributeError: - pass - - def updateY(self): - try: - self.rect.top = self.ySpinBox.value() - self.rect.bottom = self.rect.top + self.heightSpinBox.value() - self.checkLiveImage() - except AttributeError: - pass + def __update_x(self): + self.settings_dict["capture_region"]["x"] = self.x_spinbox.value() - def updateWidth(self): - self.rect.right = self.rect.left + self.widthSpinBox.value() - self.checkLiveImage() + def __update_y(self): + self.settings_dict["capture_region"]["y"] = self.y_spinbox.value() - def updateHeight(self): - self.rect.bottom = self.rect.top + self.heightSpinBox.value() - self.checkLiveImage() + def __update_width(self): + self.settings_dict["capture_region"]["width"] = self.width_spinbox.value() - # update current split image. needed this to avoid updating it through the hotkey thread. - def updateSplitImageGUI(self, qImage: QtGui.QImage): - pix = QtGui.QPixmap(qImage) - self.currentSplitImage.setPixmap(pix) + def __update_height(self): + self.settings_dict["capture_region"]["height"] = self.height_spinbox.value() - def takeScreenshot(self): - if not self.validateBeforeComparison(check_empty_directory=False): + def __take_screenshot(self): + if not validate_before_parsing(self, check_empty_directory=False): return - take_screenshot_filename = '001_SplitImage' - # check if file exists and rename it if it does - # Below starts the FileNameNumber at #001 up to #999. After that it will go to 1000, + # Check if file exists and rename it if it does. + # Below starts the file_name_number at #001 up to #999. After that it will go to 1000, # which is a problem, but I doubt anyone will get to 1000 split images... - i = 1 - while os.path.exists(os.path.join(self.split_image_directory, f"{take_screenshot_filename}.png")): - FileNameNumber = (f"{i:03}") - take_screenshot_filename = f"{FileNameNumber}_SplitImage" - i = i + 1 - - # grab screenshot of capture region - capture = capture_windows.capture_region(self.hwnd, self.rect) - capture = cv2.cvtColor(capture, cv2.COLOR_BGRA2BGR) - - # save and open image - cv2.imwrite(os.path.join(self.split_image_directory, f"{take_screenshot_filename}.png"), capture) - os.startfile(os.path.join(self.split_image_directory, f"{take_screenshot_filename}.png")) - - # check max FPS button connects here. - # TODO: Average on all images and check for transparency (cv2.COLOR_BGRA2RGB and cv2.IMREAD_UNCHANGED) - def checkFPS(self): - if not self.validateBeforeComparison(): - return - - split_image_filenames = os.listdir(self.split_image_directory) - split_images = [ - cv2.imread(os.path.join(self.split_image_directory, image), cv2.IMREAD_COLOR) - for image - in split_image_filenames] - for image in split_images: - if image is None: - error_messages.imageTypeError(image) - return - - # grab first image in the split image folder - split_image = split_images[0] - split_image = cv2.cvtColor(split_image, cv2.COLOR_BGR2RGB) - split_image = cv2.resize(split_image, COMPARISON_RESIZE) + screenshot_index = 1 + while True: + screenshot_path = os.path.join( + self.settings_dict["split_image_directory"], + f"{screenshot_index:03}_SplitImage.png", + ) + if not os.path.exists(screenshot_path): + break + screenshot_index += 1 - # run 10 iterations of screenshotting capture region + comparison. - count = 0 - t0 = time.time() - while count < 10: + # Grab screenshot of capture region + capture, _ = self.capture_method.get_frame(self) + if not is_valid_image(capture): + error_messages.region() + return - capture = capture_windows.capture_region(self.hwnd, self.rect) - capture = cv2.resize(capture, COMPARISON_RESIZE) - capture = cv2.cvtColor(capture, cv2.COLOR_BGRA2RGB) + # Save and open image + cv2.imwrite(screenshot_path, capture) + open_file(screenshot_path) - if self.comparisonmethodComboBox.currentIndex() == 0: - _ = compare.compare_l2_norm(split_image, capture) - elif self.comparisonmethodComboBox.currentIndex() == 1: - _ = compare.compare_histograms(split_image, capture) - elif self.comparisonmethodComboBox.currentIndex() == 2: - _ = compare.compare_phash(split_image, capture) + def __check_fps(self): + self.fps_value_label.setText("...") + QApplication.processEvents() + if not (validate_before_parsing(self) and parse_and_validate_images(self)): + self.fps_value_label.clear() + return - count = count + 1 + images = self.split_images + if self.start_image: + images.append(self.start_image) + if self.reset_image: + images.append(self.reset_image) + + # run X iterations of screenshotting capture region + comparison + displaying. + t0 = time() + for image in images: + count = 0 + while count < CHECK_FPS_ITERATIONS: + capture, is_old_image = self.__get_capture_for_comparison() + _ = image.compare_with_capture(self, capture) + # TODO: If is_old_image=true is always returned, this becomes an infinite loop + if not is_old_image: + count += 1 # calculate FPS - t1 = time.time() - fps = str(int(10 / (t1 - t0))) - self.fpsvalueLabel.setText(fps) + t1 = time() + fps = int((CHECK_FPS_ITERATIONS * len(images)) / (t1 - t0)) + self.fps_value_label.setText(str(fps)) - def is_current_split_out_of_range(self): - return self.split_image_number < 0 or self.split_image_number > len(self.split_image_filenames_including_loops) - 1 + def __is_current_split_out_of_range(self): + return self.split_image_number < 0 \ + or self.split_image_number > len(self.split_images_and_loop_number) - 1 - # undo split button and hotkey connect to here - def undoSplit(self): + def undo_split(self, navigate_image_only: bool = False): + """Undo Split" and "Prev. Img." buttons connect to here.""" # Can't undo until timer is started # or Undoing past the first image - if self.startautosplitterButton.text() == 'Start Auto Splitter' or ("Delayed Split") in self.currentSplitImage.text(): - return - - if (not self.undosplitButton.isEnabled() and not self.is_auto_controlled) \ - or self.is_current_split_out_of_range(): + if not self.is_running \ + or "Delayed Split" in self.current_split_image.text() \ + or (not self.undo_split_button.isEnabled() and not self.is_auto_controlled) \ + or self.__is_current_split_out_of_range(): return - elif self.groupDummySplitsCheckBox.isChecked(): + if not navigate_image_only: for i, group in enumerate(self.split_groups): if i > 0 and self.split_image_number in group: - self.split_image_number = self.split_groups[i - 1][0] + self.split_image_number = self.split_groups[i - 1][-1] break - else: - self.split_image_number = self.split_image_number - 1 - - self.updateSplitImage() + self.split_image_number -= 1 - return + self.__update_split_image() + if not navigate_image_only: + send_command(self, "undo") - # skip split button and hotkey connect to here - def skipSplit(self): + def skip_split(self, navigate_image_only: bool = False): + """Skip Split" and "Next Img." buttons connect to here.""" # Can't skip or split until timer is started # or Splitting/skipping when there are no images left - if self.startautosplitterButton.text() == 'Start Auto Splitter' or ("Delayed Split") in self.currentSplitImage.text(): + if not self.is_running \ + or "Delayed Split" in self.current_split_image.text() \ + or not (self.skip_split_button.isEnabled() or self.is_auto_controlled or navigate_image_only) \ + or self.__is_current_split_out_of_range(): return - if (not self.skipsplitButton.isEnabled() and not self.is_auto_controlled) or self.is_current_split_out_of_range(): - return - - if self.groupDummySplitsCheckBox.isChecked(): + if not navigate_image_only: for group in self.split_groups: if self.split_image_number in group: self.split_image_number = group[-1] + 1 break else: - self.split_image_number = self.split_image_number + 1 - - self.updateSplitImage() + self.split_image_number += 1 - return + self.__update_split_image() + if not navigate_image_only: + send_command(self, "skip") - # def pause(self): + def pause(self): # TODO add what to do when you hit pause hotkey, if this even needs to be done + pass def reset(self): - # When the reset button or hotkey is pressed, it will change this text, - # which will trigger in the autoSplitter function, if running, to abort and change GUI. - self.startautosplitterButton.setText('Start Auto Splitter') - return + """ + When the reset button or hotkey is pressed, it will set `is_running` to False, + which will trigger in the __auto_splitter function, if running, to abort and change GUI. + """ + self.is_running = False # Functions for the hotkeys to return to the main thread from signals and start their corresponding functions - def startAutoSplitter(self): - self.hasSentStart = True - + def start_auto_splitter(self): # If the auto splitter is already running or the button is disabled, don't emit the signal to start it. - if self.startautosplitterButton.text() == 'Running...' or \ - (not self.startautosplitterButton.isEnabled() and not self.is_auto_controlled): + if self.is_running \ + or (not self.start_auto_splitter_button.isEnabled() and not self.is_auto_controlled): return - if self.startImageLabel.text() == "Start image: ready" or self.startImageLabel.text() == "Start image: paused": - self.startImageLabel.setText("Start image: not ready") - - self.startAutoSplitterSignal.emit() - - def startReset(self): - self.resetSignal.emit() - - def startSkipSplit(self): - self.skipSplitSignal.emit() - - def startUndoSplit(self): - self.undoSplitSignal.emit() + start_label: str = self.start_image_status_value_label.text() + if start_label.endswith(("ready", "paused")): + self.start_image_status_value_label.setText("not ready") - def startPause(self): - self.pauseSignal.emit() + self.start_auto_splitter_signal.emit() - def checkForReset(self): - if self.startautosplitterButton.text() == 'Start Auto Splitter': - if self.autostartonresetCheckBox.isChecked(): - self.startAutoSplitterSignal.emit() + def __check_for_reset_state_update_ui(self): + """Check if AutoSplit is started, if not either restart (loop splits) or update the GUI.""" + if not self.is_running: + if self.settings_dict["loop_splits"]: + self.start_auto_splitter_signal.emit() else: - self.guiChangesOnReset() + self.gui_changes_on_reset(True) return True return False - def autoSplitter(self): - if not self.validateBeforeComparison(): - return - - # get split image filenames - self.split_image_filenames = os.listdir(self.split_image_directory) - - # Make sure that each of the images follows the guidelines for correct format - # according to all of the settings selected by the user. - for image in self.split_image_filenames: - # Test for image without transparency - if (cv2.imread(os.path.join(self.split_image_directory, image), cv2.IMREAD_COLOR) is None - # Test for image with transparency - and cv2.imread(os.path.join(self.split_image_directory, image), cv2.IMREAD_UNCHANGED) is None): - # Opencv couldn't open this file as an image, this isn't a correct - # file format that is supported - self.guiChangesOnReset() - error_messages.imageTypeError(image) - return - - # error out if there is a {p} flag but no pause hotkey set and is not auto controlled. - if (self.pausehotkeyLineEdit.text() == '' - and split_parser.flags_from_filename(image) & PAUSE_FLAG == PAUSE_FLAG - and not self.is_auto_controlled): - self.guiChangesOnReset() - error_messages.pauseHotkeyError() - return - - if self.splitLineEdit.text() == '' and not self.is_auto_controlled: - self.guiChangesOnReset() - error_messages.splitHotkeyError() + def __auto_splitter(self): # noqa: PLR0912,PLR0915 + if not self.settings_dict["split_hotkey"] and not self.is_auto_controlled: + self.gui_changes_on_reset(True) + error_messages.split_hotkey() return - # find reset image then remove it from the list - self.findResetImage() - - # find start_auto_splitter_image and then remove it from the list - self.removeStartAutoSplitterImage() - - # Check that there's only one reset image - for image in self.split_image_filenames: - - if split_parser.is_reset_image(image): - self.guiChangesOnReset() - error_messages.multipleKeywordImagesError('reset') - return - - # Check that there's only one auto_start_autosplitter image - for image in self.split_image_filenames: + # Set start time before parsing the images as it's a heavy operation that will cause delays + self.run_start_time = time() - if split_parser.is_start_auto_splitter_image(image): - self.guiChangesOnReset() - error_messages.multipleKeywordImagesError('start_auto_splitter') - return - - # If there is no reset hotkey set but a reset image is present, and is not auto controlled, throw an error. - if self.resetLineEdit.text() == '' and self.reset_image is not None and not self.is_auto_controlled: - self.guiChangesOnReset() - error_messages.resetHotkeyError() + if not (validate_before_parsing(self) and parse_and_validate_images(self)): + self.gui_changes_on_reset(True) return - # construct loop amounts for each split image - self.split_image_loop_amount = [] - for i, image in enumerate(self.split_image_filenames): - self.split_image_loop_amount.append(split_parser.loop_from_filename(image)) - - # construct a list of filenames, each filename copied with # of loops it has. - self.split_image_filenames_including_loops: List[str] = [] - for i, filename in enumerate(self.split_image_filenames): - current_loop = 1 - while self.split_image_loop_amount[i] >= current_loop: - self.split_image_filenames_including_loops.append(filename) - current_loop = current_loop + 1 - - # construct a list of corresponding loop number to the filenames - self.loop_numbers: List[int] = [] - loop_count = 1 - for i, filename in enumerate(self.split_image_filenames_including_loops): - if i == 0: - self.loop_numbers.append(1) - else: - if self.split_image_filenames_including_loops[i] != self.split_image_filenames_including_loops[i-1]: - loop_count = 1 - self.loop_numbers.append(loop_count) - else: - loop_count = loop_count + 1 - self.loop_numbers.append(loop_count) - - # Merge them - self.split_image_filenames_and_loop_number = [ - (filename, self.loop_numbers[i], self.split_image_filenames_including_loops.count(filename)) - for i, filename in enumerate(self.split_image_filenames_including_loops) + # Construct a list of images + loop count tuples. + self.split_images_and_loop_number = [ + item for flattenlist + in [ + [(split_image, i + 1) for i in range(split_image.loops)] + for split_image + in self.split_images + ] + for item in flattenlist ] - # construct groups of splits if needed + # Construct groups of splits self.split_groups = [] - if self.groupDummySplitsCheckBox.isChecked(): - current_group = [] - self.split_groups.append(current_group) - - for i, image in enumerate(self.split_image_filenames_including_loops): - current_group.append(i) + current_group: list[int] = [] + self.split_groups.append(current_group) + for i, image in enumerate(self.split_images_and_loop_number): + current_group.append(i) + if not image[0].check_flag(DUMMY_FLAG) and i < len(self.split_images_and_loop_number) - 1: + current_group = [] + self.split_groups.append(current_group) - flags = split_parser.flags_from_filename(image) - if flags & DUMMY_FLAG != DUMMY_FLAG and i < len(self.split_image_filenames_including_loops) - 1: - current_group = [] - self.split_groups.append(current_group) + self.is_running = True + self.gui_changes_on_start() - - # construct dummy splits array - self.dummy_splits_array = [] - for i, image in enumerate(self.split_image_filenames_including_loops): - if split_parser.flags_from_filename(image) & DUMMY_FLAG == DUMMY_FLAG: - self.dummy_splits_array.append(True) - else: - self.dummy_splits_array.append(False) - - self.guiChangesOnStart() + # Start pause time + if self.start_image: + self.__pause_loop(self.start_image.get_pause_time(self), "None (Paused).") # Initialize a few attributes self.split_image_number = 0 - self.number_of_split_images = len(self.split_image_filenames_including_loops) self.waiting_for_split_delay = False self.split_below_threshold = False + split_time = 0 + number_of_split_images = len(self.split_images_and_loop_number) + dummy_splits_array = [image_loop[0].check_flag(DUMMY_FLAG) for image_loop in self.split_images_and_loop_number] - self.run_start_time = time.time() - - # First while loop: stays in this loop until all of the split images have been split - while self.split_image_number < self.number_of_split_images: + # First loop: stays in this loop until all of the split images have been split + while self.split_image_number < number_of_split_images: # Check if we are not waiting for the split delay to send the key press - if self.waiting_for_split_delay == True: - time_millis = int(round(time.time() * 1000)) - if time_millis < self.split_time: - QtWidgets.QApplication.processEvents() + if self.waiting_for_split_delay: + time_millis = int(round(time() * 1000)) + if time_millis < split_time: + QApplication.processEvents() continue - self.updateSplitImage() - - # second while loop: stays in this loop until similarity threshold is met - # skip loop if we just finished waiting for the split delay and need to press the split key! - start = time.time() - while True: - # reset if the set screen region window was closed - if win32gui.GetWindowText(self.hwnd) == '': - self.reset() - - if self.checkForReset(): - return - - # calculate similarity for reset image - capture = self.getCaptureForComparison() - - if self.shouldCheckResetImage(): - reset_similarity = self.compareImage(self.reset_image, self.reset_mask, capture) - if reset_similarity >= self.reset_image_threshold: - self.send_command("reset") - self.reset() - - if self.checkForReset(): - return - - # TODO: Check is this actually still needed? - # get capture again if current and reset image have different mask flags - if self.imageHasTransparency != (self.reset_mask is not None): - capture = self.getCaptureForComparison() + self.__update_split_image() - # calculate similarity for split image - self.similarity = self.compareImage(self.split_image, self.image_mask, capture) - - # show live similarity if the checkbox is checked - if self.showlivesimilarityCheckBox.isChecked(): - self.livesimilarityLabel.setText(str(self.similarity)[:4]) - else: - self.livesimilarityLabel.setText(' ') - - # if the similarity becomes higher than highest similarity, set it as such. - if self.similarity > self.highest_similarity: - self.highest_similarity = self.similarity - - # show live highest similarity if the checkbox is checked - if self.showhighestsimilarityCheckBox.isChecked(): - self.highestsimilarityLabel.setText(str(self.highest_similarity)[:4]) - else: - self.highestsimilarityLabel.setText(' ') - - if not self.is_auto_controlled: - # if its the last split image or can't skip due to grouped dummy splits, disable the skip split button - if (self.split_image_number == self.number_of_split_images - 1) or (self.groupDummySplitsCheckBox.isChecked() == True and self.dummy_splits_array[self.split_image_number:].count(False) <= 1): - self.skipsplitButton.setEnabled(False) - else: - self.skipsplitButton.setEnabled(True) - - # if its the first split image, disable the undo split button - if self.split_image_number == 0: - self.undosplitButton.setEnabled(False) - else: - self.undosplitButton.setEnabled(True) - - # if the b flag is set, let similarity go above threshold first, - # then split on similarity below threshold. - # if no b flag, just split when similarity goes above threshold. - if not self.waiting_for_split_delay: - if self.flags & BELOW_FLAG == BELOW_FLAG and not self.split_below_threshold: - if self.similarity >= self.similarity_threshold: - self.split_below_threshold = True - continue - elif self.flags & BELOW_FLAG == BELOW_FLAG and self.split_below_threshold: - if self.similarity < self.similarity_threshold: - self.split_below_threshold = False - break - elif self.similarity >= self.similarity_threshold: - break - - # limit the number of time the comparison runs to reduce cpu usage - fps_limit = self.fpslimitSpinBox.value() - time.sleep((1 / fps_limit) - (time.time() - start) % (1 / fps_limit)) - QtWidgets.QApplication.processEvents() + # Type checking + if not self.split_image: + return - # comes here when threshold gets met + # Second loop: stays in this loop until similarity threshold is met + if self.__similarity_threshold_loop(number_of_split_images, dummy_splits_array): + return - # We need to make sure that this isn't a dummy split before sending - # the key press. - if not (self.flags & DUMMY_FLAG == DUMMY_FLAG): + # We need to make sure that this isn't a dummy split before sending the key press. + if not self.split_image.check_flag(DUMMY_FLAG): # If it's a delayed split, check if the delay has passed # Otherwise calculate the split time for the key press - if self.split_delay > 0 and self.waiting_for_split_delay == False: - self.split_time = int(round(time.time() * 1000)) + self.split_delay + split_delay = self.split_image.get_delay_time(self) / 1000 + if split_delay > 0 and not self.waiting_for_split_delay: + split_time = round(time() + split_delay * 1000) self.waiting_for_split_delay = True - self.undosplitButton.setEnabled(False) - self.skipsplitButton.setEnabled(False) - self.currentsplitimagefileLabel.setText(' ') + buttons_to_disable = [ + self.next_image_button, + self.previous_image_button, + self.undo_split_button, + self.skip_split_button, + ] + for button in buttons_to_disable: + button.setEnabled(False) + self.current_image_file_label.clear() # check for reset while delayed and display a counter of the remaining split delay time - delay_start_time = time.time() - while time.time() - delay_start_time < (self.split_delay / 1000): - delay_time_left = round((self.split_delay / 1000) - (time.time() - delay_start_time), 1) - self.currentSplitImage.setText(f'Delayed Split: {delay_time_left} sec remaining') - # check for reset - if win32gui.GetWindowText(self.hwnd) == '': - self.reset() - if self.checkForReset(): - return - - # calculate similarity for reset image - if self.shouldCheckResetImage(): - capture = self.getCaptureForComparison() - - reset_similarity = self.compareImage(self.reset_image, self.reset_mask, capture) - if reset_similarity >= self.reset_image_threshold: - self.send_command("reset") - self.reset() - continue - - QtTest.QTest.qWait(1) + if self.__pause_loop(split_delay, "Delayed Split:"): + return + + for button in buttons_to_disable: + button.setEnabled(True) self.waiting_for_split_delay = False # if {p} flag hit pause key, otherwise hit split hotkey - if (self.flags & PAUSE_FLAG == PAUSE_FLAG): - self.send_command("pause") - else: - self.send_command("split") + send_command(self, "pause" if self.split_image.check_flag(PAUSE_FLAG) else "split") # if loop check box is checked and its the last split, go to first split. # else go to the next split image. - if self.loopCheckBox.isChecked() and self.split_image_number == self.number_of_split_images - 1: + if self.settings_dict["loop_splits"] and self.split_image_number == number_of_split_images - 1: self.split_image_number = 0 else: - self.split_image_number = self.split_image_number + 1 - - # set a "pause" split image number. This is done so that it can detect if user hit split/undo split while paused. - pause_split_image_number = self.split_image_number - - # if its not the last split image, pause for the amount set by the user - if self.number_of_split_images != self.split_image_number: - - if not self.is_auto_controlled: - # if its the last split image and last loop number, disable the skip split button - if self.split_image_number == self.number_of_split_images - 1 or (self.groupDummySplitsCheckBox.isChecked() == True and self.dummy_splits_array[self.split_image_number:].count(False) <= 1): - self.skipsplitButton.setEnabled(False) - else: - self.skipsplitButton.setEnabled(True) - - # if its the first split image, disable the undo split button - if self.split_image_number == 0: - self.undosplitButton.setEnabled(False) - else: - self.undosplitButton.setEnabled(True) - - QtWidgets.QApplication.processEvents() - - # I have a pause loop here so that it can check if the user presses skip split, undo split, or reset here. - # Also updates the current split image text, counting down the time until the next split image - if self.pause > 0: - self.currentsplitimagefileLabel.setText(' ') - self.imageloopLabel.setText('Image Loop: -') - pause_start_time = time.time() - while time.time() - pause_start_time < self.pause: - pause_time_left = round(self.pause - (time.time() - pause_start_time), 1) - self.currentSplitImage.setText(f'None (Paused). {pause_time_left} sec remaining') - - # check for reset - if win32gui.GetWindowText(self.hwnd) == '': - self.reset() - if self.checkForReset(): - return - - # check for skip/undo split: - if self.split_image_number != pause_split_image_number: - break - - # calculate similarity for reset image - if self.shouldCheckResetImage(): - capture = self.getCaptureForComparison() - - reset_similarity = self.compareImage(self.reset_image, self.reset_mask, capture) - if reset_similarity >= self.reset_image_threshold: - self.send_command("reset") - self.reset() - continue - - QtTest.QTest.qWait(1) + self.split_image_number += 1 + + # If its not the last split image, pause for the amount set by the user + # A pause loop to check if the user presses skip split, undo split, or reset here. + # Also updates the current split image text, counting down the time until the next split image + if self.__pause_loop(self.split_image.get_pause_time(self), "None (Paused)."): + return # loop breaks to here when the last image splits - self.guiChangesOnReset() + self.is_running = False + self.gui_changes_on_reset(True) + + def __similarity_threshold_loop(self, number_of_split_images: int, dummy_splits_array: list[bool]): + """ + Wait until the similarity threshold is met. + + Returns True if the loop was interrupted by a reset. + """ + # Type checking + if not self.split_image: + return False + + start = time() + while True: + capture, _ = self.__get_capture_for_comparison() + + if self.__reset_if_should(capture): + return True + + similarity = self.split_image.compare_with_capture(self, capture) + + # Show live similarity + self.table_current_image_live_label.setText(decimal(similarity)) + + # if the similarity becomes higher than highest similarity, set it as such. + if similarity > self.highest_similarity: + self.highest_similarity = similarity + + # show live highest similarity if the checkbox is checked + self.table_current_image_highest_label.setText(decimal(self.highest_similarity)) + + # If its the last split image and last loop number, disable the next image button + # If its the first split image, disable the undo split and previous image buttons + self.next_image_button.setEnabled(self.split_image_number != number_of_split_images - 1) + self.previous_image_button.setEnabled(self.split_image_number != 0) + if not self.is_auto_controlled: + # If its the last non-dummy split image and last loop number, disable the skip split button + self.skip_split_button.setEnabled(dummy_splits_array[self.split_image_number:].count(False) > 1) + self.undo_split_button.setEnabled(self.split_image_number != 0) + QApplication.processEvents() + + # Limit the number of time the comparison runs to reduce cpu usage + frame_interval = 1 / self.settings_dict["fps_limit"] + # Use a time delta to have a consistant check interval + wait_delta_ms = int((frame_interval - (time() - start) % frame_interval) * 1000) + + below_flag = self.split_image.check_flag(BELOW_FLAG) + # if the b flag is set, let similarity go above threshold first, + # then split on similarity below threshold. + # if no b flag, just split when similarity goes above threshold. + # TODO: Abstract with similar check in start image + if not self.waiting_for_split_delay: + if similarity >= self.split_image.get_similarity_threshold(self): + if not below_flag: + break + if not self.split_below_threshold: + self.split_below_threshold = True + QTest.qWait(wait_delta_ms) + continue + elif below_flag and self.split_below_threshold and is_valid_image(capture): + self.split_below_threshold = False + break - def guiChangesOnStart(self): - self.timerStartImage.stop() - self.startautosplitterButton.setText('Running...') - self.browseButton.setEnabled(False) - self.groupDummySplitsCheckBox.setEnabled(False) - self.startImageReloadButton.setEnabled(False) + QTest.qWait(wait_delta_ms) - if not self.is_auto_controlled: - self.startautosplitterButton.setEnabled(False) - self.resetButton.setEnabled(True) - self.undosplitButton.setEnabled(True) - self.skipsplitButton.setEnabled(True) - self.setsplithotkeyButton.setEnabled(False) - self.setresethotkeyButton.setEnabled(False) - self.setskipsplithotkeyButton.setEnabled(False) - self.setundosplithotkeyButton.setEnabled(False) - self.setpausehotkeyButton.setEnabled(False) - - QtWidgets.QApplication.processEvents() - - - def guiChangesOnReset(self): - self.startautosplitterButton.setText('Start Auto Splitter') - self.imageloopLabel.setText('Image Loop: -') - self.currentSplitImage.setText(' ') - self.currentsplitimagefileLabel.setText(' ') - self.livesimilarityLabel.setText(' ') - self.highestsimilarityLabel.setText(' ') - self.currentsimilaritythresholdnumberLabel.setText(' ') - self.browseButton.setEnabled(True) - self.groupDummySplitsCheckBox.setEnabled(True) - self.startImageReloadButton.setEnabled(True) + return False - if not self.is_auto_controlled: - self.startautosplitterButton.setEnabled(True) - self.resetButton.setEnabled(False) - self.undosplitButton.setEnabled(False) - self.skipsplitButton.setEnabled(False) - self.setsplithotkeyButton.setEnabled(True) - self.setresethotkeyButton.setEnabled(True) - self.setskipsplithotkeyButton.setEnabled(True) - self.setundosplithotkeyButton.setEnabled(True) - self.setpausehotkeyButton.setEnabled(True) - - QtWidgets.QApplication.processEvents() - self.loadStartImage(False, False) - - - def compareImage(self, image, mask, capture): - if image is None or capture is None: - return 0.0 - if mask is None: - if self.comparisonmethodComboBox.currentIndex() == 0: - return compare.compare_l2_norm(image, capture) - elif self.comparisonmethodComboBox.currentIndex() == 1: - return compare.compare_histograms(image, capture) - elif self.comparisonmethodComboBox.currentIndex() == 2: - return compare.compare_phash(image, capture) - else: - if self.comparisonmethodComboBox.currentIndex() == 0: - return compare.compare_l2_norm_masked(image, capture, mask) - elif self.comparisonmethodComboBox.currentIndex() == 1: - return compare.compare_histograms_masked(image, capture, mask) - elif self.comparisonmethodComboBox.currentIndex() == 2: - return compare.compare_phash_masked(image, capture, mask) - return 0.0 - - def getCaptureForComparison(self): - # grab screenshot of capture region - capture = capture_windows.capture_region(self.hwnd, self.rect) - # Capture with nearest neighbor interpolation only if the split image has transparency to support older setups - if self.imageHasTransparency: - capture = cv2.resize(capture, COMPARISON_RESIZE, interpolation=cv2.INTER_NEAREST) - else: - capture = cv2.resize(capture, COMPARISON_RESIZE) - # convert to BGR - return cv2.cvtColor(capture, cv2.COLOR_BGRA2BGR) + def __pause_loop(self, stop_time: float, message: str): + """ + Wait for a certain time and show the timer to the user. + Can be stopped early if the current split goes past the one when the loop started. + + Returns True if the loop was interrupted by a reset. + """ + if stop_time <= 0: + return False + start_time = time() + # Set a "pause" split image number. + # This is done so that it can detect if user hit split/undo split while paused/delayed. + pause_split_image_number = self.split_image_number + while True: + # Calculate similarity for reset image + if self.__reset_if_should(self.__get_capture_for_comparison()[0]): + return True + + time_delta = time() - start_time + if ( + # Check for end of the pause/delay + time_delta >= stop_time + # Check for skip split / next image: + or self.split_image_number > pause_split_image_number + # Check for undo split / previous image: + or self.split_image_number < pause_split_image_number + ): + break - def shouldCheckResetImage(self): - return self.reset_image is not None and time.time() - self.run_start_time > self.reset_image_pause_time + self.current_split_image.setText(f"{message} {seconds_remaining_text(stop_time - time_delta)}") - def findResetImage(self): - self.reset_image = None - self.reset_mask = None + QTest.qWait(1) + return False - reset_image_file = None - for i, image in enumerate(self.split_image_filenames): - if split_parser.is_reset_image(image): - reset_image_file = image - break + def gui_changes_on_start(self): + self.timer_start_image.stop() + self.start_auto_splitter_button.setText("Running...") + self.browse_button.setEnabled(False) + self.reload_start_image_button.setEnabled(False) + self.previous_image_button.setEnabled(True) + self.next_image_button.setEnabled(True) - if reset_image_file is None: - return + # TODO: Do we actually need to disable setting new hotkeys once started? + # What does this achieve? (See below TODO) + if self.SettingsWidget: + for hotkey in HOTKEYS: + getattr(self.SettingsWidget, f"set_{hotkey}_hotkey_button").setEnabled(False) - self.split_image_filenames.remove(reset_image_file) + if not self.is_auto_controlled: + self.start_auto_splitter_button.setEnabled(False) + self.reset_button.setEnabled(True) + self.undo_split_button.setEnabled(True) + self.skip_split_button.setEnabled(True) + + QApplication.processEvents() + + def gui_changes_on_reset(self, safe_to_reload_start_image: bool = False): + self.start_auto_splitter_button.setText("Start Auto Splitter") + self.image_loop_value_label.setText("N/A") + self.current_split_image.clear() + self.current_image_file_label.clear() + self.table_current_image_live_label.setText("-") + self.table_current_image_highest_label.setText("-") + self.table_current_image_threshold_label.setText("-") + self.table_reset_image_live_label.setText("-") + self.table_reset_image_highest_label.setText("-") + self.table_reset_image_threshold_label.setText("-") + self.browse_button.setEnabled(True) + self.reload_start_image_button.setEnabled(True) + self.previous_image_button.setEnabled(False) + self.next_image_button.setEnabled(False) + + # TODO: Do we actually need to disable setting new hotkeys once started? + # What does this achieve? (see above TODO) + if self.SettingsWidget and not self.is_auto_controlled: + for hotkey in HOTKEYS: + getattr(self.SettingsWidget, f"set_{hotkey}_hotkey_button").setEnabled(True) - # create reset image and keep in memory - path = os.path.join(self.split_image_directory, reset_image_file) + if not self.is_auto_controlled: + self.start_auto_splitter_button.setEnabled(True) + self.reset_button.setEnabled(False) + self.undo_split_button.setEnabled(False) + self.skip_split_button.setEnabled(False) + + QApplication.processEvents() + if safe_to_reload_start_image: + self.load_start_image_signal.emit(False, False) + + def __get_capture_for_comparison(self): + """Grab capture region and resize for comparison.""" + capture, is_old_image = self.capture_method.get_frame(self) + + # This most likely means we lost capture + # (ie the captured window was closed, crashed, lost capture device, etc.) + if not is_valid_image(capture): + # Try to recover by using the window name + if self.settings_dict["capture_method"] == CaptureMethodEnum.VIDEO_CAPTURE_DEVICE: + self.live_image.setText("Waiting for capture device...") + else: + self.live_image.setText("Trying to recover window...") + recovered = self.capture_method.recover_window(self.settings_dict["captured_window_title"], self) + if recovered: + capture, _ = self.capture_method.get_frame(self) + + return capture, is_old_image + + def __reset_if_should(self, capture: cv2.Mat | None): + """Checks if we should reset, resets if it's the case, and returns the result.""" + if self.reset_image: + if self.settings_dict["enable_auto_reset"]: + similarity = self.reset_image.compare_with_capture(self, capture) + threshold = self.reset_image.get_similarity_threshold(self) + + pause_times = [self.reset_image.get_pause_time(self)] + if self.start_image: + pause_times.append(self.start_image.get_pause_time(self)) + paused = time() - self.run_start_time <= max(pause_times) + if paused: + should_reset = False + self.table_reset_image_live_label.setText("paused") + else: + should_reset = similarity >= threshold + if similarity > self.reset_highest_similarity: + self.reset_highest_similarity = similarity + self.table_reset_image_highest_label.setText(decimal(self.reset_highest_similarity)) + self.table_reset_image_live_label.setText(decimal(similarity)) - # Override values if they have been specified on the file - pause_from_filename = split_parser.pause_from_filename(reset_image_file) - self.reset_image_pause_time = self.pauseDoubleSpinBox.value() \ - if pause_from_filename is None \ - else pause_from_filename - threshold_from_filename = split_parser.threshold_from_filename(reset_image_file) - self.reset_image_threshold = self.similaritythresholdDoubleSpinBox.value() \ - if threshold_from_filename is None \ - else threshold_from_filename + self.table_reset_image_threshold_label.setText(decimal(threshold)) - self.reset_image = cv2.imread(path, cv2.IMREAD_UNCHANGED) - if self.reset_image is None: - error_messages.imageTypeError(path) - return - # if image has transparency, create a mask - if compare.checkIfImageHasTransparency(self.reset_image): - self.reset_image = cv2.resize(self.reset_image, COMPARISON_RESIZE, interpolation=cv2.INTER_NEAREST) - # Create mask based on resized, nearest neighbor interpolated split image - lower = np.array([0, 0, 0, 1], dtype="uint8") - upper = np.array([255, 255, 255, 255], dtype="uint8") - self.reset_mask = cv2.inRange(self.reset_image, lower, upper) - - # set split image as BGR - self.reset_image = cv2.cvtColor(self.reset_image, cv2.COLOR_BGRA2BGR) - - # otherwise, open image normally. Capture with nearest neighbor interpolation only if the split image has transparency to support older setups + if should_reset: + send_command(self, "reset") + self.reset() + else: + self.table_reset_image_live_label.setText("disabled") else: - self.reset_image = cv2.imread(path, cv2.IMREAD_COLOR) - self.reset_image = cv2.resize(self.reset_image, COMPARISON_RESIZE) - - def removeStartAutoSplitterImage(self): - start_auto_splitter_image_file = None - for _, image in enumerate(self.split_image_filenames): - if split_parser.is_start_auto_splitter_image(image): - start_auto_splitter_image_file = image - break - - if start_auto_splitter_image_file is None: - return + self.table_reset_image_live_label.setText("N/A") + self.table_reset_image_threshold_label.setText("N/A") + self.table_reset_image_highest_label.setText("N/A") - self.split_image_filenames.remove(start_auto_splitter_image_file) + return self.__check_for_reset_state_update_ui() - def updateSplitImage(self, custom_image_file: str = '', from_start_image: bool = False): - # Splitting/skipping when there are no images left or Undoing past the first image + def __update_split_image(self, specific_image: AutoSplitImage | None = None): # Start image is expected to be out of range (index 0 of 0-length array) - if "START_AUTO_SPLITTER" not in custom_image_file.upper() and self.is_current_split_out_of_range(): - self.reset() - return + if not specific_image or specific_image.image_type != ImageType.START: + # need to reset highest_similarity and split_below_threshold each time an image updates. + self.highest_similarity = 0.0 + self.split_below_threshold = False + # Splitting/skipping when there are no images left or Undoing past the first image + if self.__is_current_split_out_of_range(): + self.reset() + return - # get split image path - split_image_file = custom_image_file or self.split_image_filenames_including_loops[0 + self.split_image_number] - self.split_image_path = os.path.join(self.split_image_directory, split_image_file) + # Get split image + self.split_image = specific_image or self.split_images_and_loop_number[0 + self.split_image_number][0] + if is_valid_image(self.split_image.byte_array): + set_preview_image(self.current_split_image, self.split_image.byte_array, True) - # get flags - self.flags = split_parser.flags_from_filename(split_image_file) + self.current_image_file_label.setText(self.split_image.filename) + self.table_current_image_threshold_label.setText(decimal(self.split_image.get_similarity_threshold(self))) - self.split_image = cv2.imread(self.split_image_path, cv2.IMREAD_UNCHANGED) - if self.split_image is None: - error_messages.imageTypeError(self.split_image_path) - return - self.imageHasTransparency = compare.checkIfImageHasTransparency(self.split_image) - # if image has transparency, create a mask - if self.imageHasTransparency: - split_image_display = copy(self.split_image) - # Transform transparency into UI's gray BG color - transparent_mask = split_image_display[:, :, 3] == 0 - split_image_display[:, :, 3] == 0 - split_image_display[transparent_mask] = [240, 240, 240, 255] - split_image_display = cv2.cvtColor(split_image_display, cv2.COLOR_BGRA2RGB) - - self.split_image = cv2.resize(self.split_image, COMPARISON_RESIZE, interpolation=cv2.INTER_NEAREST) - # Create mask based on resized, nearest neighbor interpolated split image - lower = np.array([0, 0, 0, 1], dtype="uint8") - upper = np.array([255, 255, 255, 255], dtype="uint8") - self.image_mask = cv2.inRange(self.split_image, lower, upper) - - # set split image as BGR - self.split_image = cv2.cvtColor(self.split_image, cv2.COLOR_BGRA2BGR) - - # otherwise, open image normally. don't interpolate nearest neighbor here so setups before 1.2.0 still work. - else: - self.split_image = cv2.imread(self.split_image_path, cv2.IMREAD_COLOR) - split_image_display = cv2.cvtColor(copy(self.split_image), cv2.COLOR_BGR2RGB) - self.split_image = cv2.resize(self.split_image, COMPARISON_RESIZE) - self.image_mask = None - - split_image_display = cv2.resize(split_image_display, DISPLAY_RESIZE) - # Set current split image in UI - qImage = QtGui.QImage(split_image_display, - split_image_display.shape[1], - split_image_display.shape[0], - split_image_display.shape[1] * 3, - QtGui.QImage.Format.Format_RGB888) - self.updateCurrentSplitImage.emit(qImage) - self.currentsplitimagefileLabel.setText(split_image_file) - - # Override values if they have been specified on the file - pause_from_filename = split_parser.pause_from_filename(split_image_file) - self.pause = self.pauseDoubleSpinBox.value() \ - if pause_from_filename is None \ - else pause_from_filename - threshold_from_filename = split_parser.threshold_from_filename(split_image_file) - self.similarity_threshold = self.similaritythresholdDoubleSpinBox.value() \ - if threshold_from_filename is None \ - else threshold_from_filename - self.currentsimilaritythresholdnumberLabel.setText("{:.2f}".format(self.similarity_threshold)) - - # Get delay for split, if any - self.split_delay = split_parser.delay_from_filename(split_image_file) - - # Set Image Loop # - if not from_start_image: - loop_tuple = self.split_image_filenames_and_loop_number[self.split_image_number] - self.imageloopLabel.setText(f"Image Loop: {loop_tuple[1]}/{loop_tuple[2]}") + # Set Image Loop number + if specific_image and specific_image.image_type == ImageType.START: + self.image_loop_value_label.setText("N/A") else: - self.imageloopLabel.setText("Image Loop: 1/1") + loop_tuple = self.split_images_and_loop_number[self.split_image_number] + self.image_loop_value_label.setText(f"{loop_tuple[1]}/{loop_tuple[0].loops}") - # need to set split below threshold to false each time an image updates. - self.split_below_threshold = False - - self.similarity = 0 - self.highest_similarity = 0.001 - - # exit safely when closing the window - def closeEvent(self, event: QtGui.QCloseEvent = None): - #save global setting values here - self.setting_check_for_updates_on_open.setValue('check_for_updates_on_open', - self.actionCheck_for_Updates_on_Open.isChecked()) + @override + def closeEvent(self, event: QtGui.QCloseEvent | None = None): + """Exit safely when closing the window.""" - def exit(): + def exit_program() -> NoReturn: + if self.update_auto_control: + self.update_auto_control.terminate() + self.capture_method.close(self) if event is not None: event.accept() if self.is_auto_controlled: - self.update_auto_control.terminate() # stop main thread (which is probably blocked reading input) via an interrupt signal - # only available for windows in version 3.2 or higher os.kill(os.getpid(), signal.SIGINT) sys.exit() @@ -1163,52 +860,106 @@ def exit(): # This also more gracefully exits LiveSplit # Users can still manually save their settings if event is None: - exit() + exit_program() - if self.haveSettingsChanged(): - # give a different warning if there was never a settings file that was loaded successfully, and save as instead of save. - msgBox = QtWidgets.QMessageBox + if user_profile.have_settings_changed(self): + # Give a different warning if there was never a settings file that was loaded successfully, + # and "save as" instead of "save". settings_file_name = "Untitled" \ - if self.last_successfully_loaded_settings_file_path is None \ + if not self.last_successfully_loaded_settings_file_path \ else os.path.basename(self.last_successfully_loaded_settings_file_path) - warning_message = f"Do you want to save changes made to settings file {settings_file_name}?" - warning = msgBox.warning( + warning = QMessageBox.warning( self, "AutoSplit", - warning_message, - msgBox.StandardButton.Yes | msgBox.StandardButton.No | msgBox.StandardButton.Cancel) - - if warning == msgBox.StandardButton.Yes: - # TODO: Don't close if user cancelled the save - self.saveSettingsAs() - exit() - if warning == msgBox.StandardButton.No: - exit() - if warning == msgBox.StandardButton.Cancel: + f"Do you want to save changes made to settings file {settings_file_name}?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No | QMessageBox.StandardButton.Cancel, + ) + + if warning is QMessageBox.StandardButton.Yes: + if user_profile.save_settings(self): + exit_program() + else: + event.ignore() + if warning is QMessageBox.StandardButton.No: + exit_program() + if warning is QMessageBox.StandardButton.Cancel: event.ignore() else: - exit() + exit_program() + + +def set_preview_image(qlabel: QLabel, image: cv2.Mat | None, transparency: bool): + if not is_valid_image(image): + # Clear current pixmap if no image. But don't clear text + if not qlabel.text(): + qlabel.clear() + else: + if transparency: + color_code = cv2.COLOR_BGRA2RGBA + image_format = QtGui.QImage.Format.Format_RGBA8888 + else: + color_code = cv2.COLOR_BGRA2BGR + image_format = QtGui.QImage.Format.Format_BGR888 + + capture = cv2.cvtColor(image, color_code) + height, width, channels = capture.shape + qimage = QtGui.QImage(capture.data, width, height, width * channels, image_format) + qlabel.setPixmap( + QtGui.QPixmap(qimage).scaled( + qlabel.size(), + QtCore.Qt.AspectRatioMode.IgnoreAspectRatio, + QtCore.Qt.TransformationMode.SmoothTransformation, + ), + ) + + +def seconds_remaining_text(seconds: float): + return f"{seconds:.1f} second{'' if 0 < seconds <= 1 else 's'} remaining" + + +def is_already_open(): + # When running directly in Python, any AutoSplit process means it's already open + # When bundled, we must ignore itself and the splash screen + max_processes = 3 if FROZEN else 1 + process_count = 0 + for process in process_iter(): + if process.name() == "AutoSplit.exe": + process_count += 1 + if process_count >= max_processes: + return True + return False def main(): - app = QtWidgets.QApplication(sys.argv) - app.setWindowIcon(QtGui.QIcon(':/resources/icon.ico')) - - main_window = AutoSplit() - main_window.show() - if main_window.actionCheck_for_Updates_on_Open.isChecked(): - checkForUpdates(main_window, check_for_updates_on_open=True) - - # Kickoff the event loop every so often so we can handle KeyboardInterrupt (^C) - timer = QtCore.QTimer() - timer.timeout.connect(lambda: None) - timer.start(500) + # Best to call setStyle before the QApplication constructor + # https://doc.qt.io/qt-6/qapplication.html#setStyle-1 + QApplication.setStyle("fusion") + # Call to QApplication outside the try-except so we can show error messages + app = QApplication(sys.argv) + try: + app.setWindowIcon(QtGui.QIcon(":/resources/icon.ico")) + + if is_already_open(): + error_messages.already_open() + + AutoSplit() + + if not FROZEN: + # Kickoff the event loop every so often so we can handle KeyboardInterrupt (^C) + timer = QtCore.QTimer() + timer.timeout.connect(lambda: None) + timer.start(500) + + exit_code = app.exec() + except Exception as exception: # noqa: BLE001 # We really want to catch everything here + error_messages.handle_top_level_exceptions(exception) + # Catch Keyboard Interrupts for a clean close - signal.signal(signal.SIGINT, lambda _, __: sys.exit(app)) + signal.signal(signal.SIGINT, lambda code, _: sys.exit(code)) - sys.exit(app.exec()) + sys.exit(exit_code) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/AutoSplitImage.py b/src/AutoSplitImage.py new file mode 100644 index 00000000..2d59da81 --- /dev/null +++ b/src/AutoSplitImage.py @@ -0,0 +1,159 @@ +from __future__ import annotations + +import os +from enum import IntEnum +from math import sqrt +from typing import TYPE_CHECKING + +import cv2 +import numpy as np + +import error_messages +from compare import COMPARE_METHODS_BY_INDEX, check_if_image_has_transparency +from utils import MAXBYTE, RGB_CHANNEL_COUNT, ColorChannel, ImageShape, is_valid_image + +if TYPE_CHECKING: + from AutoSplit import AutoSplit + +# Resize to these width and height so that FPS performance increases +COMPARISON_RESIZE_WIDTH = 320 +COMPARISON_RESIZE_HEIGHT = 240 +COMPARISON_RESIZE = (COMPARISON_RESIZE_WIDTH, COMPARISON_RESIZE_HEIGHT) +COMPARISON_RESIZE_AREA = COMPARISON_RESIZE_WIDTH * COMPARISON_RESIZE_HEIGHT +MASK_LOWER_BOUND = np.array([0, 0, 0, 1], dtype="uint8") +MASK_UPPER_BOUND = np.array([MAXBYTE, MAXBYTE, MAXBYTE, MAXBYTE], dtype="uint8") +START_KEYWORD = "start_auto_splitter" +RESET_KEYWORD = "reset" + + +class ImageType(IntEnum): + SPLIT = 0 + RESET = 1 + START = 2 + + +class AutoSplitImage(): + path: str + filename: str + flags: int + loops: int + image_type: ImageType + byte_array: cv2.Mat | None = None + mask: cv2.Mat | None = None + # This value is internal, check for mask instead + _has_transparency = False + # These values should be overriden by some Defaults if None. Use getters instead + __delay_time: float | None = None + __comparison_method: int | None = None + __pause_time: float | None = None + __similarity_threshold: float | None = None + + def get_delay_time(self, default: AutoSplit | int): + """Get image's delay time or fallback to the default value from spinbox.""" + default_value = default \ + if isinstance(default, int) \ + else default.settings_dict["default_delay_time"] + return default_value if self.__delay_time is None else self.__delay_time + + def __get_comparison_method(self, default: AutoSplit | int): + """Get image's comparison or fallback to the default value from combobox.""" + default_value = default \ + if isinstance(default, int) \ + else default.settings_dict["default_comparison_method"] + return default_value if self.__comparison_method is None else self.__comparison_method + + def get_pause_time(self, default: AutoSplit | float): + """Get image's pause time or fallback to the default value from spinbox.""" + default_value = default \ + if isinstance(default, float) \ + else default.settings_dict["default_pause_time"] + return default_value if self.__pause_time is None else self.__pause_time + + def get_similarity_threshold(self, default: AutoSplit | float): + """Get image's similarity threshold or fallback to the default value from spinbox.""" + default_value = default \ + if isinstance(default, float) \ + else default.settings_dict["default_similarity_threshold"] + return default_value if self.__similarity_threshold is None else self.__similarity_threshold + + def __init__(self, path: str): + self.path = path + self.filename = os.path.split(path)[-1].lower() + self.flags = flags_from_filename(self.filename) + self.loops = loop_from_filename(self.filename) + self.__delay_time = delay_time_from_filename(self.filename) + self.__comparison_method = comparison_method_from_filename(self.filename) + self.__pause_time = pause_from_filename(self.filename) + self.__similarity_threshold = threshold_from_filename(self.filename) + self.__read_image_bytes(path) + + if START_KEYWORD in self.filename: + self.image_type = ImageType.START + elif RESET_KEYWORD in self.filename: + self.image_type = ImageType.RESET + else: + self.image_type = ImageType.SPLIT + + def __read_image_bytes(self, path: str): + image = cv2.imread(path, cv2.IMREAD_UNCHANGED) + if not is_valid_image(image): + self.byte_array = None + error_messages.image_type(path) + return + + self._has_transparency = check_if_image_has_transparency(image) + # If image has transparency, create a mask + if self._has_transparency: + # Adaptively determine the target size according to + # the number of nonzero elements in the alpha channel of the split image. + # This may result in images bigger than COMPARISON_RESIZE if there's plenty of transparency. + # Which wouldn't incur any performance loss in methods where masked regions are ignored. + scale = min(1, sqrt(COMPARISON_RESIZE_AREA / cv2.countNonZero(image[:, :, ColorChannel.Alpha]))) + + image = cv2.resize( + image, + dsize=None, + fx=scale, + fy=scale, + interpolation=cv2.INTER_NEAREST, + ) + + # Mask based on adaptively resized, nearest neighbor interpolated split image + self.mask = cv2.inRange(image, MASK_LOWER_BOUND, MASK_UPPER_BOUND) + else: + image = cv2.resize(image, COMPARISON_RESIZE, interpolation=cv2.INTER_NEAREST) + # Add Alpha channel if missing + if image.shape[ImageShape.Alpha] == RGB_CHANNEL_COUNT: + image = cv2.cvtColor(image, cv2.COLOR_BGR2BGRA) + + self.byte_array = image + + def check_flag(self, flag: int): + return self.flags & flag == flag + + def compare_with_capture( + self, + default: AutoSplit | int, + capture: cv2.Mat | None, + ): + """Compare image with capture using image's comparison method. Falls back to combobox.""" + if not is_valid_image(self.byte_array) or not is_valid_image(capture): + return 0.0 + capture = cv2.resize(capture, self.byte_array.shape[1::-1]) + comparison_method = self.__get_comparison_method(default) + + return COMPARE_METHODS_BY_INDEX.get(comparison_method, compare_dummy)(self.byte_array, capture, self.mask) + + +def compare_dummy(*_: object): return 0.0 + + +if True: + from split_parser import ( + comparison_method_from_filename, + delay_time_from_filename, + flags_from_filename, + loop_from_filename, + pause_from_filename, + threshold_from_filename, + ) diff --git a/src/about.py b/src/about.py deleted file mode 100644 index a4303bca..00000000 --- a/src/about.py +++ /dev/null @@ -1,58 +0,0 @@ -# Form implementation generated from reading ui file '.\res\about.ui' -# -# Created by: PyQt6 UI code generator 6.1.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_aboutAutoSplitWidget(object): - def setupUi(self, aboutAutoSplitWidget): - aboutAutoSplitWidget.setObjectName("aboutAutoSplitWidget") - aboutAutoSplitWidget.resize(264, 250) - aboutAutoSplitWidget.setMinimumSize(QtCore.QSize(264, 250)) - aboutAutoSplitWidget.setMaximumSize(QtCore.QSize(264, 250)) - font = QtGui.QFont() - font.setPointSize(9) - aboutAutoSplitWidget.setFont(font) - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(":/resources/icon.ico"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) - aboutAutoSplitWidget.setWindowIcon(icon) - self.okButton = QtWidgets.QPushButton(aboutAutoSplitWidget) - self.okButton.setGeometry(QtCore.QRect(180, 220, 75, 24)) - self.okButton.setObjectName("okButton") - self.createdbyLabel = QtWidgets.QLabel(aboutAutoSplitWidget) - self.createdbyLabel.setGeometry(QtCore.QRect(10, 44, 161, 32)) - self.createdbyLabel.setObjectName("createdbyLabel") - self.versionLabel = QtWidgets.QLabel(aboutAutoSplitWidget) - self.versionLabel.setGeometry(QtCore.QRect(10, 21, 161, 16)) - self.versionLabel.setObjectName("versionLabel") - self.donatetextLabel = QtWidgets.QLabel(aboutAutoSplitWidget) - self.donatetextLabel.setGeometry(QtCore.QRect(30, 95, 204, 32)) - self.donatetextLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.donatetextLabel.setObjectName("donatetextLabel") - self.donatebuttonLabel = QtWidgets.QLabel(aboutAutoSplitWidget) - self.donatebuttonLabel.setGeometry(QtCore.QRect(60, 150, 147, 47)) - self.donatebuttonLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.donatebuttonLabel.setObjectName("donatebuttonLabel") - self.iconLabel = QtWidgets.QLabel(aboutAutoSplitWidget) - self.iconLabel.setGeometry(QtCore.QRect(190, 17, 62, 71)) - self.iconLabel.setObjectName("iconLabel") - - self.retranslateUi(aboutAutoSplitWidget) - self.okButton.clicked.connect(aboutAutoSplitWidget.close) - QtCore.QMetaObject.connectSlotsByName(aboutAutoSplitWidget) - - def retranslateUi(self, aboutAutoSplitWidget): - _translate = QtCore.QCoreApplication.translate - aboutAutoSplitWidget.setWindowTitle(_translate("aboutAutoSplitWidget", "About AutoSplit")) - self.okButton.setText(_translate("aboutAutoSplitWidget", "OK")) - self.createdbyLabel.setText(_translate("aboutAutoSplitWidget", "

Created by Toufool and Faschz
Maintained by Avasam

")) - self.versionLabel.setText(_translate("aboutAutoSplitWidget", "Version: ")) - self.donatetextLabel.setText(_translate("aboutAutoSplitWidget", "If you enjoy using this program, please\n" -"consider donating. Thank you!")) - self.donatebuttonLabel.setText(_translate("aboutAutoSplitWidget", "

")) - self.iconLabel.setText(_translate("aboutAutoSplitWidget", "

")) diff --git a/src/capture_method/BitBltCaptureMethod.py b/src/capture_method/BitBltCaptureMethod.py new file mode 100644 index 00000000..b15c9fbd --- /dev/null +++ b/src/capture_method/BitBltCaptureMethod.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +import ctypes +import ctypes.wintypes +from typing import TYPE_CHECKING, cast + +import cv2 +import numpy as np +import pywintypes +import win32con +import win32ui +from win32 import win32gui + +from capture_method.CaptureMethodBase import CaptureMethodBase +from utils import RGBA_CHANNEL_COUNT, get_window_bounds, is_valid_hwnd + +if TYPE_CHECKING: + from AutoSplit import AutoSplit + +# This is an undocumented nFlag value for PrintWindow +PW_RENDERFULLCONTENT = 0x00000002 + + +class BitBltCaptureMethod(CaptureMethodBase): + name = "BitBlt" + short_description = "fastest, least compatible" + description = ( + "\nThe best option when compatible. But it cannot properly record " + + "\nOpenGL, Hardware Accelerated or Exclusive Fullscreen windows. " + + "\nThe smaller the selected region, the more efficient it is. " + ) + + _render_full_content = False + + def get_frame(self, autosplit: AutoSplit) -> tuple[cv2.Mat | None, bool]: + selection = autosplit.settings_dict["capture_region"] + hwnd = autosplit.hwnd + image: cv2.Mat | None = None + + if not self.check_selected_region_exists(autosplit): + return None, False + + # If the window closes while it's being manipulated, it could cause a crash + try: + window_dc = win32gui.GetWindowDC(hwnd) + dc_object = win32ui.CreateDCFromHandle(window_dc) + + # Causes a 10-15x performance drop. But allows recording hardware accelerated windows + if self._render_full_content: + ctypes.windll.user32.PrintWindow(hwnd, dc_object.GetSafeHdc(), PW_RENDERFULLCONTENT) + + # On Windows there is a shadow around the windows that we need to account for. + left_bounds, top_bounds, *_ = get_window_bounds(hwnd) + + compatible_dc = dc_object.CreateCompatibleDC() + bitmap = win32ui.CreateBitmap() + bitmap.CreateCompatibleBitmap(dc_object, selection["width"], selection["height"]) + compatible_dc.SelectObject(bitmap) + compatible_dc.BitBlt( + (0, 0), + (selection["width"], selection["height"]), + dc_object, + (selection["x"] + left_bounds, selection["y"] + top_bounds), + win32con.SRCCOPY, + ) + image = np.frombuffer(cast(bytes, bitmap.GetBitmapBits(True)), dtype=np.uint8) + image.shape = (selection["height"], selection["width"], RGBA_CHANNEL_COUNT) + except (win32ui.error, pywintypes.error): + return None, False + # Cleanup DC and handle + dc_object.DeleteDC() + compatible_dc.DeleteDC() + win32gui.ReleaseDC(hwnd, window_dc) + win32gui.DeleteObject(bitmap.GetHandle()) + return image, False + + def recover_window(self, captured_window_title: str, autosplit: AutoSplit): + hwnd = win32gui.FindWindow(None, captured_window_title) + if not is_valid_hwnd(hwnd): + return False + autosplit.hwnd = hwnd + return self.check_selected_region_exists(autosplit) diff --git a/src/capture_method/CaptureMethodBase.py b/src/capture_method/CaptureMethodBase.py new file mode 100644 index 00000000..b82dc9ca --- /dev/null +++ b/src/capture_method/CaptureMethodBase.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import cv2 + +from utils import is_valid_hwnd + +if TYPE_CHECKING: + from AutoSplit import AutoSplit + + +class CaptureMethodBase(): + name = "None" + short_description = "" + description = "" + + def __init__(self, autosplit: AutoSplit | None = None): + # Some capture methods don't need an initialization process + pass + + def reinitialize(self, autosplit: AutoSplit): + self.close(autosplit) + self.__init__(autosplit) + + def close(self, autosplit: AutoSplit): + # Some capture methods don't need an initialization process + pass + + def get_frame(self, autosplit: AutoSplit) -> tuple[cv2.Mat | None, bool]: + """ + Captures an image of the region for a window matching the given + parameters of the bounding box. + + @return: The image of the region in the window in BGRA format + """ + return None, False + + def recover_window(self, captured_window_title: str, autosplit: AutoSplit) -> bool: + return False + + def check_selected_region_exists(self, autosplit: AutoSplit) -> bool: + return is_valid_hwnd(autosplit.hwnd) diff --git a/src/capture_method/DXCamCaptureMethod.py b/src/capture_method/DXCamCaptureMethod.py new file mode 100644 index 00000000..e3983b07 --- /dev/null +++ b/src/capture_method/DXCamCaptureMethod.py @@ -0,0 +1,135 @@ +from __future__ import annotations + +import ctypes +from typing import TYPE_CHECKING, cast + +import cv2 + +# import d3dshot +import dxcam +import win32con +from win32 import win32api, win32gui + +from capture_method import CaptureMethodBase + +if TYPE_CHECKING: + from AutoSplit import AutoSplit + + +class DXCamCaptureMethod(CaptureMethodBase): + name = "DXCam Capture Method" + short_description = "fast, but slower than Sonic" + description = ( + "\nWork in progress description " + ) + + def __init__(self, autosplit: AutoSplit): + super().__init__(autosplit) + self.camera = None + self.cached_selection = None + self.cached_monitor = -1 + self.cached_frame = None + self.cached_frame_cv2 = None + self.monitor_rect = None + + def close(self, autosplit: AutoSplit): + if self.camera is not None: + del self.camera + + def create_camera(self, monitor): + if self.camera is not None: + del self.camera + + self.camera = dxcam.create(output_idx=monitor) + return self.camera + + def get_cached_selection(self, hwnd, selection): + do_reset = False + + temp_selection = selection.copy() + + offset_x, offset_y, *_ = win32gui.GetWindowRect(hwnd) + + temp_selection["x"] += offset_x + temp_selection["y"] += offset_y + + if self.cached_selection != temp_selection: + self.cached_selection = temp_selection + do_reset = True + + return self.cached_selection, do_reset + + def monitor_handle_to_index(self, handle): + index = -1 + for monitor in win32api.EnumDisplayMonitors(): + index += 1 + if monitor[0].handle == handle: + return index, monitor[2] + + return -1, None + + def get_cached_monitor(self, autosplit: AutoSplit): + hmonitor = ctypes.windll.user32.MonitorFromWindow( + autosplit.hwnd, win32con.MONITOR_DEFAULTTONEAREST, + ) + index, rect = self.monitor_handle_to_index(hmonitor) + if index == -1 or not self.check_selected_region_exists(autosplit): + return None, False + + do_reset = False + + if self.cached_monitor != index: + self.cached_monitor = index + self.monitor_rect = { + "left": rect[0], + "top": rect[1], + "right": rect[2], + "bottom": rect[3], + } + # self.monitor_width = rect[2] - rect[0] + # self.monitor_height = rect[3] - rect[1] + do_reset = True + + return self.cached_monitor, do_reset + + def get_latest_frame_cached(self, frame): + if frame is not self.cached_frame: + self.cached_frame = frame + self.cached_frame_cv2 = cv2.cvtColor(cast(cv2.Mat, frame), cv2.COLOR_RGBA2BGRA) + + return self.cached_frame_cv2 + + def get_frame(self, autosplit: AutoSplit): + monitor, do_reset_monitor = self.get_cached_monitor(autosplit) + selection, do_reset_selection = self.get_cached_selection( + autosplit.hwnd, + autosplit.settings_dict["capture_region"], + ) + + if do_reset_monitor: + self.create_camera(monitor) + do_reset_selection = True + + if do_reset_selection: + self.camera.stop() + + max() + max() + + right = min(selection["x"] + selection["width"], self.monitor_rect["right"]) + bottom = min(selection["y"] + selection["height"], self.monitor_rect["bottom"]) + + self.camera.start( + region=( + selection["x"], + selection["y"], + right, + bottom, + ), + ) + + screenshot = self.camera.get_latest_frame() + if screenshot is None: + return None, False + + return self.get_latest_frame_cached(screenshot), False diff --git a/src/capture_method/DesktopDuplicationCaptureMethod.py b/src/capture_method/DesktopDuplicationCaptureMethod.py new file mode 100644 index 00000000..a00ec7e9 --- /dev/null +++ b/src/capture_method/DesktopDuplicationCaptureMethod.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +import ctypes +from typing import TYPE_CHECKING, cast + +import cv2 +import d3dshot +import win32con +from win32 import win32gui + +from capture_method.BitBltCaptureMethod import BitBltCaptureMethod +from utils import GITHUB_REPOSITORY, get_window_bounds + +if TYPE_CHECKING: + from AutoSplit import AutoSplit + + +class DesktopDuplicationCaptureMethod(BitBltCaptureMethod): + name = "Direct3D Desktop Duplication" + short_description = "slower, bound to display" + description = ( + "\nDuplicates the desktop using Direct3D. " + + "\nIt can record OpenGL and Hardware Accelerated windows. " + + "\nAbout 10-15x slower than BitBlt. Not affected by window size. " + + "\nOverlapping windows will show up and can't record across displays. " + + "\nThis option may not be available for hybrid GPU laptops, " + + "\nsee D3DDD-Note-Laptops.md for a solution. " + + f"\nhttps://www.github.com/{GITHUB_REPOSITORY}#capture-method " + ) + + def __init__(self): + super().__init__() + # Must not set statically as some laptops will throw an error + self.desktop_duplication = d3dshot.create(capture_output="numpy") + + def get_frame(self, autosplit: AutoSplit): + selection = autosplit.settings_dict["capture_region"] + hwnd = autosplit.hwnd + hmonitor = ctypes.windll.user32.MonitorFromWindow(hwnd, win32con.MONITOR_DEFAULTTONEAREST) + if not hmonitor or not self.check_selected_region_exists(autosplit): + return None, False + + left_bounds, top_bounds, *_ = get_window_bounds(hwnd) + self.desktop_duplication.display = [ + display for display + in self.desktop_duplication.displays + if display.hmonitor == hmonitor + ][0] + offset_x, offset_y, *_ = win32gui.GetWindowRect(hwnd) + offset_x -= self.desktop_duplication.display.position["left"] + offset_y -= self.desktop_duplication.display.position["top"] + left = selection["x"] + offset_x + left_bounds + top = selection["y"] + offset_y + top_bounds + right = selection["width"] + left + bottom = selection["height"] + top + screenshot = self.desktop_duplication.screenshot((left, top, right, bottom)) + if screenshot is None: + return None, False + return cv2.cvtColor(cast(cv2.Mat, screenshot), cv2.COLOR_RGBA2BGRA), False diff --git a/src/capture_method/ForceFullContentRenderingCaptureMethod.py b/src/capture_method/ForceFullContentRenderingCaptureMethod.py new file mode 100644 index 00000000..6bbcd70e --- /dev/null +++ b/src/capture_method/ForceFullContentRenderingCaptureMethod.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from capture_method.BitBltCaptureMethod import BitBltCaptureMethod + + +class ForceFullContentRenderingCaptureMethod(BitBltCaptureMethod): + name = "Force Full Content Rendering" + short_description = "very slow, can affect rendering" + description = ( + "\nUses BitBlt behind the scene, but passes a special flag " + + "\nto PrintWindow to force rendering the entire desktop. " + + "\nAbout 10-15x slower than BitBlt based on original window size " + + "\nand can mess up some applications' rendering pipelines. " + ) + _render_full_content = True diff --git a/src/capture_method/VideoCaptureDeviceCaptureMethod.py b/src/capture_method/VideoCaptureDeviceCaptureMethod.py new file mode 100644 index 00000000..0addeb7b --- /dev/null +++ b/src/capture_method/VideoCaptureDeviceCaptureMethod.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +from threading import Event, Thread +from typing import TYPE_CHECKING + +import cv2 +import numpy as np +from pygrabber import dshow_graph + +from capture_method.CaptureMethodBase import CaptureMethodBase +from error_messages import CREATE_NEW_ISSUE_MESSAGE, exception_traceback +from utils import is_valid_image + +if TYPE_CHECKING: + from AutoSplit import AutoSplit + +OBS_CAMERA_BLANK_PIXEL = [127, 129, 128] + + +def is_blank(image: cv2.Mat): + # Running np.all on the entire array or looping manually through the + # entire array is extremely slow when we can't stop early. + # Instead we check for a few key pixels, in this case, corners + return np.all(image[::image.shape[0] - 1, ::image.shape[1] - 1] == OBS_CAMERA_BLANK_PIXEL) + + +class VideoCaptureDeviceCaptureMethod(CaptureMethodBase): + name = "Video Capture Device" + short_description = "see below" + description = ( + "\nUses a Video Capture Device, like a webcam, virtual cam, or capture card. " + + "\nYou can select one below. " + + "\nIf you want to use this with OBS' Virtual Camera, use the Virtualcam plugin instead " + + "\nhttps://github.com/Avasam/obs-virtual-cam/releases" + ) + + capture_device: cv2.VideoCapture + capture_thread: Thread | None + stop_thread: Event + last_captured_frame: cv2.Mat | None = None + is_old_image = False + + def __read_loop(self, autosplit: AutoSplit): + try: + while not self.stop_thread.is_set(): + try: + result, image = self.capture_device.read() + except cv2.error as error: + if not ( + error.code == cv2.Error.STS_ERROR and error.msg.endswith( + "in function 'cv::VideoCapture::grab'\n", + ) + ): + raise + # STS_ERROR most likely means the camera is occupied + result = False + image = None + if not result: + image = None + + # Blank frame. Reuse the previous one. + if image is not None and is_blank(image): + continue + + self.last_captured_frame = image + self.is_old_image = False + except Exception as exception: # noqa: BLE001 # We really want to catch everything here + error = exception + self.capture_device.release() + autosplit.show_error_signal.emit( + lambda: exception_traceback( + error, + "AutoSplit encountered an unhandled exception while " + + "trying to grab a frame and has stopped capture. " + + CREATE_NEW_ISSUE_MESSAGE, + ), + ) + + def __init__(self, autosplit: AutoSplit): + super().__init__() + filter_graph = dshow_graph.FilterGraph() + filter_graph.add_video_input_device(autosplit.settings_dict["capture_device_id"]) + width, height = filter_graph.get_input_device().get_current_format() + filter_graph.remove_filters() + + self.capture_device = cv2.VideoCapture(autosplit.settings_dict["capture_device_id"]) + self.capture_device.setExceptionMode(True) + # Ensure we're using the right camera size. And not OpenCV's default 640x480 + try: + self.capture_device.set(cv2.CAP_PROP_FRAME_WIDTH, width) + self.capture_device.set(cv2.CAP_PROP_FRAME_HEIGHT, height) + # Some cameras don't allow changing the resolution + except cv2.error: + pass + self.stop_thread = Event() + self.capture_thread = Thread(target=lambda: self.__read_loop(autosplit)) + self.capture_thread.start() + + def close(self, autosplit: AutoSplit): + self.stop_thread.set() + if self.capture_thread: + self.capture_thread.join() + self.capture_thread = None + self.capture_device.release() + + def get_frame(self, autosplit: AutoSplit): + if not self.check_selected_region_exists(autosplit): + return None, False + + image = self.last_captured_frame + is_old_image = self.is_old_image + self.is_old_image = True + if not is_valid_image(image): + return None, is_old_image + + selection = autosplit.settings_dict["capture_region"] + # Ensure we can't go OOB of the image + y = min(selection["y"], image.shape[0] - 1) + x = min(selection["x"], image.shape[1] - 1) + image = image[ + y:y + selection["height"], + x:x + selection["width"], + ] + return cv2.cvtColor(image, cv2.COLOR_BGR2BGRA), is_old_image + + def check_selected_region_exists(self, autosplit: AutoSplit): + return bool(self.capture_device.isOpened()) diff --git a/src/capture_method/WindowsGraphicsCaptureMethod.py b/src/capture_method/WindowsGraphicsCaptureMethod.py new file mode 100644 index 00000000..e38f7957 --- /dev/null +++ b/src/capture_method/WindowsGraphicsCaptureMethod.py @@ -0,0 +1,150 @@ +from __future__ import annotations + +import asyncio +from typing import TYPE_CHECKING, cast + +import cv2 +import numpy as np +from win32 import win32gui +from winsdk.windows.graphics import SizeInt32 +from winsdk.windows.graphics.capture import Direct3D11CaptureFramePool, GraphicsCaptureSession +from winsdk.windows.graphics.capture.interop import create_for_window +from winsdk.windows.graphics.directx import DirectXPixelFormat +from winsdk.windows.graphics.imaging import BitmapBufferAccessMode, SoftwareBitmap + +from capture_method.CaptureMethodBase import CaptureMethodBase +from utils import RGBA_CHANNEL_COUNT, WGC_MIN_BUILD, WINDOWS_BUILD_NUMBER, get_direct3d_device, is_valid_hwnd + +if TYPE_CHECKING: + from AutoSplit import AutoSplit + +WGC_NO_BORDER_MIN_BUILD = 20348 +LEARNING_MODE_DEVICE_BUILD = 17763 +"""https://learn.microsoft.com/en-us/uwp/api/windows.ai.machinelearning.learningmodeldevice""" + + +class WindowsGraphicsCaptureMethod(CaptureMethodBase): + name = "Windows Graphics Capture" + short_description = "fast, most compatible, capped at 60fps" + description = ( + f"\nOnly available in Windows 10.0.{WGC_MIN_BUILD} and up. " + + f"\nDue to current technical limitations, Windows versions below 10.0.0.{LEARNING_MODE_DEVICE_BUILD}" + + "\nrequire having at least one audio or video Capture Device connected and enabled." + + "\nAllows recording UWP apps, Hardware Accelerated and Exclusive Fullscreen windows. " + + "\nAdds a yellow border on Windows 10 (not on Windows 11)." + + "\nCaps at around 60 FPS. " + ) + + size: SizeInt32 + frame_pool: Direct3D11CaptureFramePool | None = None + session: GraphicsCaptureSession | None = None + """This is stored to prevent session from being garbage collected""" + last_captured_frame: cv2.Mat | None = None + + def __init__(self, autosplit: AutoSplit): + super().__init__(autosplit) + if not is_valid_hwnd(autosplit.hwnd): + return + + item = create_for_window(autosplit.hwnd) + frame_pool = Direct3D11CaptureFramePool.create_free_threaded( + get_direct3d_device(), + DirectXPixelFormat.B8_G8_R8_A8_UINT_NORMALIZED, + 1, + item.size, + ) + if not frame_pool: + raise OSError("Unable to create a frame pool for a capture session.") + session = frame_pool.create_capture_session(item) + if not session: + raise OSError("Unable to create a capture session.") + session.is_cursor_capture_enabled = False + if WINDOWS_BUILD_NUMBER >= WGC_NO_BORDER_MIN_BUILD: + session.is_border_required = False + session.start_capture() + + self.session = session + self.size = item.size + self.frame_pool = frame_pool + + def close(self, autosplit: AutoSplit): + if self.frame_pool: + self.frame_pool.close() + self.frame_pool = None + if self.session: + try: + self.session.close() + except OSError: + # OSError: The application called an interface that was marshalled for a different thread + # This still seems to close the session and prevent the following hard crash in LiveSplit + # "AutoSplit.exe " # noqa: E501 + pass + self.session = None + + def get_frame(self, autosplit: AutoSplit) -> tuple[cv2.Mat | None, bool]: + selection = autosplit.settings_dict["capture_region"] + # We still need to check the hwnd because WGC will return a blank black image + if not ( + self.check_selected_region_exists(autosplit) + # Only needed for the type-checker + and self.frame_pool + ): + return None, False + + try: + frame = self.frame_pool.try_get_next_frame() + # Frame pool is closed + except OSError: + return None, False + + async def coroutine(): + # We were too fast and the next frame wasn't ready yet + if not frame: + return None + return await (SoftwareBitmap.create_copy_from_surface_async(frame.surface) or asyncio.sleep(0, None)) + try: + software_bitmap = asyncio.run(coroutine()) + except SystemError as exception: + # HACK: can happen when closing the GraphicsCapturePicker + if str(exception).endswith("returned a result with an error set"): + return self.last_captured_frame, True + raise + + if not software_bitmap: + # HACK: Can happen when starting the region selector + return self.last_captured_frame, True + # raise ValueError("Unable to convert Direct3D11CaptureFrame to SoftwareBitmap.") + bitmap_buffer = software_bitmap.lock_buffer(BitmapBufferAccessMode.READ_WRITE) + if not bitmap_buffer: + raise ValueError("Unable to obtain the BitmapBuffer from SoftwareBitmap.") + reference = bitmap_buffer.create_reference() + image = np.frombuffer(cast(bytes, reference), dtype=np.uint8) + image.shape = (self.size.height, self.size.width, RGBA_CHANNEL_COUNT) + image = image[ + selection["y"]:selection["y"] + selection["height"], + selection["x"]:selection["x"] + selection["width"], + ] + self.last_captured_frame = image + return image, False + + def recover_window(self, captured_window_title: str, autosplit: AutoSplit): + hwnd = win32gui.FindWindow(None, captured_window_title) + if not is_valid_hwnd(hwnd): + return False + autosplit.hwnd = hwnd + self.close(autosplit) + try: + self.__init__(autosplit) + # Unrecordable hwnd found as the game is crashing + except OSError as exception: + if str(exception).endswith("The parameter is incorrect"): + return False + raise + return self.check_selected_region_exists(autosplit) + + def check_selected_region_exists(self, autosplit: AutoSplit): + return bool( + is_valid_hwnd(autosplit.hwnd) + and self.frame_pool + and self.session, + ) diff --git a/src/capture_method/__init__.py b/src/capture_method/__init__.py new file mode 100644 index 00000000..3be4864f --- /dev/null +++ b/src/capture_method/__init__.py @@ -0,0 +1,189 @@ +from __future__ import annotations + +import asyncio +from collections import OrderedDict +from dataclasses import dataclass +from enum import Enum, EnumMeta, unique +from typing import TYPE_CHECKING, TypedDict, cast + +from _ctypes import COMError +from pygrabber.dshow_graph import FilterGraph + +from capture_method.BitBltCaptureMethod import BitBltCaptureMethod +from capture_method.CaptureMethodBase import CaptureMethodBase +from capture_method.DesktopDuplicationCaptureMethod import DesktopDuplicationCaptureMethod +from capture_method.DXCamCaptureMethod import DXCamCaptureMethod +from capture_method.ForceFullContentRenderingCaptureMethod import ForceFullContentRenderingCaptureMethod +from capture_method.VideoCaptureDeviceCaptureMethod import VideoCaptureDeviceCaptureMethod +from capture_method.WindowsGraphicsCaptureMethod import WindowsGraphicsCaptureMethod +from utils import WGC_MIN_BUILD, WINDOWS_BUILD_NUMBER, first, try_get_direct3d_device + +if TYPE_CHECKING: + from AutoSplit import AutoSplit + + +class Region(TypedDict): + x: int + y: int + width: int + height: int + + +class CaptureMethodMeta(EnumMeta): + # Allow checking if simple string is enum + def __contains__(self, other: str): + try: + self(other) + except ValueError: + return False + return True + + +@unique +class CaptureMethodEnum(Enum, metaclass=CaptureMethodMeta): + # Allow TOML to save as a simple string + def __repr__(self): + return self.value + __str__ = __repr__ + + # Allow direct comparison with strings + def __eq__(self, other: object): + return self.value == other.__str__() + + # Restore hashing functionality + def __hash__(self): + return self.value.__hash__() + + NONE = "" + BITBLT = "BITBLT" + WINDOWS_GRAPHICS_CAPTURE = "WINDOWS_GRAPHICS_CAPTURE" + PRINTWINDOW_RENDERFULLCONTENT = "PRINTWINDOW_RENDERFULLCONTENT" + DESKTOP_DUPLICATION = "DESKTOP_DUPLICATION" + VIDEO_CAPTURE_DEVICE = "VIDEO_CAPTURE_DEVICE" + DXCAM = "DXCAM" + + +class CaptureMethodDict(OrderedDict[CaptureMethodEnum, type[CaptureMethodBase]]): + def get_index(self, capture_method: str | CaptureMethodEnum): + """Returns 0 if the capture_method is invalid or unsupported.""" + try: + return list(self.keys()).index(cast(CaptureMethodEnum, capture_method)) + except ValueError: + return 0 + + def get_method_by_index(self, index: int): + """ + Returns the `CaptureMethodEnum` at index. + If index is invalid, returns the first (default) `CaptureMethodEnum`. + Returns `CaptureMethodEnum.NONE` if there are no capture methods available. + """ + if len(self) <= 0: + return CaptureMethodEnum.NONE + if index <= 0: + return first(self) + return list(self.keys())[index] + + if TYPE_CHECKING: + __getitem__ = None # pyright: ignore[reportGeneralTypeIssues] # Disallow unsafe get + + def get(self, __key: CaptureMethodEnum): + """ + Returns the `CaptureMethodBase` subclass for `CaptureMethodEnum` if `CaptureMethodEnum` is available, + else defaults to the first available `CaptureMethodEnum`. + Returns `CaptureMethodBase` (default) directly if there's no capture methods. + """ + if __key == CaptureMethodEnum.NONE or len(self) <= 0: + return CaptureMethodBase + return super().get(__key, first(self.values())) + + +CAPTURE_METHODS = CaptureMethodDict() +if ( # Windows Graphics Capture requires a minimum Windows Build + WINDOWS_BUILD_NUMBER >= WGC_MIN_BUILD + # Our current implementation of Windows Graphics Capture does not ensure we can get an ID3DDevice + and try_get_direct3d_device() +): + CAPTURE_METHODS[CaptureMethodEnum.WINDOWS_GRAPHICS_CAPTURE] = WindowsGraphicsCaptureMethod +CAPTURE_METHODS[CaptureMethodEnum.BITBLT] = BitBltCaptureMethod +try: + import d3dshot + d3dshot.create(capture_output="numpy") +except (ModuleNotFoundError, COMError): + pass +else: + CAPTURE_METHODS[CaptureMethodEnum.DESKTOP_DUPLICATION] = DesktopDuplicationCaptureMethod +CAPTURE_METHODS[CaptureMethodEnum.DXCAM] = DXCamCaptureMethod +CAPTURE_METHODS[CaptureMethodEnum.PRINTWINDOW_RENDERFULLCONTENT] = ForceFullContentRenderingCaptureMethod +CAPTURE_METHODS[CaptureMethodEnum.VIDEO_CAPTURE_DEVICE] = VideoCaptureDeviceCaptureMethod + + +def change_capture_method(selected_capture_method: CaptureMethodEnum, autosplit: AutoSplit): + autosplit.capture_method.close(autosplit) + autosplit.capture_method = CAPTURE_METHODS.get(selected_capture_method)(autosplit) + if selected_capture_method == CaptureMethodEnum.VIDEO_CAPTURE_DEVICE: + autosplit.select_region_button.setDisabled(True) + autosplit.select_window_button.setDisabled(True) + else: + autosplit.select_region_button.setDisabled(False) + autosplit.select_window_button.setDisabled(False) + + +@dataclass +class CameraInfo(): + device_id: int + name: str + occupied: bool + backend: str + resolution: tuple[int, int] | None + + +def get_input_devices(): + # https://github.com/andreaschiavinato/python_grabber/pull/24 + return cast(list[str], FilterGraph().get_input_devices()) + + +def get_input_device_resolution(index: int): + filter_graph = FilterGraph() + filter_graph.add_video_input_device(index) + resolution = filter_graph.get_input_device().get_current_format() + filter_graph.remove_filters() + return resolution + + +async def get_all_video_capture_devices() -> list[CameraInfo]: + named_video_inputs = get_input_devices() + + async def get_camera_info(index: int, device_name: str): + backend = "" + # Probing freezes some devices (like GV-USB2 and AverMedia) if already in use + # #169 + # FIXME: Maybe offer the option to the user to obtain more info about their devies? + # Off by default. With a tooltip to explain the risk. + # video_capture = cv2.VideoCapture(index) + # video_capture.setExceptionMode(True) + # try: + # # https://docs.opencv.org/3.4/d4/d15/group__videoio__flags__base.html#ga023786be1ee68a9105bf2e48c700294d + # backend = video_capture.getBackendName() # STS_ASSERT + # video_capture.grab() # STS_ERROR + # except cv2.error as error: + # return CameraInfo(index, device_name, True, backend) \ + # if error.code in (cv2.Error.STS_ERROR, cv2.Error.STS_ASSERT) \ + # else None + # finally: + # video_capture.release() + + return CameraInfo(index, device_name, False, backend, get_input_device_resolution(index)) + + # https://github.com/python/typeshed/issues/2652 + future: asyncio.Future[list[CameraInfo | None]] = asyncio.gather( + *[ + get_camera_info(index, name) for index, name + in enumerate(named_video_inputs) + ], + ) + + return [ + camera_info for camera_info + in await future + if camera_info is not None + ] diff --git a/src/capture_windows.py b/src/capture_windows.py deleted file mode 100644 index 15a7dfee..00000000 --- a/src/capture_windows.py +++ /dev/null @@ -1,79 +0,0 @@ -from __future__ import annotations -from typing import Dict - -from ctypes import windll -from ctypes.wintypes import LONG, RECT, HBITMAP -from packaging import version -from win32 import win32gui -import platform -import pywintypes -import numpy as np -import win32ui -import win32con - - -# This is an undocumented nFlag value for PrintWindow -PW_RENDERFULLCONTENT = 0x00000002 -accelerated_windows: Dict[int, bool] = {} -is_windows_11 = version.parse(platform.version()) >= version.parse("10.0.22000") - - -def capture_region(hwnd: int, rect: RECT): - """ - Captures an image of the region for a window matching the given - parameters of the bounding box - - @param hwnd: Handle to the window being captured - @param rect: The coordinates of the region - @return: The image of the region in the window in BGRA format - """ - - # Windows 11 has some jank, and we're not ready to fully investigate it - # for now let's ensure it works at the cost of performance - is_accelerated_window = is_windows_11 or accelerated_windows.get(hwnd) - - # The window type is not yet known, let's find out! - if is_accelerated_window is None: - # We need to get the image at least once to check if it's full black - image = __get_image(hwnd, rect, False) - # TODO check for first non-black pixel, no need to iterate through the whole image - is_accelerated_window = not np.count_nonzero(image) - accelerated_windows[hwnd] = is_accelerated_window - return __get_image(hwnd, rect, True) if is_accelerated_window else image - - return __get_image(hwnd, rect, is_accelerated_window) - - -def __get_image(hwnd: int, rect: RECT, print_window: bool = False): - width: LONG = rect.right - rect.left - height: LONG = rect.bottom - rect.top - # If the window closes while it's being manipulated, it could cause a crash - try: - windowDC: int = win32gui.GetWindowDC(hwnd) - dcObject = win32ui.CreateDCFromHandle(windowDC) - - # Causes a 10-15x performance drop. But allows recording hardware accelerated windows - if (print_window): - windll.user32.PrintWindow(hwnd, dcObject.GetSafeHdc(), PW_RENDERFULLCONTENT) - - compatibleDC = dcObject.CreateCompatibleDC() - bitmap: HBITMAP = win32ui.CreateBitmap() - bitmap.CreateCompatibleBitmap(dcObject, width, height) - compatibleDC.SelectObject(bitmap) - compatibleDC.BitBlt((0, 0), (width, height), dcObject, (rect.left, rect.top), win32con.SRCCOPY) - except (win32ui.error, pywintypes.error): - errorImage = np.array([0, 0, 0, 1], dtype="uint8") - return errorImage - - image: np._BufferType = np.frombuffer(bitmap.GetBitmapBits(True), dtype='uint8') - image.shape = (height, width, 4) - - try: - dcObject.DeleteDC() - compatibleDC.DeleteDC() - win32gui.ReleaseDC(hwnd, windowDC) - win32gui.DeleteObject(bitmap.GetHandle()) - except win32ui.error: - pass - - return image diff --git a/src/compare.py b/src/compare.py index 699e92eb..938f97fe 100644 --- a/src/compare.py +++ b/src/compare.py @@ -1,164 +1,120 @@ -from PIL import Image -import cv2 -import imagehash -import numpy as np +from __future__ import annotations +from math import sqrt -def compare_histograms(source, capture) -> float: - """ - Compares two images by calculating their histograms, normalizing them, and - then comparing them using Bhattacharyya distance. - - @param source: 3 color image of any given width and height - @param capture: An image matching the dimensions of the source - @return: The similarity between the histograms as a number 0 to 1. - """ +import cv2 +import imagehash +from PIL import Image - source_hist = cv2.calcHist([source], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256]) - capture_hist = cv2.calcHist([capture], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256]) +from utils import MAXBYTE, RGBA_CHANNEL_COUNT, ColorChannel, ImageShape, is_valid_image - cv2.normalize(source_hist, source_hist) - cv2.normalize(capture_hist, capture_hist) +MAXRANGE = MAXBYTE + 1 +CHANNELS = [ImageShape.X, ImageShape.Y, ImageShape.Alpha] +HISTOGRAM_SIZE = [8, 8, 8] +RANGES = [0, MAXRANGE, 0, MAXRANGE, 0, MAXRANGE] +MASK_SIZE_MULTIPLIER = ColorChannel.Alpha * MAXBYTE * MAXBYTE - return 1 - cv2.compareHist(source_hist, capture_hist, cv2.HISTCMP_BHATTACHARYYA) -def compare_histograms_masked(source, capture, mask) -> float: +def compare_histograms(source: cv2.Mat, capture: cv2.Mat, mask: cv2.Mat | None = None): """ - Compares two images by calculating their histograms using a mask, normalizing + Compares two images by calculating their histograms, normalizing them, and then comparing them using Bhattacharyya distance. - @param source: 3 color image of any given width and height - @param capture: An image matching the dimensions of the source + @param source: RGB or BGR image of any given width and height + @param capture: An image matching the shape, dimensions and format of the source @param mask: An image matching the dimensions of the source, but 1 channel grayscale @return: The similarity between the histograms as a number 0 to 1. """ - source_hist = cv2.calcHist([source], [0, 1, 2], mask, [8, 8, 8], [0, 256, 0, 256, 0, 256]) - capture_hist = cv2.calcHist([capture], [0, 1, 2], mask, [8, 8, 8], [0, 256, 0, 256, 0, 256]) + source_hist = cv2.calcHist([source], CHANNELS, mask, HISTOGRAM_SIZE, RANGES) + capture_hist = cv2.calcHist([capture], CHANNELS, mask, HISTOGRAM_SIZE, RANGES) cv2.normalize(source_hist, source_hist) cv2.normalize(capture_hist, capture_hist) return 1 - cv2.compareHist(source_hist, capture_hist, cv2.HISTCMP_BHATTACHARYYA) -def compare_l2_norm(source, capture) -> float: - """ - Compares two images by calculating the L2 Error (square-root - of sum of squared error) - @param source: Image of any given shape - @param capture: Image matching the dimensions of the source - @return: The similarity between the images as a number 0 to 1. +def compare_l2_norm(source: cv2.Mat, capture: cv2.Mat, mask: cv2.Mat | None = None): """ - error = cv2.norm(source, capture, cv2.NORM_L2) - - # The L2 Error is summed across all pixels, so this normalizes - max_error = (source.size ** 0.5) * 255 - - return 1 - (error / max_error) - -def compare_l2_norm_masked(source, capture, mask) -> float: - """ - Compares two images by calculating the L2 Error (square-root - of sum of squared error) - + Compares two images by calculating the L2 Error (square-root of sum of squared error) @param source: Image of any given shape @param capture: Image matching the dimensions of the source @param mask: An image matching the dimensions of the source, but 1 channel grayscale @return: The similarity between the images as a number 0 to 1. """ - error = cv2.norm(source, capture, cv2.NORM_L2, mask) # The L2 Error is summed across all pixels, so this normalizes - max_error = (3 * np.count_nonzero(mask) * 255 * 255) ** 0.5 + max_error = sqrt(source.size) * MAXBYTE \ + if not is_valid_image(mask)\ + else sqrt(cv2.countNonZero(mask) * MASK_SIZE_MULTIPLIER) + if not max_error: + return 0.0 return 1 - (error / max_error) -def compare_template(source, capture) -> float: + +def compare_template(source: cv2.Mat, capture: cv2.Mat, mask: cv2.Mat | None = None): """ - Checks if the source is located within the capture by using - the sum of square differences. + Checks if the source is located within the capture by using the sum of square differences. + The mask is used to search for non-rectangular images within the capture. @param source: The subsection being searched for within the capture @param capture: Capture of an image larger than the source + @param mask: The mask of the source with the same dimensions @return: The best similarity for a region found in the image. This is represented as a number from 0 to 1. """ - - result = cv2.matchTemplate(capture, source, cv2.TM_SQDIFF) - min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) + result = cv2.matchTemplate(capture, source, cv2.TM_SQDIFF, mask=mask) + min_val, *_ = cv2.minMaxLoc(result) # matchTemplate returns the sum of square differences, this is the max # that the value can be. Used for normalizing from 0 to 1. - max_error = source.size * 255 * 255 + max_error = source.size * MAXBYTE * MAXBYTE \ + if not is_valid_image(mask) \ + else cv2.countNonZero(mask) return 1 - (min_val / max_error) -def compare_template_masked(source, capture, mask) -> float: - """ - Checks if the source is located within the capture by using - the sum of square differences. The mask is used to search for - non-rectangular images within the capture - - @param source: The subsection being searched for within the capture - @param capture: Capture of an image larger than the source - @param mask: The mask of the source with the same dimensions - @return: The best similarity for a region found in the image. This is - represented as a number from 0 to 1. - """ - - result = cv2.matchTemplate(capture, source, cv2.TM_SQDIFF, None, mask) - min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) - - return 1 - (min_val/np.count_nonzero(mask)) - -def compare_phash(source, capture): - """ - Compares the pHash of the two given images and returns the similarity between - the two. - @param source: Image of any given shape as a numpy array - @param capture: Image of any given shape as a numpy array - @return: The similarity between the hashes of the image as a number 0 to 1. +def compare_phash(source: cv2.Mat, capture: cv2.Mat, mask: cv2.Mat | None = None): """ - - source = Image.fromarray(source) - capture = Image.fromarray(capture) - - source_hash = imagehash.phash(source) - capture_hash = imagehash.phash(capture) - - return 1 - ((source_hash - capture_hash) / 64.0) - -def compare_phash_masked(source, capture, mask): - """ - Compares the pHash of the two given images and returns the similarity between - the two. + Compares the Perceptual Hash of the two given images and returns the similarity between the two. @param source: Image of any given shape as a numpy array @param capture: Image of any given shape as a numpy array @param mask: An image matching the dimensions of the source, but 1 channel grayscale @return: The similarity between the hashes of the image as a number 0 to 1. """ - # Since imagehash doesn't have any masking itself, bitwise_and will allow us # to apply the mask to the source and capture before calculating the pHash for # each of the images. As a result of this, this function is not going to be very # helpful for large masks as the images when shrinked down to 8x8 will mostly be # the same - source = cv2.bitwise_and(source, source, mask=mask) - capture = cv2.bitwise_and(capture, capture, mask=mask) + if is_valid_image(mask): + source = cv2.bitwise_and(source, source, mask=mask) + capture = cv2.bitwise_and(capture, capture, mask=mask) - source = Image.fromarray(source) - capture = Image.fromarray(capture) + source_hash = imagehash.phash(Image.fromarray(source)) + capture_hash = imagehash.phash(Image.fromarray(capture)) + hash_diff = source_hash - capture_hash + if not hash_diff: + return 0.0 + return 1 - (hash_diff / 64.0) - source_hash = imagehash.phash(source) - capture_hash = imagehash.phash(capture) - return 1 - ((source_hash - capture_hash) / 64.0) +def check_if_image_has_transparency(image: cv2.Mat): + # Check if there's a transparency channel (4th channel) and if at least one pixel is transparent (< 255) + if image.shape[2] != RGBA_CHANNEL_COUNT: + return False + mean: float = image[:, :, ColorChannel.Alpha].mean() + if mean == 0: + # Non-transparent images code path is usually faster and simpler, so let's return that + return False + # TODO error message if all pixels are transparent + # (the image appears as all black in windows, so it's not obvious for the user what they did wrong) + return mean != MAXBYTE -def checkIfImageHasTransparency(image): - # TODO check for first transparent pixel, no need to iterate through the whole image - # Check if there's a transparency channel (4th channel) and if at least one pixel is transparent (< 255) - return image.shape[2] == 4 and np.mean(image[:, :, 3]) != 255 + +COMPARE_METHODS_BY_INDEX = {0: compare_l2_norm, 1: compare_histograms, 2: compare_phash} diff --git a/src/design.py b/src/design.py deleted file mode 100644 index 28f0990c..00000000 --- a/src/design.py +++ /dev/null @@ -1,502 +0,0 @@ -# Form implementation generated from reading ui file '.\res\design.ui' -# -# Created by: PyQt6 UI code generator 6.1.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_MainWindow(object): - def setupUi(self, MainWindow): - MainWindow.setObjectName("MainWindow") - MainWindow.resize(632, 490) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) - MainWindow.setSizePolicy(sizePolicy) - MainWindow.setMinimumSize(QtCore.QSize(632, 490)) - MainWindow.setMaximumSize(QtCore.QSize(632, 490)) - font = QtGui.QFont() - font.setPointSize(9) - MainWindow.setFont(font) - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(":/resources/icon.ico"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) - MainWindow.setWindowIcon(icon) - MainWindow.setWhatsThis("") - MainWindow.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.centralwidget = QtWidgets.QWidget(MainWindow) - self.centralwidget.setObjectName("centralwidget") - self.splitimagefolderLabel = QtWidgets.QLabel(self.centralwidget) - self.splitimagefolderLabel.setGeometry(QtCore.QRect(20, 12, 98, 16)) - self.splitimagefolderLabel.setObjectName("splitimagefolderLabel") - self.splitimagefolderLineEdit = QtWidgets.QLineEdit(self.centralwidget) - self.splitimagefolderLineEdit.setGeometry(QtCore.QRect(130, 10, 412, 22)) - self.splitimagefolderLineEdit.setReadOnly(True) - self.splitimagefolderLineEdit.setObjectName("splitimagefolderLineEdit") - self.browseButton = QtWidgets.QPushButton(self.centralwidget) - self.browseButton.setGeometry(QtCore.QRect(540, 9, 75, 24)) - self.browseButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.browseButton.setObjectName("browseButton") - self.xLabel = QtWidgets.QLabel(self.centralwidget) - self.xLabel.setGeometry(QtCore.QRect(25, 145, 7, 16)) - self.xLabel.setObjectName("xLabel") - self.liveimageCheckBox = QtWidgets.QCheckBox(self.centralwidget) - self.liveimageCheckBox.setEnabled(True) - self.liveimageCheckBox.setGeometry(QtCore.QRect(120, 252, 129, 20)) - self.liveimageCheckBox.setChecked(True) - self.liveimageCheckBox.setTristate(False) - self.liveimageCheckBox.setObjectName("liveimageCheckBox") - self.selectregionButton = QtWidgets.QPushButton(self.centralwidget) - self.selectregionButton.setGeometry(QtCore.QRect(5, 70, 101, 23)) - self.selectregionButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.selectregionButton.setObjectName("selectregionButton") - self.similaritythresholdLabel = QtWidgets.QLabel(self.centralwidget) - self.similaritythresholdLabel.setGeometry(QtCore.QRect(7, 410, 151, 16)) - self.similaritythresholdLabel.setObjectName("similaritythresholdLabel") - self.similaritythresholdDoubleSpinBox = QtWidgets.QDoubleSpinBox(self.centralwidget) - self.similaritythresholdDoubleSpinBox.setGeometry(QtCore.QRect(155, 408, 61, 22)) - self.similaritythresholdDoubleSpinBox.setMaximum(1.0) - self.similaritythresholdDoubleSpinBox.setSingleStep(0.01) - self.similaritythresholdDoubleSpinBox.setProperty("value", 0.9) - self.similaritythresholdDoubleSpinBox.setObjectName("similaritythresholdDoubleSpinBox") - self.startautosplitterButton = QtWidgets.QPushButton(self.centralwidget) - self.startautosplitterButton.setGeometry(QtCore.QRect(500, 425, 121, 31)) - self.startautosplitterButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.startautosplitterButton.setObjectName("startautosplitterButton") - self.resetButton = QtWidgets.QPushButton(self.centralwidget) - self.resetButton.setGeometry(QtCore.QRect(500, 390, 121, 31)) - self.resetButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.resetButton.setObjectName("resetButton") - self.undosplitButton = QtWidgets.QPushButton(self.centralwidget) - self.undosplitButton.setGeometry(QtCore.QRect(494, 250, 64, 24)) - self.undosplitButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.undosplitButton.setObjectName("undosplitButton") - self.skipsplitButton = QtWidgets.QPushButton(self.centralwidget) - self.skipsplitButton.setGeometry(QtCore.QRect(560, 250, 61, 24)) - self.skipsplitButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.skipsplitButton.setObjectName("skipsplitButton") - self.pauseLabel = QtWidgets.QLabel(self.centralwidget) - self.pauseLabel.setGeometry(QtCore.QRect(7, 439, 131, 16)) - self.pauseLabel.setObjectName("pauseLabel") - self.checkfpsButton = QtWidgets.QPushButton(self.centralwidget) - self.checkfpsButton.setGeometry(QtCore.QRect(5, 225, 53, 21)) - self.checkfpsButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.checkfpsButton.setObjectName("checkfpsButton") - self.fpsLabel = QtWidgets.QLabel(self.centralwidget) - self.fpsLabel.setGeometry(QtCore.QRect(87, 225, 20, 20)) - self.fpsLabel.setObjectName("fpsLabel") - self.showlivesimilarityCheckBox = QtWidgets.QCheckBox(self.centralwidget) - self.showlivesimilarityCheckBox.setEnabled(True) - self.showlivesimilarityCheckBox.setGeometry(QtCore.QRect(7, 323, 124, 20)) - self.showlivesimilarityCheckBox.setChecked(True) - self.showlivesimilarityCheckBox.setTristate(False) - self.showlivesimilarityCheckBox.setObjectName("showlivesimilarityCheckBox") - self.showhighestsimilarityCheckBox = QtWidgets.QCheckBox(self.centralwidget) - self.showhighestsimilarityCheckBox.setEnabled(True) - self.showhighestsimilarityCheckBox.setGeometry(QtCore.QRect(7, 350, 145, 20)) - self.showhighestsimilarityCheckBox.setChecked(True) - self.showhighestsimilarityCheckBox.setTristate(False) - self.showhighestsimilarityCheckBox.setObjectName("showhighestsimilarityCheckBox") - self.livesimilarityLabel = QtWidgets.QLabel(self.centralwidget) - self.livesimilarityLabel.setGeometry(QtCore.QRect(171, 326, 46, 16)) - self.livesimilarityLabel.setText("") - self.livesimilarityLabel.setObjectName("livesimilarityLabel") - self.highestsimilarityLabel = QtWidgets.QLabel(self.centralwidget) - self.highestsimilarityLabel.setGeometry(QtCore.QRect(171, 352, 46, 16)) - self.highestsimilarityLabel.setText("") - self.highestsimilarityLabel.setObjectName("highestsimilarityLabel") - self.splitLabel = QtWidgets.QLabel(self.centralwidget) - self.splitLabel.setGeometry(QtCore.QRect(230, 317, 58, 16)) - self.splitLabel.setObjectName("splitLabel") - self.resetLabel = QtWidgets.QLabel(self.centralwidget) - self.resetLabel.setGeometry(QtCore.QRect(230, 341, 28, 16)) - self.resetLabel.setObjectName("resetLabel") - self.skiptsplitLabel = QtWidgets.QLabel(self.centralwidget) - self.skiptsplitLabel.setGeometry(QtCore.QRect(230, 367, 48, 16)) - self.skiptsplitLabel.setObjectName("skiptsplitLabel") - self.undosplitLabel = QtWidgets.QLabel(self.centralwidget) - self.undosplitLabel.setGeometry(QtCore.QRect(230, 393, 55, 16)) - self.undosplitLabel.setObjectName("undosplitLabel") - self.splitLineEdit = QtWidgets.QLineEdit(self.centralwidget) - self.splitLineEdit.setGeometry(QtCore.QRect(300, 314, 81, 20)) - self.splitLineEdit.setReadOnly(True) - self.splitLineEdit.setObjectName("splitLineEdit") - self.undosplitLineEdit = QtWidgets.QLineEdit(self.centralwidget) - self.undosplitLineEdit.setGeometry(QtCore.QRect(300, 391, 81, 20)) - self.undosplitLineEdit.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus) - self.undosplitLineEdit.setReadOnly(True) - self.undosplitLineEdit.setObjectName("undosplitLineEdit") - self.skipsplitLineEdit = QtWidgets.QLineEdit(self.centralwidget) - self.skipsplitLineEdit.setGeometry(QtCore.QRect(300, 365, 81, 20)) - self.skipsplitLineEdit.setReadOnly(True) - self.skipsplitLineEdit.setObjectName("skipsplitLineEdit") - self.resetLineEdit = QtWidgets.QLineEdit(self.centralwidget) - self.resetLineEdit.setGeometry(QtCore.QRect(300, 339, 81, 20)) - self.resetLineEdit.setReadOnly(True) - self.resetLineEdit.setObjectName("resetLineEdit") - self.setsplithotkeyButton = QtWidgets.QPushButton(self.centralwidget) - self.setsplithotkeyButton.setGeometry(QtCore.QRect(390, 314, 81, 21)) - self.setsplithotkeyButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.setsplithotkeyButton.setObjectName("setsplithotkeyButton") - self.setresethotkeyButton = QtWidgets.QPushButton(self.centralwidget) - self.setresethotkeyButton.setGeometry(QtCore.QRect(390, 339, 81, 21)) - self.setresethotkeyButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.setresethotkeyButton.setObjectName("setresethotkeyButton") - self.setskipsplithotkeyButton = QtWidgets.QPushButton(self.centralwidget) - self.setskipsplithotkeyButton.setGeometry(QtCore.QRect(390, 365, 81, 21)) - self.setskipsplithotkeyButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.setskipsplithotkeyButton.setObjectName("setskipsplithotkeyButton") - self.setundosplithotkeyButton = QtWidgets.QPushButton(self.centralwidget) - self.setundosplithotkeyButton.setGeometry(QtCore.QRect(390, 391, 81, 21)) - self.setundosplithotkeyButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.setundosplithotkeyButton.setObjectName("setundosplithotkeyButton") - self.line_left = QtWidgets.QFrame(self.centralwidget) - self.line_left.setGeometry(QtCore.QRect(220, 296, 2, 163)) - self.line_left.setFrameShadow(QtWidgets.QFrame.Shadow.Plain) - self.line_left.setLineWidth(1) - self.line_left.setFrameShape(QtWidgets.QFrame.Shape.VLine) - self.line_left.setObjectName("line_left") - self.timerglobalhotkeysLabel = QtWidgets.QLabel(self.centralwidget) - self.timerglobalhotkeysLabel.setGeometry(QtCore.QRect(230, 291, 251, 20)) - self.timerglobalhotkeysLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.timerglobalhotkeysLabel.setObjectName("timerglobalhotkeysLabel") - self.line_right = QtWidgets.QFrame(self.centralwidget) - self.line_right.setGeometry(QtCore.QRect(490, 296, 2, 163)) - self.line_right.setFrameShadow(QtWidgets.QFrame.Shadow.Plain) - self.line_right.setLineWidth(1) - self.line_right.setFrameShape(QtWidgets.QFrame.Shape.VLine) - self.line_right.setObjectName("line_right") - self.liveImage = QtWidgets.QLabel(self.centralwidget) - self.liveImage.setGeometry(QtCore.QRect(120, 70, 240, 180)) - self.liveImage.setFrameShape(QtWidgets.QFrame.Shape.Box) - self.liveImage.setText("") - self.liveImage.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.liveImage.setObjectName("liveImage") - self.currentSplitImage = QtWidgets.QLabel(self.centralwidget) - self.currentSplitImage.setGeometry(QtCore.QRect(380, 70, 240, 180)) - self.currentSplitImage.setFrameShape(QtWidgets.QFrame.Shape.Box) - self.currentSplitImage.setText("") - self.currentSplitImage.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.currentSplitImage.setObjectName("currentSplitImage") - self.currentsplitimageLabel = QtWidgets.QLabel(self.centralwidget) - self.currentsplitimageLabel.setGeometry(QtCore.QRect(450, 50, 102, 16)) - self.currentsplitimageLabel.setObjectName("currentsplitimageLabel") - self.widthLabel = QtWidgets.QLabel(self.centralwidget) - self.widthLabel.setGeometry(QtCore.QRect(12, 185, 33, 16)) - self.widthLabel.setObjectName("widthLabel") - self.heightLabel = QtWidgets.QLabel(self.centralwidget) - self.heightLabel.setGeometry(QtCore.QRect(66, 185, 41, 16)) - self.heightLabel.setObjectName("heightLabel") - self.fpsvalueLabel = QtWidgets.QLabel(self.centralwidget) - self.fpsvalueLabel.setGeometry(QtCore.QRect(58, 225, 26, 20)) - self.fpsvalueLabel.setText("") - self.fpsvalueLabel.setObjectName("fpsvalueLabel") - self.widthSpinBox = QtWidgets.QSpinBox(self.centralwidget) - self.widthSpinBox.setGeometry(QtCore.QRect(6, 200, 44, 22)) - self.widthSpinBox.setMinimum(1) - self.widthSpinBox.setMaximum(10000) - self.widthSpinBox.setProperty("value", 640) - self.widthSpinBox.setObjectName("widthSpinBox") - self.heightSpinBox = QtWidgets.QSpinBox(self.centralwidget) - self.heightSpinBox.setGeometry(QtCore.QRect(62, 200, 44, 22)) - self.heightSpinBox.setMinimum(1) - self.heightSpinBox.setMaximum(10000) - self.heightSpinBox.setProperty("value", 480) - self.heightSpinBox.setObjectName("heightSpinBox") - self.captureregionLabel = QtWidgets.QLabel(self.centralwidget) - self.captureregionLabel.setGeometry(QtCore.QRect(200, 50, 82, 16)) - self.captureregionLabel.setObjectName("captureregionLabel") - self.fpslimitLabel = QtWidgets.QLabel(self.centralwidget) - self.fpslimitLabel.setGeometry(QtCore.QRect(8, 252, 51, 16)) - self.fpslimitLabel.setObjectName("fpslimitLabel") - self.fpslimitSpinBox = QtWidgets.QDoubleSpinBox(self.centralwidget) - self.fpslimitSpinBox.setGeometry(QtCore.QRect(62, 250, 44, 22)) - self.fpslimitSpinBox.setPrefix("") - self.fpslimitSpinBox.setDecimals(0) - self.fpslimitSpinBox.setMinimum(30.0) - self.fpslimitSpinBox.setMaximum(5000.0) - self.fpslimitSpinBox.setSingleStep(1.0) - self.fpslimitSpinBox.setProperty("value", 60.0) - self.fpslimitSpinBox.setObjectName("fpslimitSpinBox") - self.currentsplitimagefileLabel = QtWidgets.QLabel(self.centralwidget) - self.currentsplitimagefileLabel.setGeometry(QtCore.QRect(380, 270, 241, 20)) - self.currentsplitimagefileLabel.setText("") - self.currentsplitimagefileLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.currentsplitimagefileLabel.setObjectName("currentsplitimagefileLabel") - self.takescreenshotButton = QtWidgets.QPushButton(self.centralwidget) - self.takescreenshotButton.setGeometry(QtCore.QRect(260, 250, 101, 24)) - self.takescreenshotButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.takescreenshotButton.setObjectName("takescreenshotButton") - self.xSpinBox = QtWidgets.QSpinBox(self.centralwidget) - self.xSpinBox.setGeometry(QtCore.QRect(6, 160, 44, 22)) - self.xSpinBox.setReadOnly(False) - self.xSpinBox.setButtonSymbols(QtWidgets.QAbstractSpinBox.ButtonSymbols.UpDownArrows) - self.xSpinBox.setMinimum(0) - self.xSpinBox.setMaximum(999999999) - self.xSpinBox.setSingleStep(1) - self.xSpinBox.setProperty("value", 0) - self.xSpinBox.setObjectName("xSpinBox") - self.ySpinBox = QtWidgets.QSpinBox(self.centralwidget) - self.ySpinBox.setGeometry(QtCore.QRect(62, 160, 44, 22)) - self.ySpinBox.setReadOnly(False) - self.ySpinBox.setButtonSymbols(QtWidgets.QAbstractSpinBox.ButtonSymbols.UpDownArrows) - self.ySpinBox.setMinimum(0) - self.ySpinBox.setMaximum(999999999) - self.ySpinBox.setProperty("value", 0) - self.ySpinBox.setObjectName("ySpinBox") - self.yLabel = QtWidgets.QLabel(self.centralwidget) - self.yLabel.setGeometry(QtCore.QRect(81, 145, 7, 16)) - self.yLabel.setObjectName("yLabel") - self.comparisonmethodComboBox = QtWidgets.QComboBox(self.centralwidget) - self.comparisonmethodComboBox.setGeometry(QtCore.QRect(125, 296, 91, 22)) - self.comparisonmethodComboBox.setObjectName("comparisonmethodComboBox") - self.comparisonmethodComboBox.addItem("") - self.comparisonmethodComboBox.addItem("") - self.comparisonmethodComboBox.addItem("") - self.pauseDoubleSpinBox = QtWidgets.QDoubleSpinBox(self.centralwidget) - self.pauseDoubleSpinBox.setGeometry(QtCore.QRect(155, 437, 61, 22)) - self.pauseDoubleSpinBox.setMaximum(999999999.0) - self.pauseDoubleSpinBox.setSingleStep(1.0) - self.pauseDoubleSpinBox.setProperty("value", 10.0) - self.pauseDoubleSpinBox.setObjectName("pauseDoubleSpinBox") - self.comparisonmethodLabel = QtWidgets.QLabel(self.centralwidget) - self.comparisonmethodLabel.setGeometry(QtCore.QRect(7, 298, 110, 16)) - self.comparisonmethodLabel.setObjectName("comparisonmethodLabel") - self.alignregionButton = QtWidgets.QPushButton(self.centralwidget) - self.alignregionButton.setGeometry(QtCore.QRect(5, 95, 101, 23)) - self.alignregionButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.alignregionButton.setObjectName("alignregionButton") - self.groupDummySplitsCheckBox = QtWidgets.QCheckBox(self.centralwidget) - self.groupDummySplitsCheckBox.setGeometry(QtCore.QRect(230, 442, 261, 20)) - self.groupDummySplitsCheckBox.setChecked(False) - self.groupDummySplitsCheckBox.setObjectName("groupDummySplitsCheckBox") - self.selectwindowButton = QtWidgets.QPushButton(self.centralwidget) - self.selectwindowButton.setGeometry(QtCore.QRect(5, 120, 101, 23)) - self.selectwindowButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.selectwindowButton.setObjectName("selectwindowButton") - self.imageloopLabel = QtWidgets.QLabel(self.centralwidget) - self.imageloopLabel.setGeometry(QtCore.QRect(379, 252, 113, 20)) - self.imageloopLabel.setObjectName("imageloopLabel") - self.pausehotkeyLabel = QtWidgets.QLabel(self.centralwidget) - self.pausehotkeyLabel.setGeometry(QtCore.QRect(230, 418, 31, 16)) - self.pausehotkeyLabel.setObjectName("pausehotkeyLabel") - self.pausehotkeyLineEdit = QtWidgets.QLineEdit(self.centralwidget) - self.pausehotkeyLineEdit.setGeometry(QtCore.QRect(300, 416, 81, 20)) - self.pausehotkeyLineEdit.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus) - self.pausehotkeyLineEdit.setReadOnly(True) - self.pausehotkeyLineEdit.setObjectName("pausehotkeyLineEdit") - self.setpausehotkeyButton = QtWidgets.QPushButton(self.centralwidget) - self.setpausehotkeyButton.setGeometry(QtCore.QRect(390, 416, 81, 21)) - self.setpausehotkeyButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.setpausehotkeyButton.setObjectName("setpausehotkeyButton") - self.loopCheckBox = QtWidgets.QCheckBox(self.centralwidget) - self.loopCheckBox.setEnabled(True) - self.loopCheckBox.setGeometry(QtCore.QRect(500, 340, 117, 20)) - self.loopCheckBox.setChecked(False) - self.loopCheckBox.setTristate(False) - self.loopCheckBox.setObjectName("loopCheckBox") - self.autostartonresetCheckBox = QtWidgets.QCheckBox(self.centralwidget) - self.autostartonresetCheckBox.setGeometry(QtCore.QRect(500, 360, 126, 20)) - self.autostartonresetCheckBox.setChecked(False) - self.autostartonresetCheckBox.setTristate(False) - self.autostartonresetCheckBox.setObjectName("autostartonresetCheckBox") - self.startImageReloadButton = QtWidgets.QPushButton(self.centralwidget) - self.startImageReloadButton.setGeometry(QtCore.QRect(500, 302, 121, 31)) - self.startImageReloadButton.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.startImageReloadButton.setObjectName("startImageReloadButton") - self.startImageLabel = QtWidgets.QLabel(self.centralwidget) - self.startImageLabel.setGeometry(QtCore.QRect(120, 270, 241, 16)) - self.startImageLabel.setObjectName("startImageLabel") - self.currentsimilaritythresholdLabel = QtWidgets.QLabel(self.centralwidget) - self.currentsimilaritythresholdLabel.setGeometry(QtCore.QRect(7, 380, 151, 16)) - self.currentsimilaritythresholdLabel.setObjectName("currentsimilaritythresholdLabel") - self.currentsimilaritythresholdnumberLabel = QtWidgets.QLabel(self.centralwidget) - self.currentsimilaritythresholdnumberLabel.setGeometry(QtCore.QRect(171, 380, 46, 16)) - self.currentsimilaritythresholdnumberLabel.setText("") - self.currentsimilaritythresholdnumberLabel.setObjectName("currentsimilaritythresholdnumberLabel") - self.splitimagefolderLabel.raise_() - self.splitimagefolderLineEdit.raise_() - self.browseButton.raise_() - self.xLabel.raise_() - self.liveimageCheckBox.raise_() - self.selectregionButton.raise_() - self.similaritythresholdLabel.raise_() - self.similaritythresholdDoubleSpinBox.raise_() - self.startautosplitterButton.raise_() - self.resetButton.raise_() - self.undosplitButton.raise_() - self.skipsplitButton.raise_() - self.pauseLabel.raise_() - self.checkfpsButton.raise_() - self.fpsLabel.raise_() - self.showlivesimilarityCheckBox.raise_() - self.showhighestsimilarityCheckBox.raise_() - self.livesimilarityLabel.raise_() - self.highestsimilarityLabel.raise_() - self.splitLabel.raise_() - self.resetLabel.raise_() - self.skiptsplitLabel.raise_() - self.undosplitLabel.raise_() - self.splitLineEdit.raise_() - self.undosplitLineEdit.raise_() - self.skipsplitLineEdit.raise_() - self.resetLineEdit.raise_() - self.setsplithotkeyButton.raise_() - self.setresethotkeyButton.raise_() - self.setskipsplithotkeyButton.raise_() - self.setundosplithotkeyButton.raise_() - self.line_left.raise_() - self.timerglobalhotkeysLabel.raise_() - self.line_right.raise_() - self.currentsplitimageLabel.raise_() - self.liveImage.raise_() - self.currentSplitImage.raise_() - self.widthLabel.raise_() - self.heightLabel.raise_() - self.fpsvalueLabel.raise_() - self.widthSpinBox.raise_() - self.heightSpinBox.raise_() - self.captureregionLabel.raise_() - self.fpslimitLabel.raise_() - self.fpslimitSpinBox.raise_() - self.currentsplitimagefileLabel.raise_() - self.takescreenshotButton.raise_() - self.xSpinBox.raise_() - self.ySpinBox.raise_() - self.yLabel.raise_() - self.comparisonmethodComboBox.raise_() - self.pauseDoubleSpinBox.raise_() - self.comparisonmethodLabel.raise_() - self.alignregionButton.raise_() - self.groupDummySplitsCheckBox.raise_() - self.selectwindowButton.raise_() - self.imageloopLabel.raise_() - self.pausehotkeyLabel.raise_() - self.pausehotkeyLineEdit.raise_() - self.setpausehotkeyButton.raise_() - self.loopCheckBox.raise_() - self.autostartonresetCheckBox.raise_() - self.startImageReloadButton.raise_() - self.startImageLabel.raise_() - self.currentsimilaritythresholdLabel.raise_() - self.currentsimilaritythresholdnumberLabel.raise_() - MainWindow.setCentralWidget(self.centralwidget) - self.menuBar = QtWidgets.QMenuBar(MainWindow) - self.menuBar.setGeometry(QtCore.QRect(0, 0, 632, 22)) - self.menuBar.setObjectName("menuBar") - self.menuHelp = QtWidgets.QMenu(self.menuBar) - self.menuHelp.setObjectName("menuHelp") - self.menuFile = QtWidgets.QMenu(self.menuBar) - self.menuFile.setObjectName("menuFile") - MainWindow.setMenuBar(self.menuBar) - self.actionView_Help = QtGui.QAction(MainWindow) - self.actionView_Help.setObjectName("actionView_Help") - self.actionAbout = QtGui.QAction(MainWindow) - self.actionAbout.setObjectName("actionAbout") - self.actionSplit_Settings = QtGui.QAction(MainWindow) - self.actionSplit_Settings.setObjectName("actionSplit_Settings") - self.actionSave_Settings = QtGui.QAction(MainWindow) - self.actionSave_Settings.setObjectName("actionSave_Settings") - self.actionLoad_Settings = QtGui.QAction(MainWindow) - self.actionLoad_Settings.setObjectName("actionLoad_Settings") - self.actionSave_Settings_As = QtGui.QAction(MainWindow) - self.actionSave_Settings_As.setObjectName("actionSave_Settings_As") - self.actionCheck_for_Updates = QtGui.QAction(MainWindow) - self.actionCheck_for_Updates.setObjectName("actionCheck_for_Updates") - self.actionCheck_for_Updates_on_Open = QtGui.QAction(MainWindow) - self.actionCheck_for_Updates_on_Open.setCheckable(True) - self.actionCheck_for_Updates_on_Open.setChecked(True) - self.actionCheck_for_Updates_on_Open.setEnabled(True) - self.actionCheck_for_Updates_on_Open.setObjectName("actionCheck_for_Updates_on_Open") - self.menuHelp.addAction(self.actionView_Help) - self.menuHelp.addAction(self.actionAbout) - self.menuHelp.addAction(self.actionCheck_for_Updates) - self.menuHelp.addAction(self.actionCheck_for_Updates_on_Open) - self.menuFile.addAction(self.actionSave_Settings) - self.menuFile.addAction(self.actionSave_Settings_As) - self.menuFile.addAction(self.actionLoad_Settings) - self.menuBar.addAction(self.menuFile.menuAction()) - self.menuBar.addAction(self.menuHelp.menuAction()) - - self.retranslateUi(MainWindow) - QtCore.QMetaObject.connectSlotsByName(MainWindow) - MainWindow.setTabOrder(self.splitimagefolderLineEdit, self.xSpinBox) - MainWindow.setTabOrder(self.xSpinBox, self.ySpinBox) - MainWindow.setTabOrder(self.ySpinBox, self.widthSpinBox) - MainWindow.setTabOrder(self.widthSpinBox, self.heightSpinBox) - MainWindow.setTabOrder(self.heightSpinBox, self.fpslimitSpinBox) - MainWindow.setTabOrder(self.fpslimitSpinBox, self.liveimageCheckBox) - MainWindow.setTabOrder(self.liveimageCheckBox, self.comparisonmethodComboBox) - MainWindow.setTabOrder(self.comparisonmethodComboBox, self.showlivesimilarityCheckBox) - MainWindow.setTabOrder(self.showlivesimilarityCheckBox, self.showhighestsimilarityCheckBox) - MainWindow.setTabOrder(self.showhighestsimilarityCheckBox, self.similaritythresholdDoubleSpinBox) - MainWindow.setTabOrder(self.similaritythresholdDoubleSpinBox, self.pauseDoubleSpinBox) - MainWindow.setTabOrder(self.pauseDoubleSpinBox, self.splitLineEdit) - MainWindow.setTabOrder(self.splitLineEdit, self.resetLineEdit) - MainWindow.setTabOrder(self.resetLineEdit, self.skipsplitLineEdit) - MainWindow.setTabOrder(self.skipsplitLineEdit, self.undosplitLineEdit) - MainWindow.setTabOrder(self.undosplitLineEdit, self.groupDummySplitsCheckBox) - - def retranslateUi(self, MainWindow): - _translate = QtCore.QCoreApplication.translate - MainWindow.setWindowTitle(_translate("MainWindow", "AutoSplit")) - self.splitimagefolderLabel.setText(_translate("MainWindow", "Split Image Folder:")) - self.browseButton.setText(_translate("MainWindow", "Browse...")) - self.xLabel.setText(_translate("MainWindow", "X")) - self.liveimageCheckBox.setText(_translate("MainWindow", "Live Capture Region")) - self.selectregionButton.setText(_translate("MainWindow", "Select Region")) - self.similaritythresholdLabel.setText(_translate("MainWindow", "Default similarity threshold:")) - self.startautosplitterButton.setText(_translate("MainWindow", "Start Auto Splitter")) - self.resetButton.setText(_translate("MainWindow", "Reset")) - self.undosplitButton.setText(_translate("MainWindow", "Undo Split")) - self.skipsplitButton.setText(_translate("MainWindow", "Skip Split")) - self.pauseLabel.setText(_translate("MainWindow", "Default pause time (sec):")) - self.checkfpsButton.setText(_translate("MainWindow", "Max FPS")) - self.fpsLabel.setText(_translate("MainWindow", "FPS")) - self.showlivesimilarityCheckBox.setText(_translate("MainWindow", "Show live similarity")) - self.showhighestsimilarityCheckBox.setText(_translate("MainWindow", "Show highest similarity")) - self.splitLabel.setText(_translate("MainWindow", "Start / Split")) - self.resetLabel.setText(_translate("MainWindow", "Reset")) - self.skiptsplitLabel.setText(_translate("MainWindow", "Skip Split")) - self.undosplitLabel.setText(_translate("MainWindow", "Undo Split")) - self.setsplithotkeyButton.setText(_translate("MainWindow", "Set Hotkey")) - self.setresethotkeyButton.setText(_translate("MainWindow", "Set Hotkey")) - self.setskipsplithotkeyButton.setText(_translate("MainWindow", "Set Hotkey")) - self.setundosplithotkeyButton.setText(_translate("MainWindow", "Set Hotkey")) - self.timerglobalhotkeysLabel.setText(_translate("MainWindow", "Timer Global Hotkeys")) - self.currentsplitimageLabel.setText(_translate("MainWindow", "Current Split Image")) - self.widthLabel.setText(_translate("MainWindow", "Width")) - self.heightLabel.setText(_translate("MainWindow", "Height")) - self.captureregionLabel.setText(_translate("MainWindow", "Capture Region")) - self.fpslimitLabel.setText(_translate("MainWindow", "FPS Limit:")) - self.takescreenshotButton.setText(_translate("MainWindow", "Take Screenshot")) - self.yLabel.setText(_translate("MainWindow", "Y")) - self.comparisonmethodComboBox.setItemText(0, _translate("MainWindow", "L2 Norm")) - self.comparisonmethodComboBox.setItemText(1, _translate("MainWindow", "Histograms")) - self.comparisonmethodComboBox.setItemText(2, _translate("MainWindow", "pHash")) - self.comparisonmethodLabel.setText(_translate("MainWindow", "Comparison Method")) - self.alignregionButton.setText(_translate("MainWindow", "Align Region")) - self.groupDummySplitsCheckBox.setText(_translate("MainWindow", "Group dummy splits when undoing/skipping")) - self.selectwindowButton.setText(_translate("MainWindow", "Select Window")) - self.imageloopLabel.setText(_translate("MainWindow", "Image Loop:")) - self.pausehotkeyLabel.setText(_translate("MainWindow", "Pause")) - self.setpausehotkeyButton.setText(_translate("MainWindow", "Set Hotkey")) - self.loopCheckBox.setText(_translate("MainWindow", "Loop Split Images")) - self.autostartonresetCheckBox.setText(_translate("MainWindow", "Auto Start On Reset")) - self.startImageReloadButton.setText(_translate("MainWindow", "Reload Start Image")) - self.startImageLabel.setText(_translate("MainWindow", "Start image:")) - self.currentsimilaritythresholdLabel.setText(_translate("MainWindow", "Current similarity threshold:")) - self.menuHelp.setTitle(_translate("MainWindow", "Help")) - self.menuFile.setTitle(_translate("MainWindow", "File")) - self.actionView_Help.setText(_translate("MainWindow", "View Help")) - self.actionAbout.setText(_translate("MainWindow", "About")) - self.actionSplit_Settings.setText(_translate("MainWindow", "Split Image Settings")) - self.actionSave_Settings.setText(_translate("MainWindow", "Save Settings")) - self.actionLoad_Settings.setText(_translate("MainWindow", "Load Settings")) - self.actionSave_Settings_As.setText(_translate("MainWindow", "Save Settings As...")) - self.actionCheck_for_Updates.setText(_translate("MainWindow", "Check for Updates...")) - self.actionCheck_for_Updates_on_Open.setText(_translate("MainWindow", "Check for Updates on Open")) diff --git a/src/error_messages.py b/src/error_messages.py index 1a0e40f1..63462140 100644 --- a/src/error_messages.py +++ b/src/error_messages.py @@ -1,80 +1,195 @@ -# Error messages -from PyQt6 import QtWidgets +"""Error messages.""" +from __future__ import annotations +import os +import signal +import sys +import traceback +from types import TracebackType +from typing import TYPE_CHECKING, NoReturn -def setTextMessage(message: str): - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle('Error') - msgBox.setText(message) - msgBox.exec() +from PySide6 import QtCore, QtWidgets +from utils import FROZEN, GITHUB_REPOSITORY -def splitImageDirectoryError(): - setTextMessage("No split image folder is selected.") +if TYPE_CHECKING: + from AutoSplit import AutoSplit -def splitImageDirectoryNotFoundError(): - setTextMessage("The Split Image Folder does not exist.") +def __exit_program(): + # stop main thread (which is probably blocked reading input) via an interrupt signal + os.kill(os.getpid(), signal.SIGINT) + sys.exit(1) -def splitImageDirectoryEmpty(): - setTextMessage("The Split Image Folder is empty.") +def set_text_message(message: str, details: str = "", kill_button: str = "", accept_button: str = ""): + message_box = QtWidgets.QMessageBox() + message_box.setWindowTitle("Error") + message_box.setTextFormat(QtCore.Qt.TextFormat.RichText) + message_box.setText(message) + # Button order is important for default focus + if accept_button: + message_box.addButton(accept_button, QtWidgets.QMessageBox.ButtonRole.AcceptRole) + if kill_button: + force_quit_button = message_box.addButton(kill_button, QtWidgets.QMessageBox.ButtonRole.ResetRole) + force_quit_button.clicked.connect(__exit_program) + if details: + message_box.setDetailedText(details) + # Preopen the details + for button in message_box.buttons(): + if message_box.buttonRole(button) == QtWidgets.QMessageBox.ButtonRole.ActionRole: + button.click() + break + message_box.exec() -def imageTypeError(image: str): - setTextMessage(f"\"{image}\" is not a valid image file, does not exist, or the full image file path contains a special character.") +def split_image_directory(): + set_text_message("No split image folder is selected.") -def regionError(): - setTextMessage("No region is selected or the Capture Region window is not open. Select a region or load settings while the Capture Region window is open.") +def split_image_directory_not_found(): + set_text_message("The Split Image Folder does not exist.") -def splitHotkeyError(): - setTextMessage("No split hotkey has been set.") +def split_image_directory_empty(): + set_text_message("The Split Image Folder is empty.") -def pauseHotkeyError(): - setTextMessage("Your split image folder contains an image filename with a pause flag {p}, but no pause hotkey is set.") +def image_type(image: str): + set_text_message( + f"{image!r} is not a valid image file, does not exist, " + + "or the full image file path contains a special character.", + ) -def alignRegionImageTypeError(): - setTextMessage("File not a valid image file") +def region(): + set_text_message( + "No region is selected or the Capture Region window is not open. " + + "Select a region or load settings while the Capture Region window is open.", + ) -def alignmentNotMatchedError(): - setTextMessage("No area in capture region matched reference image. Alignment failed.") +def split_hotkey(): + set_text_message("No split hotkey has been set.") -def noKeywordImageError(keyword: str): - setTextMessage(f"Your split image folder does not contain an image with the keyword \"{keyword}\".") +def pause_hotkey(): + set_text_message( + "Your split image folder contains an image filename with a pause flag {p}, " + + "but no pause hotkey is set.", + ) -def multipleKeywordImagesError(keyword: str): - setTextMessage(f"Only one image with the keyword \"{keyword}\" is allowed.") +def align_region_image_type(): + set_text_message("File not a valid image file") -def resetHotkeyError(): - setTextMessage("Your split image folder contains a reset image, but no reset hotkey is set.") +def alignment_not_matched(): + set_text_message("No area in capture region matched reference image. Alignment failed.") -def dummySplitsError(): - setTextMessage("Group dummy splits when undoing/skipping cannot be checked if any split image has a loop parameter greater than 1") +def no_keyword_image(keyword: str): + set_text_message(f"Your split image folder does not contain an image with the keyword {keyword!r}.") -def oldVersionSettingsFileError(): - setTextMessage("Old version settings file detected. This version allows settings files from v1.3 and above.") +def multiple_keyword_images(keyword: str): + set_text_message(f"Only one image with the keyword {keyword!r} is allowed.") -def invalidSettingsError(): - setTextMessage("Invalid settings file.") +def reset_hotkey(): + set_text_message("Your split image folder contains a reset image, but no reset hotkey is set.") -def noSettingsFileOnOpenError(): - setTextMessage("No settings file found. One can be loaded on open if placed in the same folder as AutoSplit.exe") +def old_version_settings_file(): + set_text_message( + "Old version settings file detected. This version allows settings files in .toml format. Starting from v2.0.", + ) -def tooManySettingsFilesOnOpenError(): - setTextMessage("Too many settings files found. Only one can be loaded on open if placed in the same folder as AutoSplit.exe") +def invalid_settings(): + set_text_message("Invalid settings file.") -def checkForUpdatesError(): - setTextMessage("An error occurred while attempting to check for updates. Please check your connection.") + +def invalid_hotkey(hotkey_name: str): + set_text_message(f"Invalid hotkey {hotkey_name!r}") + + +def no_settings_file_on_open(): + set_text_message( + "No settings file found. One can be loaded on open if placed in the same folder as the AutoSplit executable.", + ) + + +def too_many_settings_files_on_open(): + set_text_message( + "Too many settings files found. " + + "Only one can be loaded on open if placed in the same folder as the AutoSplit executable.", + ) + + +def check_for_updates(): + set_text_message("An error occurred while attempting to check for updates. Please check your connection.") + + +def load_start_image(): + set_text_message( + "Start Image found, but cannot be loaded unless Start, Reset, and Pause hotkeys are set. " + + "Please set these hotkeys, and then click the Reload Start Image button.", + ) + + +def stdin_lost(): + set_text_message("stdin not supported or lost, external control like LiveSplit integration will not work.") + + +def already_open(): + set_text_message( + "An instance of AutoSplit is already running.
Are you sure you want to open a another one?", + "", + "Don't open", + "Ignore", + ) + + +def exception_traceback(exception: BaseException, message: str = ""): + if not message: + message = "AutoSplit encountered an unhandled exception and will try to recover, " + \ + f"however, there is no guarantee it will keep working properly. {CREATE_NEW_ISSUE_MESSAGE}" + set_text_message( + message, + "\n".join(traceback.format_exception(None, exception, exception.__traceback__)), + "Close AutoSplit", + ) + + +CREATE_NEW_ISSUE_MESSAGE = ( + f"Please create a New Issue at " + + f"github.com/{GITHUB_REPOSITORY}/issues, describe what happened, " + + "and copy & paste the entire error message below" +) + + +def make_excepthook(autosplit: AutoSplit): + def excepthook(exception_type: type[BaseException], exception: BaseException, _traceback: TracebackType | None): + # Catch Keyboard Interrupts for a clean close + if exception_type is KeyboardInterrupt or isinstance(exception, KeyboardInterrupt): + sys.exit(0) + # HACK: Can happen when starting the region selector while capturing with WindowsGraphicsCapture + if ( + exception_type is SystemError + and str(exception) == " returned a result with an error set" + ): + return + # Whithin LiveSplit excepthook needs to use MainWindow's signals to show errors + autosplit.show_error_signal.emit(lambda: exception_traceback(exception)) + return excepthook + + +def handle_top_level_exceptions(exception: Exception) -> NoReturn: + message = f"AutoSplit encountered an unrecoverable exception and will likely now close. {CREATE_NEW_ISSUE_MESSAGE}" + # Print error to console if not running in executable + if FROZEN: + exception_traceback(exception, message) + else: + traceback.print_exception(type(exception), exception, exception.__traceback__) + sys.exit(1) diff --git a/src/hotkeys.py b/src/hotkeys.py index f722ea58..b0575467 100644 --- a/src/hotkeys.py +++ b/src/hotkeys.py @@ -1,307 +1,284 @@ from __future__ import annotations -from typing import Any, Callable, TYPE_CHECKING, Union -if TYPE_CHECKING: - from AutoSplit import AutoSplit + +from collections.abc import Callable +from typing import TYPE_CHECKING, Literal, cast import keyboard import pyautogui -import threading +from PySide6 import QtWidgets + +import error_messages +from utils import fire_and_forget, is_digit + +if TYPE_CHECKING: + from AutoSplit import AutoSplit + # While not usually recommended, we don't manipulate the mouse, and we don't want the extra delay pyautogui.FAILSAFE = False +SET_HOTKEY_TEXT = "Set Hotkey" +PRESS_A_KEY_TEXT = "Press a key..." + +Commands = Literal["split", "start", "pause", "reset", "skip", "undo"] +Hotkey = Literal["split", "reset", "skip_split", "undo_split", "pause", "toggle_auto_reset_image"] +HOTKEYS: list[Hotkey] = ["split", "reset", "skip_split", "undo_split", "pause", "toggle_auto_reset_image"] -# do all of these after you click "set hotkey" but before you type the hotkey. -def beforeSettingHotkey(self: AutoSplit): - self.startautosplitterButton.setEnabled(False) - self.setsplithotkeyButton.setEnabled(False) - self.setresethotkeyButton.setEnabled(False) - self.setskipsplithotkeyButton.setEnabled(False) - self.setundosplithotkeyButton.setEnabled(False) - self.setpausehotkeyButton.setEnabled(False) - - -# do all of these things after you set a hotkey. a signal connects to this because -# changing GUI stuff in the hotkey thread was causing problems -def afterSettingHotkey(self: AutoSplit): - self.setsplithotkeyButton.setText('Set Hotkey') - self.setresethotkeyButton.setText('Set Hotkey') - self.setskipsplithotkeyButton.setText('Set Hotkey') - self.setundosplithotkeyButton.setText('Set Hotkey') - self.setpausehotkeyButton.setText('Set Hotkey') - self.startautosplitterButton.setEnabled(True) - self.setsplithotkeyButton.setEnabled(True) - self.setresethotkeyButton.setEnabled(True) - self.setskipsplithotkeyButton.setEnabled(True) - self.setundosplithotkeyButton.setEnabled(True) - self.setpausehotkeyButton.setEnabled(True) - - -def is_digit(key: str): - try: - key_as_num = int(key) - return key_as_num >= 0 and key_as_num <= 9 - except Exception: - return False +def remove_all_hotkeys(): + keyboard.unhook_all() -def send_command(self: AutoSplit, command: str): - if self.is_auto_controlled: + +def before_setting_hotkey(autosplit: AutoSplit): + """Do all of these after you click "Set Hotkey" but before you type the hotkey.""" + autosplit.start_auto_splitter_button.setEnabled(False) + if autosplit.SettingsWidget: + for hotkey in HOTKEYS: + getattr(autosplit.SettingsWidget, f"set_{hotkey}_hotkey_button").setEnabled(False) + + +def after_setting_hotkey(autosplit: AutoSplit): + """ + Do all of these things after you set a hotkey. + A signal connects to this because changing GUI stuff is only possible in the main thread. + """ + if not autosplit.is_running: + autosplit.start_auto_splitter_button.setEnabled(True) + if autosplit.SettingsWidget: + for hotkey in HOTKEYS: + getattr(autosplit.SettingsWidget, f"set_{hotkey}_hotkey_button").setText(SET_HOTKEY_TEXT) + getattr(autosplit.SettingsWidget, f"set_{hotkey}_hotkey_button").setEnabled(True) + + +def send_command(autosplit: AutoSplit, command: Commands): + if autosplit.is_auto_controlled: print(command, flush=True) + elif command in {"split", "start"}: + _send_hotkey(autosplit.settings_dict["split_hotkey"]) + elif command == "pause": + _send_hotkey(autosplit.settings_dict["pause_hotkey"]) + elif command == "reset": + _send_hotkey(autosplit.settings_dict["reset_hotkey"]) + elif command == "skip": + _send_hotkey(autosplit.settings_dict["skip_split_hotkey"]) + elif command == "undo": + _send_hotkey(autosplit.settings_dict["undo_split_hotkey"]) + else: - if command == "split" or command == "start": - _send_hotkey(self.splitLineEdit.text()) - elif command == "pause": - _send_hotkey(self.pausehotkeyLineEdit.text()) - elif command == "reset": - _send_hotkey(self.resetLineEdit.text()) - else: - raise KeyError(f"'{command}' is not a valid LiveSplit.AutoSplitIntegration command") - - -# Supports sending the appropriate scan code for all the special cases -def _send_hotkey(key_or_scan_code: Union[int, str, Any]): - if not key_or_scan_code: + raise KeyError(f"{command!r} is not a valid LiveSplit.AutoSplitIntegration command") + + +def _unhook(hotkey_callback: Callable[[], None] | None): + try: + if hotkey_callback: + keyboard.unhook_key(hotkey_callback) + except (AttributeError, KeyError, ValueError): + pass + + +def _send_hotkey(hotkey_or_scan_code: int | str | None): + """Supports sending the appropriate scan code for all the special cases.""" + if not hotkey_or_scan_code: return - hotkey_type = type(key_or_scan_code) # Deal with regular inputs - if hotkey_type is int: - return keyboard.send(key_or_scan_code) - elif hotkey_type is not str: - raise TypeError(f'key_or_scan_code "{key_or_scan_code}" ({hotkey_type}) should be an int or str') - if (not (key_or_scan_code.startswith('num ') or key_or_scan_code == 'decimal')): - return keyboard.send(key_or_scan_code) - - # Deal with problematic keys. Even by sending specific scan code 'keyboard' still sends the default (wrong) key - # keyboard.send(keyboard.key_to_scan_codes(key_or_scan_code)[1]) - pyautogui.hotkey(key_or_scan_code.replace(' ', '')) - + # If an int or does not contain the following strings + if isinstance(hotkey_or_scan_code, int) \ + or not ("num " in hotkey_or_scan_code or "decimal" in hotkey_or_scan_code or "+" in hotkey_or_scan_code): + keyboard.send(hotkey_or_scan_code) + return -def __validate_keypad(expected_key: str, keyboard_event: keyboard.KeyboardEvent): + # FIXME: Localized keys won't work here + # Deal with problematic keys. Even by sending specific scan code "keyboard" still sends the default (wrong) key + # keyboard also has issues with capitalization modifier (shift+A) + # keyboard.send(keyboard.key_to_scan_codes(key_or_scan_code)[1]) + pyautogui.hotkey( + *[ + "+" if key == "plus" else key + for key + in hotkey_or_scan_code.replace(" ", "").split("+") + ], + ) + + +def __validate_keypad(expected_key: str, keyboard_event: keyboard.KeyboardEvent) -> bool: + """ + NOTE: This is a workaround very specific to numpads. + Windows reports different physical keys with the same scan code. + For example, "Home", "Num Home" and "Num 7" are all `71`. + See: https://github.com/boppreh/keyboard/issues/171#issuecomment-390437684 . + + Since we reuse the key string we set to send to LiveSplit, we can't use fake names like "num home". + We're also trying to achieve the same hotkey behaviour as LiveSplit has. + """ # Prevent "(keypad)delete", "(keypad)./decimal" and "del" from triggering each other # as well as "." and "(keypad)./decimal" - if keyboard_event.scan_code == 83 or keyboard_event.scan_code == 52: - if expected_key == keyboard_event.name: - return True - else: - # TODO: "del" won't work with "(keypad)delete" if localized in non-english (ie: "suppr" in french) - return False + if keyboard_event.scan_code in {83, 52}: + # TODO: "del" won't work with "(keypad)delete" if localized in non-english (ie: "suppr" in french) + return expected_key == keyboard_event.name # Prevent "action keys" from triggering "keypad keys" - if is_digit(keyboard_event.name[-1]): - # Prevent "regular numbers" from activating "keypad numbers" - if expected_key.startswith("num "): - return keyboard_event.is_keypad - # Prevent "keypad numbers" from activating "regular numbers" - else: - return not keyboard_event.is_keypad - else: - # Prevent "keypad action keys" from triggering "regular numbers" and "keypad numbers" - # Still allow the same key that might be localized differently on keypad vs non-keypad - return not is_digit(expected_key[-1]) - - -# NOTE: This is a workaround very specific to numpads. -# Windows reports different physical keys with the same scan code. -# For example, "Home", "Num Home" and "Num 7" are all "71". -# See: https://github.com/boppreh/keyboard/issues/171#issuecomment-390437684 -# -# We're doing the check here instead of saving the key code because it'll -# cause issues with save files and the non-keypad shared keys are localized -# while the keypad ones aren't. -# -# Since we reuse the key string we set to send to LiveSplit, we can't use fake names like "num home". -# We're also trying to achieve the same hotkey behaviour as LiveSplit has. -def _hotkey_action(keyboard_event: keyboard.KeyboardEvent, key_name: str, action: Callable[[]]): + if keyboard_event.name and is_digit(keyboard_event.name[-1]): + # Prevent "regular numbers" and "keypad numbers" from activating each other + return bool( + keyboard_event.is_keypad + if expected_key.startswith("num ") + else not keyboard_event.is_keypad, + ) + + # Prevent "keypad action keys" from triggering "regular numbers" and "keypad numbers" + # Still allow the same key that might be localized differently on keypad vs non-keypad + return not is_digit(expected_key[-1]) + + +def _hotkey_action(keyboard_event: keyboard.KeyboardEvent, key_name: str, action: Callable[[], None]): + """ + We're doing the check here instead of saving the key code because + the non-keypad shared keys are localized while the keypad ones aren't. + They also share scan codes on Windows. + """ if keyboard_event.event_type == keyboard.KEY_DOWN and __validate_keypad(key_name, keyboard_event): action() def __get_key_name(keyboard_event: keyboard.KeyboardEvent): + """Ensures proper keypad name.""" + event_name = str(keyboard_event.name) + # Normally this is done by keyboard.get_hotkey_name. But our code won't always get there. + if event_name == "+": + return "plus" return f"num {keyboard_event.name}" \ if keyboard_event.is_keypad and is_digit(keyboard_event.name) \ - else str(keyboard_event.name) - - -def __is_key_already_set(self: AutoSplit, key_name: str): - return key_name == self.splitLineEdit.text() \ - or key_name == self.resetLineEdit.text() \ - or key_name == self.skipsplitLineEdit.text() \ - or key_name == self.undosplitLineEdit.text() \ - or key_name == self.pausehotkeyLineEdit.text() - - -# --------------------HOTKEYS-------------------------- -# TODO: Refactor to de-duplicate all this code, including settings_file.py -# Going to comment on one func, and others will be similar. -def setSplitHotkey(self: AutoSplit): - self.setsplithotkeyButton.setText('Press a key...') - - # disable some buttons - self.beforeSettingHotkey() - - # new thread points to callback. this thread is needed or GUI will freeze + else event_name + + +def __get_hotkey_name(names: list[str]): + """ + Uses keyboard.get_hotkey_name but works with non-english modifiers and keypad + See: https://github.com/boppreh/keyboard/issues/516 . + """ + def sorting_key(key: str): + return not keyboard.is_modifier(keyboard.key_to_scan_codes(key)[0]) + + if len(names) == 1: + return names[0] + clean_names = sorted(keyboard.get_hotkey_name(names).split("+"), key=sorting_key) + # Replace the last key in hotkey_name with what we actually got as a last key_name + # This ensures we keep proper keypad names + return "+".join(clean_names[:-1] + names[-1:]) + + +def __read_hotkey(): + """ + Blocks until a hotkey combination is read. + Returns the hotkey_name and last KeyboardEvent. + """ + names: list[str] = [] + while True: + keyboard_event = keyboard.read_event(True) + # LiveSplit supports modifier keys as the last key, so any keyup means end of hotkey + if keyboard_event.event_type == keyboard.KEY_UP: + break + key_name = __get_key_name(keyboard_event) + # Ignore long presses + if names and names[-1] == key_name: + continue + names.append(__get_key_name(keyboard_event)) + # Stop at the first non-modifier to prevent registering a hotkey with multiple regular keys + if not keyboard.is_modifier(keyboard_event.scan_code): + break + return __get_hotkey_name(names) + + +def __remove_key_already_set(autosplit: AutoSplit, key_name: str): + for hotkey in HOTKEYS: + settings_key = f"{hotkey}_hotkey" + if autosplit.settings_dict[settings_key] == key_name: # pyright: ignore[reportGeneralTypeIssues] + _unhook(getattr(autosplit, f"{hotkey}_hotkey")) + autosplit.settings_dict[settings_key] = "" # pyright: ignore[reportGeneralTypeIssues] + if autosplit.SettingsWidget: + getattr(autosplit.SettingsWidget, f"{hotkey}_input").setText("") + + +def __get_hotkey_action(autosplit: AutoSplit, hotkey: Hotkey): + if hotkey == "split": + return autosplit.start_auto_splitter + if hotkey == "skip_split": + return lambda: autosplit.skip_split(True) + if hotkey == "undo_split": + return lambda: autosplit.undo_split(True) + if hotkey == "toggle_auto_reset_image": + def toggle_auto_reset_image(): + new_value = not autosplit.settings_dict["enable_auto_reset"] + autosplit.settings_dict["enable_auto_reset"] = new_value + if autosplit.SettingsWidget: + autosplit.SettingsWidget.enable_auto_reset_image_checkbox.setChecked(new_value) + return toggle_auto_reset_image + return getattr(autosplit, f"{hotkey}_signal").emit + + +def is_valid_hotkey_name(hotkey_name: str): + return any( + key and not keyboard.is_modifier(keyboard.key_to_scan_codes(key)[0]) + for key + in hotkey_name.split("+") + ) + +# TODO: using getattr/setattr is NOT a good way to go about this. It was only temporarily done to +# reduce duplicated code. We should use a dictionary of hotkey class or something. + + +def set_hotkey(autosplit: AutoSplit, hotkey: Hotkey, preselected_hotkey_name: str = ""): + if autosplit.SettingsWidget: + # Unfocus all fields + cast(QtWidgets.QWidget, autosplit.SettingsWidget).setFocus() + getattr(autosplit.SettingsWidget, f"set_{hotkey}_hotkey_button").setText(PRESS_A_KEY_TEXT) + + # Disable some buttons + before_setting_hotkey(autosplit) + + # New thread points to read_and_set_hotkey. this thread is needed or GUI will freeze # while the program waits for user input on the hotkey - def callback(hotkey): - # try to remove the previously set hotkey if there is one. - try: - keyboard.unhook_key(hotkey) - # KeyError was coming up when loading the program and - # the lineEdit area was empty (no hotkey set), then you - # set one, reload the setting once back to blank works, - # but if you click reload settings again, it errors - # we can just have it pass, but don't want to throw in - # generic exception here in case another one of these - # pops up somewhere. - except (AttributeError, KeyError): - pass - - # wait until user presses the hotkey, then keyboard module reads the input - key_name = __get_key_name(keyboard.read_event(True)) - try: - # If the key the user presses is equal to itself or another hotkey already set, - # this causes issues. so here, it catches that, and will make no changes to the hotkey. - - # or - - # keyboard module allows you to hit multiple keys for a hotkey. they are joined - # together by +. If user hits two keys at the same time, make no changes to the - # hotkey. A try and except is needed if a hotkey hasn't been set yet. I'm not - # allowing for these multiple-key hotkeys because it can cause crashes, and - # not many people are going to really use or need this. - if __is_key_already_set(self, key_name) or (key_name != '+' and '+' in key_name): - self.afterSettingHotkeySignal.emit() - return - except AttributeError: - self.afterSettingHotkeySignal.emit() - return - - # add the key as the hotkey, set the text into the LineEdit, set it as old_xxx_key, - # then emite a signal to re-enable some buttons and change some text in GUI. - - # We need to inspect the event to know if it comes from numpad because of _canonial_names. - # See: https://github.com/boppreh/keyboard/issues/161#issuecomment-386825737 - # The best way to achieve this is make our own hotkey handling on top of hook - # See: https://github.com/boppreh/keyboard/issues/216#issuecomment-431999553 - self.split_hotkey = keyboard.hook_key(key_name, lambda e: _hotkey_action(e, key_name, self.startAutoSplitter)) - self.splitLineEdit.setText(key_name) - self.split_key = key_name - self.afterSettingHotkeySignal.emit() - - t = threading.Thread(target=callback, args=(self.split_hotkey,)) - t.start() - - -def setResetHotkey(self: AutoSplit): - self.setresethotkeyButton.setText('Press a key...') - self.beforeSettingHotkey() - - def callback(hotkey): + @fire_and_forget + def read_and_set_hotkey(): try: - keyboard.unhook_key(hotkey) - except (AttributeError, KeyError): - pass - - key_name = __get_key_name(keyboard.read_event(True)) + hotkey_name = preselected_hotkey_name if preselected_hotkey_name else __read_hotkey() - try: - if __is_key_already_set(self, key_name) or (key_name != '+' and '+' in key_name): - self.afterSettingHotkeySignal.emit() + if not is_valid_hotkey_name(hotkey_name): + autosplit.show_error_signal.emit(lambda: error_messages.invalid_hotkey(hotkey_name)) return - except AttributeError: - self.afterSettingHotkeySignal.emit() - return - - self.reset_hotkey = keyboard.hook_key(key_name, lambda e: _hotkey_action(e, key_name, self.startReset)) - self.resetLineEdit.setText(key_name) - self.reset_key = key_name - self.afterSettingHotkeySignal.emit() - - t = threading.Thread(target=callback, args=(self.reset_hotkey,)) - t.start() - - -def setSkipSplitHotkey(self: AutoSplit): - self.setskipsplithotkeyButton.setText('Press a key...') - self.beforeSettingHotkey() - - def callback(hotkey): - try: - keyboard.unhook_key(hotkey) - except (AttributeError, KeyError): - pass - - key_name = __get_key_name(keyboard.read_event(True)) - - try: - if __is_key_already_set(self, key_name) or (key_name != '+' and '+' in key_name): - self.afterSettingHotkeySignal.emit() - return - except AttributeError: - self.afterSettingHotkeySignal.emit() - return - - self.skip_split_hotkey = keyboard.hook_key(key_name, lambda e: _hotkey_action(e, key_name, self.startSkipSplit)) - self.skipsplitLineEdit.setText(key_name) - self.skip_split_key = key_name - self.afterSettingHotkeySignal.emit() - - t = threading.Thread(target=callback, args=(self.skip_split_hotkey,)) - t.start() - - -def setUndoSplitHotkey(self: AutoSplit): - self.setundosplithotkeyButton.setText('Press a key...') - self.beforeSettingHotkey() - - def callback(hotkey): - try: - keyboard.unhook_key(hotkey) - except (AttributeError, KeyError): - pass - - key_name = __get_key_name(keyboard.read_event(True)) - - try: - if __is_key_already_set(self, key_name) or (key_name != '+' and '+' in key_name): - self.afterSettingHotkeySignal.emit() - return - except AttributeError: - self.afterSettingHotkeySignal.emit() - return - - self.undo_split_hotkey = keyboard.hook_key(key_name, lambda e: _hotkey_action(e, key_name, self.startUndoSplit)) - self.undosplitLineEdit.setText(key_name) - self.undo_split_key = key_name - self.afterSettingHotkeySignal.emit() - - t = threading.Thread(target=callback, args=(self.undo_split_hotkey,)) - t.start() - - -def setPauseHotkey(self: AutoSplit): - self.setpausehotkeyButton.setText('Press a key...') - self.beforeSettingHotkey() - - def callback(hotkey): - try: - keyboard.unhook_key(hotkey) - except (AttributeError, KeyError): - pass - - key_name = __get_key_name(keyboard.read_event(True)) - - try: - if __is_key_already_set(self, key_name) or (key_name != '+' and '+' in key_name): - self.afterSettingHotkeySignal.emit() - return - except AttributeError: - self.afterSettingHotkeySignal.emit() - return - - self.pause_hotkey = keyboard.hook_key(key_name, lambda e: _hotkey_action(e, key_name, self.startPause)) - self.pausehotkeyLineEdit.setText(key_name) - self.pause_key = key_name - self.afterSettingHotkeySignal.emit() - t = threading.Thread(target=callback, args=(self.pause_hotkey,)) - t.start() + # Try to remove the previously set hotkey if there is one + _unhook(getattr(autosplit, f"{hotkey}_hotkey")) + # Remove any hotkey using the same key combination + + __remove_key_already_set(autosplit, hotkey_name) + + action = __get_hotkey_action(autosplit, hotkey) + setattr( + autosplit, + f"{hotkey}_hotkey", + # keyboard.add_hotkey doesn't give the last keyboard event, so we can't __validate_keypad. + # This means "ctrl + num 5" and "ctrl + 5" will both be registered. + # For that reason, we still prefer keyboard.hook_key for single keys. + # keyboard module allows you to hit multiple keys for a hotkey. they are joined together by +. + keyboard.add_hotkey(hotkey_name, action) + if "+" in hotkey_name + # We need to inspect the event to know if it comes from numpad because of _canonial_names. + # See: https://github.com/boppreh/keyboard/issues/161#issuecomment-386825737 + # The best way to achieve this is make our own hotkey handling on top of hook + # See: https://github.com/boppreh/keyboard/issues/216#issuecomment-431999553 + else keyboard.hook_key( + hotkey_name, + lambda keyboard_event: _hotkey_action(keyboard_event, hotkey_name, action), + ), + ) + + if autosplit.SettingsWidget: + getattr(autosplit.SettingsWidget, f"{hotkey}_input").setText(hotkey_name) + autosplit.settings_dict[f"{hotkey}_hotkey"] = hotkey_name # pyright: ignore[reportGeneralTypeIssues] + except Exception as exception: # noqa: BLE001 # We really want to catch everything here + error = exception + autosplit.show_error_signal.emit(lambda: error_messages.exception_traceback(error)) + finally: + autosplit.after_setting_hotkey_signal.emit() + + read_and_set_hotkey() diff --git a/src/menu_bar.py b/src/menu_bar.py index e1d162eb..381b9320 100644 --- a/src/menu_bar.py +++ b/src/menu_bar.py @@ -1,82 +1,355 @@ from __future__ import annotations -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from AutoSplit import AutoSplit -import os -from PyQt6 import QtWidgets +import asyncio +import webbrowser +from typing import TYPE_CHECKING, Any, cast import requests -from packaging import version -import update_checker +from packaging.version import parse as version_parse +from PySide6 import QtCore, QtWidgets +from requests.exceptions import RequestException + import error_messages -import resources_rc # noqa: F401 -from about import Ui_aboutAutoSplitWidget +import user_profile +from capture_method import ( + CAPTURE_METHODS, + CameraInfo, + CaptureMethodEnum, + change_capture_method, + get_all_video_capture_devices, +) +from gen import about, design, settings as settings_ui, update_checker +from hotkeys import HOTKEYS, Hotkey, set_hotkey +from utils import AUTOSPLIT_VERSION, GITHUB_REPOSITORY, decimal, fire_and_forget + +if TYPE_CHECKING: + from AutoSplit import AutoSplit -# AutoSplit Version number -VERSION = "1.6.1" +class __AboutWidget(QtWidgets.QWidget, about.Ui_AboutAutoSplitWidget): # noqa: N801 # Private class + """About Window.""" -# About Window -class AboutWidget(QtWidgets.QWidget, Ui_aboutAutoSplitWidget): def __init__(self): super().__init__() self.setupUi(self) - self.createdbyLabel.setOpenExternalLinks(True) - self.donatebuttonLabel.setOpenExternalLinks(True) - self.versionLabel.setText(f"Version: {VERSION}") + self.created_by_label.setOpenExternalLinks(True) + self.donate_button_label.setOpenExternalLinks(True) + self.version_label.setText(f"Version: {AUTOSPLIT_VERSION}") self.show() -class UpdateCheckerWidget(QtWidgets.QWidget, update_checker.Ui_UpdateChecker): - def __init__(self, latest_version: str, autosplit: AutoSplit, check_for_updates_on_open: bool = False): + +def open_about(autosplit: AutoSplit): + if not autosplit.AboutWidget or cast(QtWidgets.QWidget, autosplit.AboutWidget).isHidden(): + autosplit.AboutWidget = __AboutWidget() + + +class __UpdateCheckerWidget(QtWidgets.QWidget, update_checker.Ui_UpdateChecker): # noqa: N801 # Private class + def __init__(self, latest_version: str, design_window: design.Ui_MainWindow, check_on_open: bool = False): super().__init__() self.setupUi(self) - self.labelCurrentVersionNumber.setText(VERSION) - self.labelLatestVersionNumber.setText(latest_version) - self.pushButtonLeft.clicked.connect(self.openUpdate) - self.pushButtonRight.clicked.connect(self.closeWindow) - self.autosplit = autosplit - if version.parse(latest_version) > version.parse(VERSION): - self.labelUpdateStatus.setText("There is an update available for AutoSplit.") - self.labelGoToDownload.setText("Open download page?") - self.pushButtonLeft.setVisible(True) - self.pushButtonLeft.setText("Open") - self.pushButtonRight.setText("Later") - if not check_for_updates_on_open: - self.checkBoxDoNotAskMeAgain.setVisible(False) + self.current_version_number_label.setText(AUTOSPLIT_VERSION) + self.latest_version_number_label.setText(latest_version) + self.left_button.clicked.connect(self.open_update) + self.do_not_ask_again_checkbox.stateChanged.connect(self.do_not_ask_me_again_state_changed) + self.design_window = design_window + if version_parse(latest_version) > version_parse(AUTOSPLIT_VERSION): + self.do_not_ask_again_checkbox.setVisible(check_on_open) + self.left_button.setFocus() self.show() - elif not check_for_updates_on_open: - self.labelUpdateStatus.setText("You are on the latest AutoSplit version.") - self.pushButtonLeft.setVisible(False) - self.pushButtonRight.setText("OK") - self.checkBoxDoNotAskMeAgain.setVisible(False) + elif not check_on_open: + self.update_status_label.setText("You are on the latest AutoSplit version.") + self.go_to_download_label.setVisible(False) + self.left_button.setVisible(False) + self.right_button.setText("OK") + self.do_not_ask_again_checkbox.setVisible(False) self.show() - def openUpdate(self): - if self.checkBoxDoNotAskMeAgain.isChecked(): - self.autosplit.actionCheck_for_Updates_on_Open.setChecked(False) - os.system("start \"\" https://github.com/Toufool/Auto-Split/releases/latest") + def open_update(self): + webbrowser.open(f"https://github.com/{GITHUB_REPOSITORY}/releases/latest") self.close() - def closeWindow(self): - if self.checkBoxDoNotAskMeAgain.isChecked(): - self.autosplit.actionCheck_for_Updates_on_Open.setChecked(False) - self.close() + def do_not_ask_me_again_state_changed(self): + user_profile.set_check_for_updates_on_open( + self.design_window, + self.do_not_ask_again_checkbox.isChecked(), + ) + + +def open_update_checker(autosplit: AutoSplit, latest_version: str, check_on_open: bool): + if not autosplit.UpdateCheckerWidget or cast(QtWidgets.QWidget, autosplit.UpdateCheckerWidget).isHidden(): + autosplit.UpdateCheckerWidget = __UpdateCheckerWidget(latest_version, autosplit, check_on_open) + + +def view_help(): + webbrowser.open(f"https://github.com/{GITHUB_REPOSITORY}#tutorial") + + +class __CheckForUpdatesThread(QtCore.QThread): # noqa: N801 # Private class + def __init__(self, autosplit: AutoSplit, check_on_open: bool): + super().__init__() + self.autosplit = autosplit + self.check_on_open = check_on_open + + def run(self): + try: + response = requests.get(f"https://api.github.com/repos/{GITHUB_REPOSITORY}/releases/latest", timeout=30) + latest_version = str(response.json()["name"]).split("v")[1] + self.autosplit.update_checker_widget_signal.emit(latest_version, self.check_on_open) + except (RequestException, KeyError): + if not self.check_on_open: + self.autosplit.show_error_signal.emit(error_messages.check_for_updates) + + +def about_qt(): + webbrowser.open("https://wiki.qt.io/About_Qt") + + +def about_qt_for_python(): + webbrowser.open("https://wiki.qt.io/Qt_for_Python") + -def viewHelp(): - os.system("start \"\" https://github.com/Toufool/Auto-Split#tutorial") +def check_for_updates(autosplit: AutoSplit, check_on_open: bool = False): + autosplit.CheckForUpdatesThread = __CheckForUpdatesThread(autosplit, check_on_open) + autosplit.CheckForUpdatesThread.start() -def about(self: AutoSplit): - self.AboutWidget = AboutWidget() +class __SettingsWidget(QtWidgets.QWidget, settings_ui.Ui_SettingsWidget): # noqa: N801 # Private class + __video_capture_devices: list[CameraInfo] = [] + """ + Used to temporarily store the existing cameras, + we don't want to call `get_all_video_capture_devices` agains and possibly have a different result + """ -def checkForUpdates(autosplit: AutoSplit, check_for_updates_on_open: bool = False): - try: - response = requests.get("https://api.github.com/repos/Toufool/Auto-Split/releases/latest") - latest_version = response.json()["name"].split("v")[1] - autosplit.UpdateCheckerWidget = UpdateCheckerWidget(latest_version, autosplit, check_for_updates_on_open) - except: - if not check_for_updates_on_open: - error_messages.checkForUpdatesError() + def __update_default_threshold(self, value: Any): + self.__set_value("default_similarity_threshold", value) + self.autosplit.table_current_image_threshold_label.setText( + decimal(self.autosplit.split_image.get_similarity_threshold(self.autosplit)) + if self.autosplit.split_image + else "-", + ) + self.autosplit.table_reset_image_threshold_label.setText( + decimal(self.autosplit.reset_image.get_similarity_threshold(self.autosplit)) + if self.autosplit.reset_image + else "-", + ) + + def __set_value(self, key: str, value: Any): + self.autosplit.settings_dict[key] = value + + def get_capture_device_index(self, capture_device_id: int): + """Returns 0 if the capture_device_id is invalid.""" + try: + return [device.device_id for device in self.__video_capture_devices].index(capture_device_id) + except ValueError: + return 0 + + def __enable_capture_device_if_its_selected_method( + self, selected_capture_method: str | CaptureMethodEnum | None = None, + ): + if selected_capture_method is None: + selected_capture_method = self.autosplit.settings_dict["capture_method"] + is_video_capture_device = selected_capture_method == CaptureMethodEnum.VIDEO_CAPTURE_DEVICE + self.capture_device_combobox.setEnabled(is_video_capture_device) + if is_video_capture_device: + self.capture_device_combobox.setCurrentIndex( + self.get_capture_device_index(self.autosplit.settings_dict["capture_device_id"]), + ) + else: + self.capture_device_combobox.setPlaceholderText('Select "Video Capture Device" above') + self.capture_device_combobox.setCurrentIndex(-1) + + def __capture_method_changed(self): + selected_capture_method = CAPTURE_METHODS.get_method_by_index(self.capture_method_combobox.currentIndex()) + self.__enable_capture_device_if_its_selected_method(selected_capture_method) + change_capture_method(selected_capture_method, self.autosplit) + return selected_capture_method + + def __capture_device_changed(self): + device_index = self.capture_device_combobox.currentIndex() + if device_index == -1: + return + capture_device = self.__video_capture_devices[device_index] + self.autosplit.settings_dict["capture_device_name"] = capture_device.name + self.autosplit.settings_dict["capture_device_id"] = capture_device.device_id + if self.autosplit.settings_dict["capture_method"] == CaptureMethodEnum.VIDEO_CAPTURE_DEVICE: + # Re-initializes the VideoCaptureDeviceCaptureMethod + change_capture_method(CaptureMethodEnum.VIDEO_CAPTURE_DEVICE, self.autosplit) + + @fire_and_forget + def __set_all_capture_devices(self): + self.__video_capture_devices = asyncio.run(get_all_video_capture_devices()) + if len(self.__video_capture_devices) > 0: + for i in range(self.capture_device_combobox.count()): + self.capture_device_combobox.removeItem(i) + self.capture_device_combobox.addItems([ + f"* {device.name}" + + (f" [{device.backend}]" if device.backend else "") + + (" (occupied)" if device.occupied else "") + for device in self.__video_capture_devices + ]) + self.__enable_capture_device_if_its_selected_method() else: - pass + self.capture_device_combobox.setPlaceholderText("No device found.") + + def __set_readme_link(self): + self.custom_image_settings_info_label.setText( + self.custom_image_settings_info_label + .text() + .format(GITHUB_REPOSITORY=GITHUB_REPOSITORY), + ) + # HACK: This is a workaround because custom_image_settings_info_label + # simply will not open links with a left click no matter what we tried. + self.readme_link_button.clicked.connect( + lambda: webbrowser.open(f"https://github.com/{GITHUB_REPOSITORY}#readme"), + ) + self.readme_link_button.setStyleSheet("border: 0px; background-color:rgba(0,0,0,0%);") + + def __init__(self, autosplit: AutoSplit): + super().__init__() + self.setupUi(self) + self.autosplit = autosplit + self.__set_readme_link() + # Don't autofocus any particular field + self.setFocus() + + +# region Build the Capture method combobox + capture_method_values = CAPTURE_METHODS.values() + self.__set_all_capture_devices() + capture_list_items = [ + f"- {method.name} ({method.short_description})" + for method in capture_method_values + ] + list_view = QtWidgets.QListView() + list_view.setWordWrap(True) + # HACK: The first time the dropdown is rendered, it does not have the right height + # Assuming all options take 2 lines (except camera and BitBlt which have 1). + # And all lines take 16 pixels + # And all separators take 2 pixels + doubled_len = 2 * len(capture_method_values) or 2 + list_view.setMinimumHeight((doubled_len - 2) * 16 + doubled_len) + list_view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.capture_method_combobox.setView(list_view) + self.capture_method_combobox.addItems(capture_list_items) + self.capture_method_combobox.setToolTip( + "\n\n".join([ + f"{method.name} :\n{method.description}" + for method in capture_method_values + ]), + ) +# endregion + + # Hotkey initial values and bindings + def hotkey_connect(hotkey: Hotkey): + return lambda: set_hotkey(self.autosplit, hotkey) + for hotkey in HOTKEYS: + hotkey_input: QtWidgets.QLineEdit = getattr(self, f"{hotkey}_input") + set_hotkey_hotkey_button: QtWidgets.QPushButton = getattr(self, f"set_{hotkey}_hotkey_button") + hotkey_input.setText( + cast( + str, + autosplit.settings_dict[f"{hotkey}_hotkey"], # pyright: ignore[reportGeneralTypeIssues] + ), + ) + + set_hotkey_hotkey_button.clicked.connect(hotkey_connect(hotkey)) + # Make it very clear that hotkeys are not used when auto-controlled + if autosplit.is_auto_controlled and hotkey != "toggle_auto_reset_image": + set_hotkey_hotkey_button.setEnabled(False) + hotkey_input.setEnabled(False) + +# region Set initial values + # Capture Settings + self.fps_limit_spinbox.setValue(autosplit.settings_dict["fps_limit"]) + self.live_capture_region_checkbox.setChecked(autosplit.settings_dict["live_capture_region"]) + self.capture_method_combobox.setCurrentIndex( + CAPTURE_METHODS.get_index(autosplit.settings_dict["capture_method"]), + ) + # No self.capture_device_combobox.setCurrentIndex + # It'll set itself asynchronously in self.__set_all_capture_devices() + + # Image Settings + self.default_comparison_method.setCurrentIndex(autosplit.settings_dict["default_comparison_method"]) + self.default_similarity_threshold_spinbox.setValue(autosplit.settings_dict["default_similarity_threshold"]) + self.default_delay_time_spinbox.setValue(autosplit.settings_dict["default_delay_time"]) + self.default_pause_time_spinbox.setValue(autosplit.settings_dict["default_pause_time"]) + self.loop_splits_checkbox.setChecked(autosplit.settings_dict["loop_splits"]) + self.enable_auto_reset_image_checkbox.setChecked(autosplit.settings_dict["enable_auto_reset"]) +# endregion +# region Binding + # Capture Settings + self.fps_limit_spinbox.valueChanged.connect( + lambda: self.__set_value("fps_limit", self.fps_limit_spinbox.value()), + ) + self.live_capture_region_checkbox.stateChanged.connect( + lambda: self.__set_value("live_capture_region", self.live_capture_region_checkbox.isChecked()), + ) + self.capture_method_combobox.currentIndexChanged.connect( + lambda: self.__set_value("capture_method", self.__capture_method_changed()), + ) + self.capture_device_combobox.currentIndexChanged.connect(self.__capture_device_changed) + + # Image Settings + self.default_comparison_method.currentIndexChanged.connect( + lambda: self.__set_value("default_comparison_method", self.default_comparison_method.currentIndex()), + ) + self.default_similarity_threshold_spinbox.valueChanged.connect( + lambda: self.__update_default_threshold(self.default_similarity_threshold_spinbox.value()), + ) + self.default_delay_time_spinbox.valueChanged.connect( + lambda: self.__set_value("default_delay_time", self.default_delay_time_spinbox.value()), + ) + self.default_pause_time_spinbox.valueChanged.connect( + lambda: self.__set_value("default_pause_time", self.default_pause_time_spinbox.value()), + ) + self.loop_splits_checkbox.stateChanged.connect( + lambda: self.__set_value("loop_splits", self.loop_splits_checkbox.isChecked()), + ) + self.enable_auto_reset_image_checkbox.stateChanged.connect( + lambda: self.__set_value("enable_auto_reset", self.enable_auto_reset_image_checkbox.isChecked()), + ) +# endregion + + self.show() + + +def open_settings(autosplit: AutoSplit): + if not autosplit.SettingsWidget or cast(QtWidgets.QWidget, autosplit.SettingsWidget).isHidden(): + autosplit.SettingsWidget = __SettingsWidget(autosplit) + + +def get_default_settings_from_ui(autosplit: AutoSplit): + temp_dialog = QtWidgets.QWidget() + default_settings_dialog = settings_ui.Ui_SettingsWidget() + default_settings_dialog.setupUi(temp_dialog) + default_settings: user_profile.UserProfileDict = { + "split_hotkey": default_settings_dialog.split_input.text(), + "reset_hotkey": default_settings_dialog.reset_input.text(), + "undo_split_hotkey": default_settings_dialog.undo_split_input.text(), + "skip_split_hotkey": default_settings_dialog.skip_split_input.text(), + "pause_hotkey": default_settings_dialog.pause_input.text(), + "toggle_auto_reset_image_hotkey": default_settings_dialog.toggle_auto_reset_image_input.text(), + "fps_limit": default_settings_dialog.fps_limit_spinbox.value(), + "live_capture_region": default_settings_dialog.live_capture_region_checkbox.isChecked(), + "enable_auto_reset": default_settings_dialog.enable_auto_reset_image_checkbox.isChecked(), + "capture_method": CAPTURE_METHODS.get_method_by_index( + default_settings_dialog.capture_method_combobox.currentIndex(), + ), + "capture_device_id": default_settings_dialog.capture_device_combobox.currentIndex(), + "capture_device_name": "", + "default_comparison_method": default_settings_dialog.default_comparison_method.currentIndex(), + "default_similarity_threshold": default_settings_dialog.default_similarity_threshold_spinbox.value(), + "default_delay_time": default_settings_dialog.default_delay_time_spinbox.value(), + "default_pause_time": default_settings_dialog.default_pause_time_spinbox.value(), + "loop_splits": default_settings_dialog.loop_splits_checkbox.isChecked(), + "split_image_directory": autosplit.split_image_folder_input.text(), + "captured_window_title": "", + "capture_region": { + "x": autosplit.x_spinbox.value(), + "y": autosplit.y_spinbox.value(), + "width": autosplit.width_spinbox.value(), + "height": autosplit.height_spinbox.value(), + }, + } + del temp_dialog + return default_settings diff --git a/src/region_selection.py b/src/region_selection.py new file mode 100644 index 00000000..310b71f1 --- /dev/null +++ b/src/region_selection.py @@ -0,0 +1,385 @@ +from __future__ import annotations + +import ctypes +import ctypes.wintypes +import os +from math import ceil +from typing import TYPE_CHECKING, cast + +import cv2 +import numpy as np +from PySide6 import QtCore, QtGui, QtWidgets +from PySide6.QtTest import QTest +from pywinctl import getTopWindowAt +from typing_extensions import override +from win32 import win32gui +from win32con import SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN +from winsdk._winrt import initialize_with_window +from winsdk.windows.foundation import AsyncStatus, IAsyncOperation +from winsdk.windows.graphics.capture import GraphicsCaptureItem, GraphicsCapturePicker + +import error_messages +from utils import MAXBYTE, get_window_bounds, is_valid_hwnd, is_valid_image + +user32 = ctypes.windll.user32 + +if TYPE_CHECKING: + from AutoSplit import AutoSplit + +ALIGN_REGION_THRESHOLD = 0.9 +BORDER_WIDTH = 2 +SUPPORTED_IMREAD_FORMATS = [ + ("Windows bitmaps", "*.bmp *.dib"), + ("JPEG files", "*.jpeg *.jpg *.jpe"), + ("JPEG 2000 files", "*.jp2"), + ("Portable Network Graphics", "*.png"), + ("WebP", "*.webp"), + ("Portable image format", "*.pbm *.pgm *.ppm *.pxm *.pnm"), + ("PFM files", "*.pfm"), + ("Sun rasters", "*.sr *.ras"), + ("TIFF files", "*.tiff *.tif"), + ("OpenEXR Image files", "*.exr"), + ("Radiance HDR", "*.hdr *.pic"), +] +"""https://docs.opencv.org/4.5.4/d4/da8/group__imgcodecs.html#ga288b8b3da0892bd651fce07b3bbd3a56""" +IMREAD_EXT_FILTER = "All Files (" \ + + " ".join([f"{extensions}" for _, extensions in SUPPORTED_IMREAD_FORMATS]) \ + + ");;"\ + + ";;".join([f"{imread_format} ({extensions})" for imread_format, extensions in SUPPORTED_IMREAD_FORMATS]) + + +def __select_graphics_item(autosplit: AutoSplit): # pyright: ignore [reportUnusedFunction] + # TODO: For later as a different picker option + """Uses the built-in GraphicsCapturePicker to select the Window.""" + def callback(async_operation: IAsyncOperation[GraphicsCaptureItem], async_status: AsyncStatus): + try: + if async_status != AsyncStatus.COMPLETED: + return + except SystemError as exception: + # HACK: can happen when closing the GraphicsCapturePicker + if str(exception).endswith("returned a result with an error set"): + return + raise + item = async_operation.get_results() + if not item: + return + autosplit.settings_dict["captured_window_title"] = item.display_name + autosplit.capture_method.reinitialize(autosplit) + + picker = GraphicsCapturePicker() + initialize_with_window(picker, int(autosplit.effectiveWinId())) + async_operation = picker.pick_single_item_async() + # None if the selection is canceled + if async_operation: + async_operation.completed = callback + + +def select_region(autosplit: AutoSplit): + # Create a screen selector widget + selector = SelectRegionWidget() + + # Need to wait until the user has selected a region using the widget + # before moving on with selecting the window settings + while True: + width = selector.width() + height = selector.height() + x = selector.x() + y = selector.y() + if width > 0 and height > 0: + break + QTest.qWait(1) + del selector + + window = getTopWindowAt(x, y) + if not window: + error_messages.region() + return + hwnd = window.getHandle() + window_text = window.title + if not is_valid_hwnd(hwnd) or not window_text: + error_messages.region() + return + + autosplit.hwnd = hwnd + autosplit.settings_dict["captured_window_title"] = window_text + autosplit.capture_method.reinitialize(autosplit) + + left_bounds, top_bounds, *_ = get_window_bounds(hwnd) + window_x, window_y, *_ = win32gui.GetWindowRect(hwnd) + offset_x = window_x + left_bounds + offset_y = window_y + top_bounds + __set_region_values( + autosplit, + left=x - offset_x, + top=y - offset_y, + width=width, + height=height, + ) + + +def select_window(autosplit: AutoSplit): + # Create a screen selector widget + selector = SelectWindowWidget() + + # Need to wait until the user has selected a region using the widget before moving on with + # selecting the window settings + while True: + x = selector.x() + y = selector.y() + if x and y: + break + QTest.qWait(1) + del selector + + window = getTopWindowAt(x, y) + if not window: + error_messages.region() + return + hwnd = window.getHandle() + window_text = window.title + if not is_valid_hwnd(hwnd) or not window_text: + error_messages.region() + return + + autosplit.hwnd = hwnd + autosplit.settings_dict["captured_window_title"] = window_text + autosplit.capture_method.reinitialize(autosplit) + + # Exlude the borders and titlebar from the window selection. To only get the client area. + _, __, window_width, window_height = get_window_bounds(hwnd) + _, __, client_width, client_height = win32gui.GetClientRect(hwnd) + border_width = ceil((window_width - client_width) / 2) + titlebar_with_border_height = window_height - client_height - border_width + + __set_region_values( + autosplit, + left=border_width, + top=titlebar_with_border_height, + width=client_width, + height=client_height - border_width * 2, + ) + + +def align_region(autosplit: AutoSplit): + # Check to see if a region has been set + if not autosplit.capture_method.check_selected_region_exists(autosplit): + error_messages.region() + return + # This is the image used for aligning the capture region to the best fit for the user. + template_filename = cast( + str, # https://bugreports.qt.io/browse/PYSIDE-2285 + QtWidgets.QFileDialog.getOpenFileName( + autosplit, + "Select Reference Image", + "", + IMREAD_EXT_FILTER, + )[0], + ) + + # Return if the user presses cancel + if not template_filename: + return + + template = cv2.imread(template_filename, cv2.IMREAD_COLOR) + + # Validate template is a valid image file + if not is_valid_image(template): + error_messages.align_region_image_type() + return + + # Obtaining the capture of a region which contains the + # subregion being searched for to align the image. + capture, _ = autosplit.capture_method.get_frame(autosplit) + + if not is_valid_image(capture): + error_messages.region() + return + + best_match, best_height, best_width, best_loc = __test_alignment(capture, template) + + # Go ahead and check if this satisfies our requirement before setting the region + # We don't want a low similarity image to be aligned. + if best_match < ALIGN_REGION_THRESHOLD: + error_messages.alignment_not_matched() + return + + # The new region can be defined by using the min_loc point and the best_height and best_width of the template. + __set_region_values( + autosplit, + left=autosplit.settings_dict["capture_region"]["x"] + best_loc[0], + top=autosplit.settings_dict["capture_region"]["y"] + best_loc[1], + width=best_width, + height=best_height, + ) + + +def __set_region_values(autosplit: AutoSplit, left: int, top: int, width: int, height: int): + autosplit.settings_dict["capture_region"]["x"] = left + autosplit.settings_dict["capture_region"]["y"] = top + autosplit.settings_dict["capture_region"]["width"] = width + autosplit.settings_dict["capture_region"]["height"] = height + + autosplit.x_spinbox.setValue(left) + autosplit.y_spinbox.setValue(top) + autosplit.width_spinbox.setValue(width) + autosplit.height_spinbox.setValue(height) + + +def __test_alignment(capture: cv2.Mat, template: cv2.Mat): + """ + Obtain the best matching point for the template within the + capture. This assumes that the template is actually smaller + than the dimensions of the capture. Since we are using SQDIFF + the best match will be the min_val which is located at min_loc. + The best match found in the image, set everything to 0 by default + so that way the first match will overwrite these values. + """ + best_match = 0.0 + best_height = 0 + best_width = 0 + best_loc = (0, 0) + + # This tests 50 images scaled from 20% to 300% of the original template size + for scale in np.linspace(0.2, 3, num=56): + width = int(template.shape[1] * scale) + height = int(template.shape[0] * scale) + + # The template can not be larger than the capture + if width > capture.shape[1] or height > capture.shape[0]: + continue + + resized = cv2.resize(template, (width, height), interpolation=cv2.INTER_NEAREST) + + result = cv2.matchTemplate(capture, resized, cv2.TM_SQDIFF) + min_val, _, min_loc, *_ = cv2.minMaxLoc(result) + + # The maximum value for SQ_DIFF is dependent on the size of the template + # we need this value to normalize it from 0.0 to 1.0 + max_error = resized.size * MAXBYTE * MAXBYTE + similarity = 1 - (min_val / max_error) + + # Check if the similarity was good enough to get alignment + if similarity > best_match: + best_match = similarity + best_width = width + best_height = height + best_loc = min_loc + return best_match, best_height, best_width, best_loc + + +def validate_before_parsing(autosplit: AutoSplit, show_error: bool = True, check_empty_directory: bool = True): + error = None + if not autosplit.settings_dict["split_image_directory"]: + error = error_messages.split_image_directory + elif not os.path.isdir(autosplit.settings_dict["split_image_directory"]): + error = error_messages.split_image_directory_not_found + elif check_empty_directory and not os.listdir(autosplit.settings_dict["split_image_directory"]): + error = error_messages.split_image_directory_empty + elif not autosplit.capture_method.check_selected_region_exists(autosplit): + error = error_messages.region + if error and show_error: + error() + return not error + + +class BaseSelectWidget(QtWidgets.QWidget): + _x = 0 + _y = 0 + + @override + def x(self): + return self._x + + @override + def y(self): + return self._y + + def __init__(self): + super().__init__() + # We need to pull the monitor information to correctly draw the geometry covering all portions + # of the user's screen. These parameters create the bounding box with left, top, width, and height + self.setGeometry( + user32.GetSystemMetrics(SM_XVIRTUALSCREEN), + user32.GetSystemMetrics(SM_YVIRTUALSCREEN), + user32.GetSystemMetrics(SM_CXVIRTUALSCREEN), + user32.GetSystemMetrics(SM_CYVIRTUALSCREEN), + ) + self.setWindowTitle(" ") + self.setWindowOpacity(0.5) + self.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint) + self.show() + + @override + def keyPressEvent(self, event: QtGui.QKeyEvent): + if event.key() == QtCore.Qt.Key.Key_Escape: + self.close() + + +class SelectWindowWidget(BaseSelectWidget): + """Widget to select a window and obtain its bounds.""" + + @override + def mouseReleaseEvent(self, event: QtGui.QMouseEvent): + self._x = int(event.position().x()) + self.geometry().x() + self._y = int(event.position().y()) + self.geometry().y() + self.close() + + +class SelectRegionWidget(BaseSelectWidget): + """ + Widget for dragging screen region + Originated from https://github.com/harupy/snipping-tool . + """ + + _right: int = 0 + _bottom: int = 0 + __begin = QtCore.QPoint() + __end = QtCore.QPoint() + + def __init__(self): + QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.CrossCursor)) + super().__init__() + + @override + def height(self): + return self._bottom - self._y + + @override + def width(self): + return self._right - self._x + + @override + def paintEvent(self, event: QtGui.QPaintEvent): + if self.__begin != self.__end: + qpainter = QtGui.QPainter(self) + qpainter.setPen(QtGui.QPen(QtGui.QColor("red"), BORDER_WIDTH)) + qpainter.setBrush(QtGui.QColor("opaque")) + qpainter.drawRect(QtCore.QRect(self.__begin, self.__end)) + + @override + def mousePressEvent(self, event: QtGui.QMouseEvent): + self.__begin = event.position().toPoint() + self.__end = self.__begin + self.update() + + @override + def mouseMoveEvent(self, event: QtGui.QMouseEvent): + self.__end = event.position().toPoint() + self.update() + + @override + def mouseReleaseEvent(self, event: QtGui.QMouseEvent): + if self.__begin != self.__end: + # The coordinates are pulled relative to the top left of the set geometry, + # so the added virtual screen offsets convert them back to the virtual screen coordinates + self._x = min(self.__begin.x(), self.__end.x()) + self.geometry().x() + self._y = min(self.__begin.y(), self.__end.y()) + self.geometry().y() + self._right = max(self.__begin.x(), self.__end.x()) + self.geometry().x() + self._bottom = max(self.__begin.y(), self.__end.y()) + self.geometry().y() + + self.close() + + @override + def close(self): + QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor)) + return super().close() diff --git a/src/resources_rc.py b/src/resources_rc.py deleted file mode 100644 index 4f7f7b54..00000000 --- a/src/resources_rc.py +++ /dev/null @@ -1,2311 +0,0 @@ -# Resource object code (Python 3) -# Created by: object code -# Created by: The Resource Compiler for Qt version 6.2.1 -# WARNING! All changes made in this file will be lost! - -from PySide6 import QtCore - -qt_resource_data = b"\ -\x00\x00\x0a\x8d\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x93\x00\x00\x00/\x08\x03\x00\x00\x00\xd03\x08)\ -\x00\x00\x03\x00PLTE\xff\xff\xff\xfb\xcbX\xff\xca\ -M\xfe\xc6C\xff\xc49\xfe\xc39\xff\xd7y\xff\xdc\x8a\ -\xff\xd2h\xfb\xc8M\xf6\xc0@\xf4\xc1HYC\x13\x00\ -\x00\x00\x05\x04\x04/$\x0akQ\x16\xeb\xb54\xf3\xba\ -7\x10\x08\x06\x1c\x14\x05\xd6\xa4/x[\x1b\x87h\x1e\ -\xfa\xbf7\xe5\xb03E1\x0c\x95r!\xcb\x9c-\xb5\ -\x8b(\xda\xa70\xa3}$\xe1\xac2cK\x16\xa4\xa6\ -\xa9\xbc\xbb\xbb\xb9\xb8\xb8\xb6\xb3\xb3eff\xbd\xbd\xbd\ -\xe8\xe8\xe8\x03\x0b\x0eLNN\x84\xb9\xebI\x99\xe2N\ -\xa3\xe8Y\xae\xec2\x87\xdc*{\xd8 p\xd2\x13b\ -\xcd\x06U\xc8\xe8\xef\xf9\xe3\xe4\xe5\xc2\xc2\xc1\xff\xff\xff\ -\xf8\xf8\xf9m\x01\x0dR\x01\x072\x03\x06vP\x0f\x94\ -\x07\x16\x05*9\x00^\x82\x04n\x9ay\xb6\xeb\x00K\ -\xc4\xea\xeb\xec\xd4\xd4\xd4\xcd\xcd\xcd\xba\xbd\xbd\xbc\xa0\xa5\ -\xa3\xac\xaf\xaf\xb6\xb9\xf6\xf6\xf7\xb2\x04 \xfc\x01\x1c\xf0\ -,\x10\xf6\x9e\x1b\xff\xb3\x1f{N\x0e\x939j(\x8c\ -\xd8\x00\xb3\xf7\x01\xa8\xe9\x00\xa1\xdei\xaf\xe9r\xbb\xef\ -~\xc1\xf1l\xa8\xe6\x5c\xa3\xe6I\x85\xd7B\x7f\xd5:\ -w\xd1\xed\xed\xed\xc4\xc5\xc5\xed\xf0\xf2\xfd\xfd\xfd\xed\x87\ -\x98\xdb$H|Bi5M|'p}5{\x85\ -\xe0\xe6\xeb\xf0\xf2\xf6\xf3\xf5\xf9\xf2\xf4\xf5\xfb\xfc\xfc\xeb\ -\x01\x1b\xfea\x01\xfbx\x08\xff\xaa\x1d\x8db\xa9W\x83\ -\xce\x03\x9b\xd7\x06Zh\x98\xc4\xed\x9d\xcc\xf2\xbc\xdb\xf5\ -\xa8\xd2\xf4\xb3\xd2\xf2\xa8\xcc\xf0\x83\xb4\xe8\x8b\xb1\xe6\x95\ -\xb8\xe8z\xa4\xe1f\x97\xdd\xff\xfa\xf4\xf9\xfb\xfc\xf6\xc7\ -\xcf\xd9\x01&\x0b-a\xd4\xda\xdc\xda\xdc\xe6\x8c\x91\xb5\ -\x84\x8b\xaf\xeb\xec\xf1tz\xa6\xac\xb0\xca\x87\x8e\xb3\xbc\ -\xc0\xd4\xd0\xd4\xdeSf\x92ht\x96\xcf\xd0\xde\xd4\x00\ -\x18\xf9E\x08\xfa\x8c\x12\x99b\x11\x9aN\x8duu\xc4\ -\x00\x84\xb6\xd1\xe2\xed\x90\xbc\xebv\xaa\xe4\xa0\xc5\xedq\ -\x9f\xe0_\x92\xdbX\x8d\xda\xd8\xdb\xdd\x90\x98\x9c\xc3\xc8\ -\xca\xb7\xbb\xbd\xd4\xd7\xd8\x98\xa2\xa7\xd1\xcb\xc6\xfe\xcb\x9f\ -\xfd\xbd\x86\xd3\xc0\xb0\xbe\xc3\xc5\x84\x8f\x95\xf2\xf1\xf1\xf3\ -\xad\xb8\xe6\x0c.i\x13;\x00%WN\x89\x93+6\ -x\xc4\xc9\xd8\x98\x9d\xbe\xa3\xa7\xc5KT\x8b\xe7\xe8\xf0\ -\x03\x15Z\xfa\x81\x0e9\x85\xc7\x00\x92\xcb\x91\xc6\xf0\x8a\ -\xb7\xe9\x85\xac\xe4S`hv\x82\x88\x9f\xaf\xba\xf3\xd9\ -\xc4\xda\x8aE.02\xfe\xf8\xf9\xee\x94\xa6\xf0{\x8f\ -;Y\x83\xa6\xc5\xca\xb3\xb9\xce\x0a\x14\x17h\x9d\xe1\x99\ -\xa8\xb8\xe4\xd2\xc2\xf8\x87)\xf8\xc1\x8a\x94\x9e\xa7?\ -\x22!!\x13T\xc7\xff\xfd\xf9\xfb\xa4Y\xfc\xb5ye\ -\x5c\x81\xae\x9d\x8c\xcd\xbe\xc2C\xef\xaeH\x00\x00\x00\x01\ -tRNS\x00@\xe6\xd8f\x00\x00\x00\x01orN\ -T\x01\xcf\xa2w\x9a\x00\x00\x07.IDATX\xc3\ -\xed\xd6y\x5c\x93u\x1c\x07\xf0g\xf0\x8cB\xf9=\x0f\ -{\x9e\xc91\x10\x01#\xe40\x0e\x93\x89\x08\x9e\x98\xa9\ -A)X\x04*\x88\x22\xd7\x04SP\x04\x86\xc8a\x82\ -\x0a\x1ee9\x0eEM\x93\xd46`h\x0aQR\x1e\ -`\x9a\x09QxT\x06e\x87i\x17\xdd\xf5\xfdmc\ -L}\x00c\xb3\x7f\xf2\xc3^\xbb\x9em\xcf\xfb\xf5\xfd\ -~\x9f\xdf\x0f\x82\xb8\x8b\xf0\x8c\x8cI>\x9f\xe4\x93\xda\ -\xf05\xaf\xf9:\xef\xe3\xe78\xbd~\xce\xd8\x88w7\ -g\xbb\x9b\x18\x19\xf3LL\x1e0@LLx\xc6F\ -\x86\x10\xf1\x8c\x1f4y\x10\xead\x80\x18\x1b\xc1O\x19\ -\xeb_+S\x9e\x89\x91!<\xdd12\xe1\x99\xeaM\ -\xe2\x19R\x84\xc3\xd3\x13\xc535h\x914\xa52\xd5\ -\xab}\x83\xee\x01\x09P\x83\xf4 \x0d6\xbd\x17$\x92\ -4\x1d\xacG\x99\xb8~\xd0\x0c!\x8a6\x17\xf4yR\ -F\xc0\xf4y|\xe0\x85\xe2\xf5bb\x85\x08\x09\x87\xf4\ -uN\x0bd\xd9\xb7i\xc0\x135\xd8\x8a\xdbdIZ\ -\x8b\x90Y\xdf&\x8b>MV\x03n\x1e\xc3\xe7\xfa=\ -l\x22\xad)4\x84\xe4\xdb\xd0\xac\xc8\x96$\x054m\ -I\xb3\xa0`,hJh\xc9'-X$\xa4\x01-\ -0\x13\x0a9q|f\xa0&A\xef&\x92FC\xa1\ -\x1a\x88F\x94-i\x8d\x10K#@\xda\x22\x9a\xa6\x90\ --i)D\xb4\xc8\x82dh$\x12rv\x91/\xb8\ -\x17&\xb8\x17P\x94\x1di\x89D`b\x19\xd5\xdb\x02\ -;\x92\xb4A\xe6\xdd\xbd\xc3O\x19\x8a\xfe\xcfLB4\ -l(\x82\xd3\xd9#\x16LB\x12p\xb8y6\x0eP\ -\x9b\xee\x19w@\x2233\x8a5\xac\xa9\xd7\x19'\xed\ -\x10\xb2\x1d\x8a\xcf.@,\x1f\xea\xa46\xd9\x0b)\x91\ -Y\x8f\x09\x9e:8\x98\x9bs\xcd\xf8\x80M\x04\xd3\x9b\ -\xc9\xca\x0c\xd1|{D1\xe40(\x96\xb6N\xb8e\ -C\xd5&\xdc;\xcb^/\xce\x01\x8f8AXs\x9b\ -\x84\x22\x0a\xb1\xb6\xb87B3\x0af]['\x1b$\ -\xb2\x11a\xd30\xc4\x9a\x8bH\x06F\xdd\x82\xb3N\xd6\ -z\x98\x18N\x13\x5cn\x0e\x98\xcb\x98SH8\x8c\xec\ -\xa9\x93@\x04\x16\x02\x199\xd2\xdd\xc3\xc3\xd3\xd3\xcb\xdb\ -{\x14\xce\xa3\x041\xda\xd1\xe7\xce89\xf6j\x1a.\ -\xbe3c|\xb0\x09\xfeOB\xac\xaf\xaf\xefX?\xd1\ -\xb8q\xe6\xf0\xaf\x10\x04\x9b\xf0#\xf2\xf3\xf5\xf7\xf7\xf5\ -\x0b\x18?a|\x80\x0b~\x8dM\x135&wl\xf2\ -\xf2\x02\xd2$\x08\x98&O\xe18\xc9h\xf8-\x22\xd0\ -i\xeaT\xa7\xc7 \x8e8\xd3p\x1e\xf7!\x9c9>\ ->\xbd\xdb\xc4\xce\x98\x09y\x22(888\xe8IJ\ -\xc7\xe4\xa7:\xf0\xd4\xac\xd9!\xb3C\xc7\xbbt\x9b\xe6\ -<\xfd\xf43\xf07'\xecYw\xf7\xf0\x88\x88\xb9s\ -\xbd\xe6zc\xd3\xf7\xd5\xd7\xf0\x95o>=\x90\xb3Y\xe2\xff\ -\xfa\xe1\x8e3-\x1f\xb6\xb6\xb5u~\xfb21\x5ce\ -\xaa-\xcc\x17Wm\xca\xaf\xa9\xc9\xdf\xb8!>\xe1t\ -\xadv-\xa0|7K$\x9b\xfd\xafC\xeb\xae\xd3\xba\ -\xeb\x13\xab:ph\xc2\xb2e\xa1/\x05t\xafO\xcd\ -\x9e\x9e\x9e\xcd2\x99,Sv-\x13\x0a\x15Q\x9fQ\ -^_&\xbd\xc8\xb1\x16|\xb7\xf7\xc6\xcd\xe3*\xd3\xf7\ -'rrr\x0e\xe5\xe6\xb6\xc4f\xb7\xc5&\xc6\xc9\xe5\ -\x09\x1aS\xaf\xeb\x13ti\xecX?J(\xfaA\xc4\ -\x22\xdd\xf5\x09\x1f\xf0\xf3C.\x01\xb9\x01MHk\xf2\ -\xf2\xf2\x0a\xaf\xab\x93\x95^+\x0do\xc7\xbd\xab\x97\xb6\ -\xb7\x97\xc5\xdfnZ}i\xdf\x8d\xb375\xa6\x1f\xaf\ -\x82\xe9\xa7\xdc\xdc\xe2\x8e\x9f\xe5\x9d\xad\x9d1\x9dQ\xfd\ -\x99\x9a\xba\xba\xba\x1a\xbb\xcew56\xc2C\xa3\xae\xa9\ -\xc9\xd5\xf5\xbc\xabk\x17\xdc~\xd1\x9a\xca\xbc\xd4\xf1\x86\ -]\xe5Wmn]3\x7f\xbbt\xf9w\x00\xdd<\xab\ -1\xad9x\xeb\xeat\x0av\x96\xbe\xf7\x16\x16c\xf0\ -\x0d\x9b~\xd15\xb1\x8d@r\x05\xb2\x8e\xa9\xdc\x1b\x07\ -\xefq\x93t\xd2\xb3\xb7\x80\xe7\x0f\x00%\xdd\xc4IR\ -\x99v]\xb9p\xf5\xea\xe7\x7f\x16\x15)\x8b\xe4\x90\xc5\ -\x9d\xd3am\xefw\x0ff\xf1\x8d\x85\x0d\x97\xbdm\x0f\ -\xc6o\xe2\xf4\xec\xc1\xed\xe18\x11xS\xd1I\x9cv\ -\x0fv\xfe\xe4k\x9d\xfc\x85\xf7\xe0@\xe7\xbf!>\xfb\ -\xbbS\x11\x18\xe83\xff\x1f\x22\xba\xf7\xfb!O\x97\xb9\ -\x00\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x82\xd2\ -\x00\ -\x00\x01\x00\x02\x00@@\x00\x00\x01\x00 \x00(B\x00\ -\x00&\x00\x00\x00\x00\x00\x00\x00\x01\x00 \x00\x84@\x00\ -\x00NB\x00\x00(\x00\x00\x00@\x00\x00\x00\x80\x00\x00\ -\x00\x01\x00 \x00\x00\x00\x00\x00\x00@\x00\x00\x12\x0b\x00\ -\x00\x12\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\ -\x01\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\x8e\x14\x00\x8d~\x0b\x01\x8d\xad%\ -\x12\x8d\xba-7\x8d\xc01f\x8d\xc53\x94\x8d\xc85\ -\xb9\x8d\xca6\xd5\x8d\xcb7\xe6\x8d\xcb7\xed\x8d\xcb7\ -\xec\x8d\xcb7\xe5\x8d\xca6\xd5\x8d\xc85\xbe\x8d\xc54\ -\x9d\x8d\xc11q\x8d\xbb-@\x8d\xaf&\x16\x8d\x8a\x12\ -\x02\x8d\x9b\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x01\xff\xff\xff\ -\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d^\x0e\x00\x8d\xf8C\ -\x00\x8d\xaa#\x0d\x8d\xbb-@\x8d\xc42\x87\x8d\xc96\ -\xc4\x8d\xcd9\xe8\x8d\xd0:\xfa\x8d\xd2;\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd2;\ -\xff\x8d\xd1;\xfc\x8d\xce9\xed\x8d\xca6\xca\x8d\xc43\ -\x8e\x8d\xbc.G\x8d\xae&\x12\x8d\x0b\x00\x00\x8d\x8f\x13\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x8df\x0e\x00\x8d\xffI\x00\x8d\xae&\x13\x8d\xbe/\ -\x5c\x8d\xc75\xb8\x8d\xce9\xf0\x8d\xd2;\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd2;\ -\xff\x8d\xcf9\xf4\x8d\xc96\xc4\x8d\xc00m\x8d\xb2(\ -\x1c\x8d\x16\x00\x00\x8d\x9a\x19\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\x00\x00\x00\x8d\xb7)\ -\x00\x8d\xa8#\x0a\x8d\xbc.U\x8d\xc85\xc0\x8d\xcf:\ -\xf8\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd0:\xfc\x8d\xc96\ -\xcf\x8d\xbe/f\x8d\xad%\x10\x8d\xc10\x00\x8d\x82\x0b\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\x9e\x1c\x00\x8d\x00\x00\x00\x8d\xb6*\ -,\x8d\xc43\xa4\x8d\xce9\xf4\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xcf:\xf9\x8d\xc64\xb3\x8d\xb8,9\x8d~\x08\ -\x01\x8d\xa0\x1d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x8d\xaa#\x00\x8d\x9d\x1b\x04\x8d\xbc.U\x8d\xca6\ -\xd7\x8d\xd2;\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd2<\xff\x8d\xcb7\xe2\x8d\xbe/\ -j\x8d\xa7!\x0a\x8d\xb1'\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xb0'\ -\x00\x8d\xa5 \x0a\x8d\xbf0v\x8d\xcd8\xee\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\xff\x8d\xd2:\ -\xff\x8d\xd2;\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3;\xff\x8d\xd2;\xff\x8d\xd2:\xff\x8d\xd2;\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xce9\ -\xf6\x8d\xc22\x91\x8d\xaf&\x13\x8d\xb7*\x00\x8d\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xb2(\x00\x8d\xa9\x22\ -\x0c\x8d\xc11\x88\x8d\xcf9\xf7\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd2;\ -\xff\x8d\xd3;\xff\x8c\xd6B\xff\x89\xdbN\xff\x86\xe2]\ -\xff\x83\xe8k\xff\x81\xecu\xff\x80\xeey\xff\x80\xedx\ -\xff\x81\xebs\xff\x83\xe7k\xff\x85\xe3`\xff\x89\xdcQ\ -\xff\x8b\xd6C\xff\x8d\xd3<\xff\x8d\xd2;\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd0:\xfc\x8d\xc43\xa2\x8d\xaf'\x16\x8d\xb8+\ -\x00\x8d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\xb1'\x00\x8d\xa5!\x0a\x8d\xc11\ -\x8a\x8d\xcf:\xf9\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd2;\xff\x8d\xd4>\xff\x89\xdcQ\ -\xff\x82\xe9o\xff}\xf5\x88\xffz\xfb\x97\xffx\xfe\x9e\ -\xffx\xff\xa1\xffx\xff\xa1\xffx\xff\xa2\xffx\xff\xa2\ -\xffx\xff\xa1\xffx\xff\xa1\xffx\xfe\x9f\xffy\xfc\x99\ -\xff|\xf6\x8b\xff\x82\xebr\xff\x88\xdeU\xff\x8c\xd5@\ -\xff\x8d\xd2;\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd0:\xfd\x8d\xc43\xa1\x8d\xad%\ -\x13\x8d\xb5)\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x8d\xaa#\x00\x8d\x92\x14\x04\x8d\xbf0x\x8d\xcf9\ -\xf8\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\ -\xff\x8d\xd3=\xff\x88\xddS\xff\x80\xef{\xffz\xfb\x98\ -\xffx\xff\xa1\xffx\xff\xa1\xffx\xff\xa1\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa1\xffx\xff\xa1\xffy\xfc\x9a\xff~\xf2\x82\ -\xff\x87\xe0Y\xff\x8c\xd4?\xff\x8d\xd3;\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd0:\xfc\x8d\xc22\ -\x91\x8d\xa6!\x0a\x8d\xaf&\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\x9e\x1c\ -\x00\x8d\xf0N\x00\x8d\xbc.T\x8d\xcd8\xee\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\xff\x8b\xd8G\ -\xff\x82\xebr\xffz\xfb\x98\xffx\xff\xa1\xffx\xff\xa1\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa1\ -\xffy\xfd\x9b\xff\x80\xeey\xff\x8a\xdaK\xff\x8d\xd3;\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xcf9\ -\xf7\x8d\xbf0p\x8de\x00\x01\x8d\xa8#\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d0\x00\x00\x8d\xbc-\ -\x00\x8d\xb5*)\x8d\xc96\xd4\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3;\xff\x88\xddR\xff}\xf4\x88\ -\xffx\xff\xa0\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa1\xff|\xf7\x8e\xff\x87\xe0Z\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xcc8\xe7\x8d\xb9,B\x8d\xc85\x00\x8d\xa2\x1e\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xae%\x00\x8d\xa3\x1e\ -\x09\x8d\xc43\x9f\x8d\xd2;\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x87\xe0Z\xff{\xf9\x92\xffx\xff\xa1\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa1\xffz\xfb\x98\ -\xff\x85\xe5d\xff\x8d\xd3=\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd2<\xff\x8d\xc75\xbd\x8d\xae%\x15\x8d\xb4)\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\xa5 \x00\x8d\xcd9\x00\x8d\xbc.\ -T\x8d\xce9\xf3\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\ -\xff\x87\xe0Z\xffz\xfa\x95\xffx\xff\xa1\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa1\ -\xffy\xfc\x9a\xff\x85\xe4c\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd0:\xfc\x8d\xc00q\x8d\xffy\ -\x00\x8d\xa4 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\xb3(\x00\x8d\xae%\x13\x8d\xc85\ -\xc0\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\xff\x88\xddR\ -\xff{\xf9\x93\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa1\xffz\xfb\x98\xff\x86\xe1[\xff\x8d\xd3;\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xca6\xd5\x8d\xb3)\ -\x22\x8d\xb7+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x8d\xa5 \x00\x8d\xcf9\x00\x8d\xbe/]\x8d\xcf:\ -\xf9\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3;\xff\x8b\xd7F\xff}\xf4\x87\ -\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa1\xff{\xf8\x90\xff\x89\xdbM\ -\xff\x8d\xd3;\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd1;\xfe\x8d\xc11\ -y\x8d\xffm\x00\x8d\xa5 \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x8d\xaf&\x00\x8d\xa6!\x0b\x8d\xc75\xb5\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3=\xff\x82\xeap\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xf6\x95\xffx\xfb\x9c\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa1\xff\x7f\xf0}\ -\xff\x8c\xd5@\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xca6\ -\xcf\x8d\xb1(\x1a\x8d\xb4)\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x8d\xbe/\x00\x8d\xba-<\x8d\xce9\xee\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd2;\xff\x88\xddS\xffz\xfb\x97\xffx\xff\xa1\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa2\xffz\xddx\xffy\xe6\x83\xffx\xff\xa2\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffy\xfd\x9c\ -\xff\x86\xe2]\xff\x8d\xd2;\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xcf:\ -\xfa\x8d\xbe/[\x8d\xcb7\x00\x8d\xa3\x1f\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xa8\x22\ -\x00\x8d\xff^\x00\x8d\xc32\x81\x8d\xd1;\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd4>\xff\x80\xef{\xffx\xff\xa1\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa2\xffz\xd9s\xff{\xc5[\xffx\xff\xa1\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa1\ -\xff~\xf3\x85\xff\x8c\xd6B\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd2<\ -\xff\x8d\xc64\xa5\x8d\x91\x14\x05\x8d\xad%\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xb1'\ -\x00\x8d\xaa#\x0e\x8d\xc96\xc0\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\ -\xff\x89\xdcO\xffz\xfb\x97\xffx\xff\xa1\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa2\xffz\xe0{\xff}\xa65\xffy\xf5\x95\ -\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffy\xfd\x9c\xff\x87\xe0Z\xff\x8d\xd2;\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xcc7\xda\x8d\xb5)\x1f\x8d\xb6*\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xbb-\ -\x00\x8d\xb9,/\x8d\xcd8\xe8\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\ -\xff\x83\xe8l\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa2\xffy\xe8\x85\xff~\x95 \xffz\xdcu\ -\xffx\xff\xa2\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa1\xff\x80\xeez\xff\x8d\xd4=\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xcf:\xf6\x8d\xbe/I\x8d\xc22\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xa0\x1e\x00\x8d\xc96\ -\x00\x8d\xc00Z\x8d\xd0:\xfb\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8c\xd5A\ -\xff}\xf4\x87\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa1\xffy\xee\x8c\xff~\x93\x1e\xff|\xb7I\ -\xffx\xfe\x9f\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa1\xff{\xf8\x91\xff\x8b\xd8G\xff\x8d\xd3;\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd1;\xff\x8d\xc32w\x8d\xdeC\x00\x8d\xa7!\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xa8\x22\x00\x8d\xf0N\ -\x00\x8d\xc43\x81\x8d\xd2;\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\xff\x89\xdbN\ -\xffz\xfb\x97\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa1\xffy\xf3\x92\xff~\x99%\xff~\x97#\ -\xffy\xf0\x8e\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffy\xfd\x9c\xff\x87\xdfW\xff\x8d\xd2;\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xc75\xa0\x8d`\x00\x02\x8d\xac%\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xac$\x00\x8dP\x00\ -\x02\x8d\xc75\x9f\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd2:\xff\x86\xe1]\ -\xffx\xfe\x9e\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa1\xffx\xf7\x97\xff~\xa0.\xff\x7f\x87\x0f\ -\xff{\xd2j\xffx\xff\xa2\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xff\x84\xe6h\xff\x8d\xd2;\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xca6\xbe\x8d\xa9\x22\x0b\x8d\xb0'\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xaf&\x00\x8d\xa0\x1d\ -\x07\x8d\xc96\xb4\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd2;\xff\x84\xe7i\ -\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xfb\x9b\xff}\xa87\xff\x7f\x83\x0b\ -\xff}\xae>\xffx\xfc\x9c\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa2\xff\x81\xedv\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xcc8\xd2\x8d\xb3)\x16\x8d\xb4)\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xb1'\x00\x8d\xac$\ -\x0d\x8d\xcb7\xc2\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\xff\x82\xebr\ -\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xfd\x9e\xff}\xb0@\xff\x7f\x84\x0c\ -\xff~\x92\x1d\xffy\xe9\x86\xffx\xff\xa2\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa2\xff\x7f\xf1\x80\xff\x8d\xd4>\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xcd8\xdd\x8d\xb7+\x1f\x8d\xb8+\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xb3(\x00\x8d\xb0'\ -\x10\x8d\xcb7\xc9\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x81\xedv\ -\xffx\xff\xa2\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa2\ -\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xff|\xb8J\xff\x7f\x85\x0d\ -\xff\x7f\x87\x0f\xff{\xc8^\xffx\xff\xa2\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa2\xff~\xf2\x83\xff\x8c\xd4?\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xce9\xe1\x8d\xb9,#\x8d\xba,\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xb3)\x00\x8d\xb1'\ -\x11\x8d\xcc7\xca\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x81\xedw\ -\xffx\xff\xa2\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa1\xffy\xe7\x84\xff{\xcfg\ -\xffy\xef\x8d\xffx\xfe\x9f\xffx\xff\xa2\xffx\xff\xa1\ -\xffx\xff\xa0\xffx\xff\xa1\xff|\xc0T\xff\x7f\x85\x0d\ -\xff\x7f\x85\x0d\xff}\xa43\xffx\xf8\x98\xffx\xff\xa1\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa2\xff~\xf2\x83\xff\x8c\xd4?\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xce9\xe0\x8d\xb9,\x22\x8d\xba,\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xb2(\x00\x8d\xae%\ -\x0e\x8d\xcb7\xc5\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\xff\x81\xebs\ -\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa2\xffz\xdbu\xff\x7f\x8b\x14\ -\xff~\x98#\xff|\xbaL\xffz\xe0z\xffx\xf8\x98\ -\xffx\xff\xa2\xffx\xff\xa3\xff{\xc9_\xff\x7f\x86\x0e\ -\xff\x7f\x87\x0e\xff\x7f\x8d\x17\xffz\xe1|\xffx\xff\xa2\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa2\xff\x7f\xf0\x7f\xff\x8d\xd4>\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xcd8\xdb\x8d\xb7+\x1e\x8d\xb8+\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xb0&\x00\x8d\xa5 \ -\x09\x8d\xca6\xb9\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd2;\xff\x83\xe7j\ -\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa2\xffz\xe2}\xff\x7f\x8d\x16\ -\xff\x7f\x86\x0d\xff\x7f\x86\x0d\xff\x7f\x8d\x17\xff}\xa64\ -\xff{\xccc\xffy\xf0\x8e\xff{\xd0g\xff\x7f\x88\x10\ -\xff\x7f\x87\x0f\xff\x7f\x86\x0d\xff|\xbdQ\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa2\xff\x81\xedw\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xcc8\xd1\x8d\xb3)\x15\x8d\xb5)\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xad%\x00\x8dn\x00\ -\x02\x8d\xc85\xa2\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd2:\xff\x86\xe1]\ -\xffx\xfe\x9e\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa2\xffy\xe8\x84\xff~\x90\x1a\ -\xff\x7f\x86\x0e\xff\x7f\x87\x0f\xff\x7f\x87\x0e\xff\x7f\x85\x0d\ -\xff\x7f\x88\x10\xff~\x99$\xff}\xa20\xff\x7f\x88\x10\ -\xff\x7f\x87\x0f\xff\x7f\x86\x0d\xff~\x9c)\xffy\xf3\x92\ -\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa1\xff\x83\xe8k\xff\x8d\xd2;\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xca7\xc0\x8d\xaa#\x0c\x8d\xb1'\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xa8\x22\x00\x8d\xf3P\ -\x00\x8d\xc53\x82\x8d\xd2;\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\xff\x89\xdbN\ -\xffz\xfb\x98\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa1\xffy\xec\x8a\xff~\x93\x1e\ -\xff\x7f\x86\x0e\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x86\x0e\xff\x7f\x86\x0e\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x8a\x12\xffz\xd7p\ -\xffx\xff\xa2\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffy\xfe\x9d\xff\x87\xe0Z\xff\x8d\xd2;\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xc85\xa5\x8d\x81\x0a\x03\x8d\xad%\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xa0\x1d\x00\x8d\xc96\ -\x00\x8d\xc01[\x8d\xd0:\xfc\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8c\xd6B\ -\xff}\xf5\x89\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa1\xffy\xf0\x8f\xff~\x97\x22\ -\xff\x7f\x86\x0e\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x85\x0d\xff|\xb3D\ -\xffx\xfd\x9e\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa1\xff{\xf9\x92\xff\x8a\xd9I\xff\x8d\xd3;\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd2;\xff\x8d\xc43\x7f\x8d\xeeL\x00\x8d\xa8\x22\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xbb.\ -\x00\x8d\xba-2\x8d\xce9\xeb\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x82\xeap\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa1\xffy\xf5\x94\xff~\x9c(\ -\xff\x7f\x86\x0d\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x86\x0e\xff~\x95 \ -\xffy\xed\x8a\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa1\xff\x80\xef|\xff\x8d\xd4>\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd0:\xf8\x8d\xbe/O\x8d\xc43\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xb1(\ -\x00\x8d\xad%\x11\x8d\xca6\xc6\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd2;\ -\xff\x88\xddR\xffz\xfc\x99\xffx\xff\xa1\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa1\xffx\xf8\x98\xff}\xa20\ -\xff\x7f\x85\x0d\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x10\ -\xff{\xcdd\xffx\xff\xa2\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffy\xfe\x9e\xff\x86\xe2]\xff\x8d\xd2;\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xcc8\xdd\x8d\xb6*\x22\x8d\xb7+\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xa9#\ -\x00\x8d\xff\xff\x00\x8d\xc43\x8c\x8d\xd2;\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8c\xd4?\xff\x7f\xf0~\xffx\xff\xa1\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xfb\x9b\xff}\xa87\ -\xff\x7f\x85\x0d\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x85\x0d\ -\xff}\xa98\xffx\xfa\x9a\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa1\ -\xff}\xf5\x8a\xff\x8b\xd7D\xff\x8d\xd3;\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xc64\xa8\x8d\x96\x17\x06\x8d\xad%\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x8d\xc01\x00\x8d\xbc.D\x8d\xce9\xf2\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd2;\xff\x88\xdeV\xffy\xfc\x99\xffx\xff\xa1\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xfd\x9e\xff}\xaf?\ -\xff\x7f\x85\x0d\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x86\x0e\ -\xff~\x90\x19\xffy\xe5\x81\xffx\xff\xa2\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xfe\x9e\ -\xff\x85\xe4b\xff\x8d\xd3;\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd0:\ -\xfb\x8d\xbf0`\x8d\xce9\x00\x8d\xa4\x1f\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x8d\xb1'\x00\x8d\xaa#\x0f\x8d\xc85\xbd\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd4>\xff\x81\xedw\xffx\xff\xa1\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xff|\xb6H\ -\xff\x7f\x85\x0d\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x8b\x14\xff\x7f\x88\x10\ -\xff\x7f\x85\x0d\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x86\x0e\xff|\xc2W\xffx\xff\xa1\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa1\xff\x7f\xf1\x81\ -\xff\x8c\xd5A\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xca7\ -\xd6\x8d\xb3)\x1f\x8d\xb6*\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x8d\xa7!\x00\x8d\xd4=\x00\x8d\xbf0c\x8d\xd0:\ -\xfb\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3;\xff\x8a\xd9J\xff|\xf6\x8c\ -\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa1\xff|\xbdP\ -\xff\x7f\x85\x0d\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x86\x0e\xff|\xb3E\xff{\xcdd\ -\xff}\xa54\xff\x7f\x8d\x16\xff\x7f\x85\x0d\xff\x7f\x86\x0e\ -\xff\x7f\x85\x0d\xff~\xa0.\xffy\xf5\x95\xffx\xff\xa1\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa1\xff{\xf9\x94\xff\x89\xdcQ\ -\xff\x8d\xd3;\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd1;\xff\x8d\xc22\ -\x86\x8d\x00\x00\x00\x8d\xa9\x22\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\xb4)\x00\x8d\xaf&\x17\x8d\xc85\ -\xc6\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\xff\x87\xdfW\ -\xffz\xfa\x96\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa2\xff{\xc4Y\ -\xff\x7f\x86\x0d\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x85\x0d\xff|\xb9L\xffx\xff\xa2\ -\xffx\xf8\x98\xffz\xdex\xff|\xb8K\xff~\x97#\ -\xff\x7f\x87\x0f\xff\x7f\x8a\x12\xffz\xdbu\xffx\xff\xa2\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa1\xffy\xfd\x9b\xff\x85\xe4b\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xcb7\xde\x8d\xb6*\ -,\x8d\xba-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\xa6!\x00\x8d\xd5>\x00\x8d\xbe/\ -^\x8d\xcf9\xf7\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x85\xe3`\xffz\xfc\x99\xffx\xff\xa1\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa2\xff{\xccb\ -\xff\x7f\x87\x0e\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x85\x0d\xff|\xb2C\xffx\xfe\x9f\ -\xffx\xff\xa1\xffx\xff\xa2\xffx\xfe\x9f\xffy\xee\x8c\ -\xff{\xcdd\xff}\xa53\xff|\xc3W\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa1\ -\xffy\xfd\x9d\xff\x83\xe8k\xff\x8d\xd4>\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd0:\xfe\x8d\xc11~\x8d\x08\x00\ -\x01\x8d\xa9\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xb1'\x00\x8d\xaa#\ -\x0e\x8d\xc54\xae\x8d\xd2;\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x85\xe3a\xffz\xfb\x97\xffx\xff\xa1\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa2\xff{\xd3k\ -\xff\x7f\x88\x10\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x85\x0d\xff}\xab;\xffx\xfc\x9c\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa1\ -\xffx\xff\xa2\xffx\xf8\x98\xffy\xf1\x8f\xffx\xfe\x9f\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa1\xffy\xfc\x9b\ -\xff\x83\xe7j\xff\x8d\xd4>\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xc85\xc5\x8d\xb1'\x1b\x8d\xb6*\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\x95\x15\x00\x8d\xc11\ -\x00\x8d\xb7+6\x8d\xcb7\xdf\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x87\xe0Y\xff|\xf7\x8e\ -\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa2\xffz\xd9s\ -\xff\x7f\x8a\x12\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x85\x0d\xff}\xa53\xffx\xf9\x9a\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa1\xffx\xff\xa1\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa1\xff{\xf9\x94\xff\x85\xe3a\ -\xff\x8d\xd4=\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xcd8\xec\x8d\xbb-K\x8d\xcc8\x00\x8d\xa6 \ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xa6 \ -\x00\x8d\xff\xff\x00\x8d\xbd/c\x8d\xce9\xf3\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\xff\x8a\xdaK\ -\xff\x80\xeey\xffy\xfd\x9b\xffx\xff\xa3\xffz\xe0{\ -\xff\x7f\x8c\x15\xff\x7f\x87\x0f\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x86\x0d\xff~\x9f,\xffx\xf7\x96\ -\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa1\ -\xffy\xfe\x9e\xff~\xf2\x81\xff\x89\xdcQ\xff\x8d\xd3;\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd0:\ -\xfb\x8d\xc01\x7f\x8d\x8d\x11\x04\x8d\xaa#\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x8d\xad%\x00\x8d\x9f\x1c\x07\x8d\xc11\x85\x8d\xcf:\ -\xfa\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\ -\xff\x8c\xd4?\xff\x87\xe1[\xff~\xf4\x86\xffz\xe4}\ -\xff~\x8e\x18\xff\x7f\x86\x0e\xff\x7f\x87\x0f\xff\x7f\x87\x0f\ -\xff\x7f\x87\x0f\xff\x7f\x86\x0e\xff~\x9a%\xffy\xf3\x91\ -\xffx\xff\xa1\xffx\xff\xa0\xffx\xff\xa0\xffx\xff\xa0\ -\xffx\xff\xa1\xffx\xff\xa1\xffy\xfe\x9d\xff}\xf4\x88\ -\xff\x85\xe3`\xff\x8c\xd5A\xff\x8d\xd3;\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd1;\xff\x8d\xc43\ -\xa4\x8d\xab#\x10\x8d\xb3(\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\xb3(\x00\x8d\xaa#\x0f\x8d\xc32\ -\x98\x8d\xd0:\xfc\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd2;\xff\x8c\xd6B\xff\x88\xd6L\ -\xff\x85\xab&\xff\x81\x94\x17\xff\x80\x8a\x11\xff\x7f\x87\x0f\ -\xff\x7f\x86\x0e\xff\x7f\x85\x0d\xff~\x94\x1f\xffy\xee\x8d\ -\xffx\xff\xa3\xffx\xff\xa1\xffx\xff\xa0\xffy\xfd\x9c\ -\xff{\xf9\x92\xff\x80\xef|\xff\x86\xe1\x5c\xff\x8c\xd6C\ -\xff\x8d\xd3;\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd1;\xff\x8d\xc54\xb3\x8d\xb1(\ -\x1c\x8d\xbc-\x00\x8d4\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xb5)\x00\x8d\xae%\ -\x13\x8d\xc32\x9b\x8d\xd0:\xfb\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3;\ -\xff\x8d\xd3<\xff\x8c\xcc8\xff\x89\xc01\xff\x87\xb3)\ -\xff\x85\xa9#\xff\x84\xa2\x1e\xff\x83\xa6&\xff\x7f\xe4q\ -\xff\x7f\xf1\x7f\xff\x81\xecv\xff\x84\xe6h\xff\x87\xdfX\ -\xff\x8a\xd8H\xff\x8d\xd4>\xff\x8d\xd2;\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd1;\xfe\x8d\xc54\xb1\x8d\xb2( \x8d\xbf0\ -\x00\x8d\x8d\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xb4)\ -\x00\x8d\xad%\x11\x8d\xc21\x8c\x8d\xce9\xf5\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd4=\xff\x8d\xd4=\ -\xff\x8d\xd3<\xff\x8d\xd2<\xff\x8d\xd2;\xff\x8d\xd4>\ -\xff\x8d\xd4=\xff\x8d\xd3<\xff\x8d\xd2;\xff\x8d\xd2;\ -\xff\x8d\xd3;\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xcf:\ -\xfa\x8d\xc33\xa0\x8d\xb0'\x1a\x8d\xbe0\x00\x8d\x85\x0c\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x8d\xb0'\x00\x8d\xa5 \x09\x8d\xbe/h\x8d\xcb7\ -\xe2\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xcd8\xed\x8d\xc01\ -~\x8d\xad$\x0f\x8d\xb4)\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d \x00\ -\x00\x8d\x1e\x00\x00\x8d\x0f\x00\x00\x8d\xff\x9a\x00\x8d\xb5*\ -@\x8d\xbe/\xe5\x8d\xcb7\xff\x8d\xd2;\xff\x8d\xd4<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd0:\xfd\x8d\xc85\xc7\x8d\xbb-M\x8d\x9f\x1c\ -\x04\x8d\xaa#\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xad%\x00\x8d\xa2\x1e\ -\x04\x8d\xc00g\x8d\xc74\x9a\x8d\xc43s\x8d\xb9,\ -e\x8d\xad%\xe7\x8d\xae&\xff\x8d\xb8+\xff\x8d\xc53\ -\xff\x8d\xcf:\xff\x8d\xd3<\xff\x8d\xd4<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd1;\xfe\x8d\xcb7\ -\xdc\x8d\xc11{\x8d\xb1'\x1a\x8d\xffS\x00\x8d\x7f\x0f\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xb6*\x00\x8d\xb4)\ -\x1d\x8d\xcb7\xd9\x8d\xd3<\xff\x8d\xd1;\xfe\x8d\xcf:\ -\xfa\x8d\xca7\xfe\x8d\xc43\xff\x8d\xbd.\xff\x8d\xb8+\ -\xff\x8d\xb9,\xff\x8d\xc00\xff\x8d\xc85\xff\x8d\xce9\ -\xff\x8d\xd2;\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd0:\xfb\x8d\xca7\xd4\x8d\xc21\x80\x8d\xb5*\ -'\x8d\x8b\x10\x02\x8d\x9f\x1c\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\xbf0\x00\x8d\xbc.\ -<\x8d\xcf9\xf1\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd4<\xff\x8d\xd4<\xff\x8d\xd3<\xff\x8d\xd2;\ -\xff\x8d\xcf9\xff\x8d\xca6\xff\x8d\xc64\xff\x8d\xc43\ -\xff\x8d\xc43\xff\x8d\xc64\xff\x8d\xc85\xff\x8d\xcb7\ -\xff\x8d\xce9\xff\x8d\xd0:\xff\x8d\xd1;\xff\x8d\xd2<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd1;\xff\x8d\xcf:\xf7\x8d\xcc8\xdc\x8d\xc74\ -\xa9\x8d\xbf0_\x8d\xb3(\x1d\x8d\x86\x0d\x01\x8d\x99\x19\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\xa1\x1e\x00\x8d\xcd8\x00\x8d\xc11\ -d\x8d\xd1;\xfe\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd4<\xff\x8d\xd4<\xff\x8d\xd3<\ -\xff\x8d\xd1;\xff\x8d\xcf9\xff\x8d\xcb7\xff\x8d\xc75\ -\xff\x8d\xc32\xff\x8d\xbf0\xff\x8d\xbd/\xff\x8d\xbc.\ -\xff\x8d\xbb.\xff\x8d\xbc.\xff\x8d\xbd.\xff\x8d\xbc.\ -\xed\x8d\xc01\x81\x8d\xbe/H\x8d\xb5* \x8d\x9f\x1d\ -\x06\x8d\xcb4\x00\x8dc\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\xaa#\x00\x8d\xff\x99\x00\x8d\xc54\ -\x8f\x8d\xd2;\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd2;\xff\x8d\xcf9\xff\x8d\xca6\ -\xff\x8d\xc43\xff\x8d\xbd/\xff\x8d\xb6*\xff\x8d\xb1'\ -\xd7\x8d\xb2(8\x8d\xa9\x22\x0f\x8d\x85\x10\x02\x8d\xa7\x1b\ -\x00\x8d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\xae&\x00\x8d\xa3\x1f\x09\x8d\xc96\ -\xb8\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd4<\xff\x8d\xd3<\xff\x8d\xd2;\xff\x8d\xcf:\ -\xfc\x8d\xcd8\xe8\x8d\xca7\xcd\x8d\xc54\xa5\x8d\xbb-\ -;\x8d\xc95\x00\x8d\xa8\x22\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\xb6*\x00\x8d\xb5*\x1d\x8d\xcc8\ -\xd9\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd2;\xff\x8d\xc43\ -\x8b\x8d\xffe\x00\x8d\xa9\x22\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\xbd/\x00\x8d\xbc.5\x8d\xce9\ -\xef\x8d\xd4<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd1;\xff\x8d\xc32\ -r\x8d\xd8?\x00\x8d\xa7!\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\xb8,\x00\x8d\xb7+\x1f\x8d\xc75\ -\xb7\x8d\xce9\xe9\x8d\xd0:\xf9\x8d\xd2;\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xcf:\xf6\x8d\xbe/\ -I\x8d\xc32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8d\x8e\x13\x00\x8dZ\x00\x00\x8d\xae&\ -\x10\x8d\xb9,-\x8d\xbf0P\x8d\xc33z\x8d\xc75\ -\xa5\x8d\xcb7\xcb\x8d\xcd9\xe7\x8d\xd0:\xf9\x8d\xd2;\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xcd8\xe3\x8d\xb8+\ -'\x8d\xb9,\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x8dL\x00\x00\x8d\xda3\x00\x8d\x93\x17\ -\x04\x8d\xaf&\x12\x8d\xb9,,\x8d\xbf0O\x8d\xc32\ -y\x8d\xc75\xa4\x8d\xca7\xc9\x8d\xcd8\xe6\x8d\xd0:\ -\xf8\x8d\xd1;\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xca6\xc5\x8d\xac$\ -\x0f\x8d\xb1'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d2\x00\x00\x8d\xd9.\ -\x00\x8d\x90\x16\x03\x8d\xaf&\x11\x8d\xb9,*\x8d\xbf0\ -M\x8d\xc32w\x8d\xc75\xa2\x8d\xca7\xc8\x8d\xcd8\ -\xe5\x8d\xd0:\xf8\x8d\xd1;\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xd3<\ -\xff\x8d\xd3<\xff\x8d\xd3<\xff\x8d\xc74\x9f\x8d^\x00\ -\x02\x8d\xac$\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\ -\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8d\x0d\x00\ -\x00\x8d\xd6)\x00\x8d\x8c\x15\x03\x8d\xae%\x11\x8d\xb9,\ -)\x8d\xbf0K\x8d\xc32u\x8d\xc75\x9f\x8d\xca7\ -\xc5\x8d\xcd8\xe3\x8d\xcf:\xf5\x8d\xd1;\xfe\x8d\xd2<\ -\xff\x8d\xd3<\xff\x8d\xd1;\xff\x8d\xc32s\x8d\xd9@\ -\x00\x8d\xa6 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ -\x01\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x8d\x00\x00\x00\x8d\xce%\x00\x8d\x89\x13\x03\x8d\xad%\ -\x12\x8d\xb8,-\x8d\xbe0T\x8d\xc32\x83\x8d\xc74\ -\xb4\x8d\xca7\xe2\x8d\xc85\xe0\x8d\xba-<\x8d\xbe/\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x01\x7f\xff\xfe\ -\x00\x00\x7f\xff\xfe\xff\xff\xf8\x00\x00\x1f\xff\xff\xff\xff\xe0\ -\x00\x00\x07\xff\xff\xff\xff\x80\x00\x00\x01\xff\xff\xff\xff\x00\ -\x00\x00\x00\x7f\xff\xff\xfc\x00\x00\x00\x00?\xff\xff\xf8\x00\ -\x00\x00\x00\x1f\xff\xff\xf0\x00\x00\x00\x00\x0f\xff\xff\xe0\x00\ -\x00\x00\x00\x07\xff\xff\xc0\x00\x00\x00\x00\x03\xff\xff\xc0\x00\ -\x00\x00\x00\x01\xff\xff\x80\x00\x00\x00\x00\x01\xff\xff\x00\x00\ -\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\ -\x00\x00\x00\x00\x7f\xfe\x00\x00\x00\x00\x00\x00\x7f\xfc\x00\x00\ -\x00\x00\x00\x00?\xfc\x00\x00\x00\x00\x00\x00?\xfc\x00\x00\ -\x00\x00\x00\x00\x1f\xf8\x00\x00\x00\x00\x00\x00\x1f\xf8\x00\x00\ -\x00\x00\x00\x00\x1f\xf8\x00\x00\x00\x00\x00\x00\x1f\xf8\x00\x00\ -\x00\x00\x00\x00\x0f\xf0\x00\x00\x00\x00\x00\x00\x0f\xf0\x00\x00\ -\x00\x00\x00\x00\x0f\xf0\x00\x00\x00\x00\x00\x00\x0f\xf0\x00\x00\ -\x00\x00\x00\x00\x0f\xf0\x00\x00\x00\x00\x00\x00\x0f\xf0\x00\x00\ -\x00\x00\x00\x00\x0f\xf0\x00\x00\x00\x00\x00\x00\x0f\xf0\x00\x00\ -\x00\x00\x00\x00\x0f\xf8\x00\x00\x00\x00\x00\x00\x0f\xf8\x00\x00\ -\x00\x00\x00\x00\x1f\xf8\x00\x00\x00\x00\x00\x00\x1f\xf8\x00\x00\ -\x00\x00\x00\x00\x1f\xfc\x00\x00\x00\x00\x00\x00\x1f\xfc\x00\x00\ -\x00\x00\x00\x00?\xfc\x00\x00\x00\x00\x00\x00?\xfe\x00\x00\ -\x00\x00\x00\x00\x7f\xfe\x00\x00\x00\x00\x00\x00\x7f\xff\x00\x00\ -\x00\x00\x00\x00\x7f\xff\x00\x00\x00\x00\x00\x00\xff\xff\x80\x00\ -\x00\x00\x00\x01\xff\xff\xc0\x00\x00\x00\x00\x01\xff\xff\xc0\x00\ -\x00\x00\x00\x03\xff\xff\xe0\x00\x00\x00\x00\x07\xff\xff\xf0\x00\ -\x00\x00\x00\x0f\xff\xff\xf8\x00\x00\x00\x00\x1f\xff\xff\xfc\x00\ -\x00\x00\x00?\xff\xff\xff\x00\x00\x00\x00\x7f\xff\xff\xf0\x00\ -\x00\x00\x01\xff\xff\xff\xf0\x00\x00\x00\x03\xff\xff\xff\xf0\x00\ -\x00\x00\x0f\xff\xff\xff\xf0\x00\x00\x00\x7f\xff\xff\xff\xf0\x00\ -\x00\x00\xff\xff\xff\xff\xe0\x00\x00\x00\x7f\xff\xff\xff\xe0\x00\ -\x00\x00\x7f\xff\xff\xff\xe0\x00\x00\x00\x7f\xff\xff\xff\xe0\x00\ -\x00\x00\x7f\xff\xff\xff\xf0\x00\x00\x00\x7f\xff\xff\xff\xff\x00\ -\x00\x00\x7f\xff\xff\xff\xff\xf8\x00\x00\x7f\xff\xff\xff\xff\xff\ -\xc0\x00\xff\xff\xff\x7f\xff\xff\xfe\x00\xff\xff\xfe\x89PN\ -G\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x01\ -\x00\x00\x00\x01\x00\x08\x06\x00\x00\x00\x5cr\xa8f\x00\x00\ -@KIDATx\xda\xed\xbdy\x9c\x1d\xd7]\xe0\ -\xfb=\xe7T\xd5\xdd{Swk\x97%\xb5\xb5x\x89\ -\xb3\x98,$\x84\x90\x0d\xcf\x0c\x90G 1\x84\x10\x18\ -`X'\x0cy\x13\xf6\xc9\x83\x07\x0c[\x86\x1e\x96\xc0\ -\x10\xde0\x90\x04\x02D&\x0b\x09L /\x84\x00!\ -!\x89cg\xb3\xbcE\xb6dY\x96\xb5t\xb7\xba\xfb\ -n\xb5\x9cs\xe6\x8f\xba\xb7\xd5\x92ZRK\xba\xb7\xaa\ -\xee\xed\xfa~>mkiu\x9d[U\xbf\xf5\xfc~\ -\xbf#\xc8\xc9\xb9\x01\xb6\xbc}\x16\xbf\x01#\xd3\x14\xa4\ -b\x02\xc1\x0e!9 %wH\xc5\xcdB1\x82E\ -\xea\x90'\x8d\xe1\xddB\xf01 \ -\xe5\xb2\xb5\xe3\xc6{\x8e\xdb\x11v\x07\x84\xbc\xf1\xeb7\ -\x17`\xe9\x14oQ\x1e\xbf\x1c\xf9y\x18\x906\xce\x8d\ -\xff\x88\x9c\xac\xb2\xe3\x8fg\xb1\x1a\xe1\x96(\x0a\xc1\xb8\ -\x90\xec\x10\x82\xbd\xc5\x1a\xb7I\xc5\x01\xe92\xa3\x1c\xb6\ -9\x1e\xe3\xca\xc3s<\x84t@\xf5H\xd8\xd7By\ - \x14\xb7\xb7\x97\xf0\x9c\x02A\xda\xf7h\xa3\x93+\x80\ -!\xe1\xa6?\x99\xc5Z\x84\x94\x14\x85dBH\xb6\x0b\ -\xc1>\xa18 %\xb7I\x97\xdd\xcaa\x87\xe31\xaa\ -<\x0a\x8e\x07]7\xbe_\xc2\xbe\x16\x8e\x0b\xcaa\xaf\ -\x90\x8c\x02g\xd2\xbeo\x1b\x9d\x5c\x01\x0c \xbb\xde\xd5\ -\x11vEQJ6u\x84}\xbfP\x1c\x90\x8a[\xa4\ -\xc3^\xe5\xb2\xcdq\x19s<<\x95\x92\xb0\xafE\xc7\ -\xc3\xd8)$\xdb\xc9\x15@\xea\xe49\x80\x0c\xa3\xde<\ -\xcb\xee\xe7Cs\x01Q\xa8P\x12\xb1\xb0\xef\x10\x82\x9b\ -\x85\xe2V\xa9\xd8/\x1dnv\x5c\xb6(\xaf#\xecn\ -v\x84\xfdr,\x9d\xa2U\x9f\xe3\xf5B\xf0~\xc8\xf3\ -\x00i\x92{\x00\x19b\xe7;f1\x1a\xe1\x16)\x0b\ -\xc1\x84\x90\xec\x02f*\x9b\xb8M*\xf6\xab8\x1b\xbf\ -Ey\x8c\xaeXv'\xdb\xc2\xbe\x16\x8eGIHn\ -U.\xef\xd7y\x16 Ur\x05\x90\x12\xbb\xdf\x1do\ -\xbdI\x87\xb2\x90l\x12\x82\x9dB\xb2_H\x0eJ\xc5\ --*\x8e\xd9\xb7)\x8f\xda \x0b\xfb%\x888\x11(\ -\x15\xb7\xb4\x97p\xdd\xe2\xf9=\xe9\x9c\xe4\xc9\x15@\x02\ -\xec\xfe\xd3Y\x8cF\x0aEIJ\xa6\x84\x8c\xcbe\x9d\ -B\x1c\xb3+\x87\xdd\xcac\xabr\x19q\x0a\xb8\xca\x1d\ -\x12a_\x0b\xdb\x09Q\x1cn\x96\x8aQ\xe0l\xdaK\ -\xda\xc8\xe4\x0a\xa0\xc7\xeczW,\xec\x8eGYH&\ -\x85`\x97\x90\xecs\xe2}\xf6\xfd*N\xd0mV.\ -\xa3\x8e\x87\xe3xqbl(\x85\xfd2H\xb5\x92\x08\ -\xdcF\xae\x00R%O\x02^'\xd3\xbf=Ku+\ -\x04\x0d\xa4\xe3Q\xed\xb8\xf17\x09\xc9\xcdBr[G\ -\xd8\xf7(\x97\xcd\x8e\xc7\x88\xf2p\x9cNA\x8dt`\ -\xa3\xb7\x5ct\x12\x81\xaf\x13\x82\xbf\x82<\x11\x98\x16\xb9\ -\x07\xb0\x1e\xbes\x96\x99WC\xd8@JEUH\xa6\ -\x84`\xa7\x80\x83\xa5\x11\x0e\x88\xd8\x8d\xdf\xa5\x5c\xb6:\ -\x1e\xd5\x15a\xeff\xe37\xb8\xb0\xaf\x85\xe3Q\x12\x82\ -\xdb\xa4\xe2\xafL^\x88\x9e\x1a\xb9\x02\xb8\x88M\xbf5\ -\xcb\xd4^h\x9eCIEEH\xa6\x85`'\x9a\x83\ -^\x99\x83\xca\xe1\xa0t\xb8\xc9\xf1\x98V.5\xc7\xc3\ -\xe9&\xe8D.\xec\xeb\xe3|\x22\xf0\x80\xdf\xc0q\x8b\ -Di/i\xa3\xb2\xa1\x15\xc0\xb6\xff9\xcb\xe9\xc7a\ -\xeb~\x94rW,\xfb\xee\xb0\xcd>\xaf\xc2mJ\xb1\ -O:\xecq<\xa6:\xc2\xae.\x11v\xc1\xaa\xd6\x8a\ -\x9cuq>\x11x@:\x8c\x91\xe7\x01Rc\xc3(\ -\x80\x89\xdf\x98\xe5\x05?\x00_z\x1fJy\xd4\x84`\ -J\x08\xf6\xdc\xf4,\xf6\xc9NQ\x8dr\xb9I\xb9L\ -+\x8f\xaa\xe3\xa1\xba\xd9\xf8+\x0a{.\xfc\xd7\x85T\ -\xa0\x5c\xb6\x0b\xc1\x16r\x05\x90\x1aC\xaa\x00f\xd9{\ -\x08t\x80#$U!\xd8,\x047=\xfc\xb7\x1c,\ -\xd6:[o.;;\xc2^\xc9\x85=y\xa4\x04\xc7\ -c\x93\x90\xdc\x8c\xe0\xcbi\xafg\xa32\xf0\x0a`\xcf\ -\x9f\xcd\x12E\xa0\x14\x0eP\x15\x92-Bp\x93\x80\x83\ -^\x89\x83\xd2\xe1@'A7\xd5\xb1\xec\xf2\x12a_\ -\x8b\x5c\xd8\xfbK\x9c\x07(\x09\xc1\x01\xa5`\xe6\xd0l\ -\xbe\x13\x90\x02\x03\xad\x00\xf6\xbeg\x16\x01c\xae\xe2e\ -R\xf1\xf5\xcae\x7f\xa7\xa8f\x93\xe3RU\x1e\xd2\xf1\ -:\x99\xf8\xb1\xfb\xdd\xb3s:\ -\xa4%\x1d\xec\xb1\xef\xcc\xc3\x81\xac2\xf0\x11\xf3J\xf7\ -\x98\xe5\x07k\xd3\xfc~ur\xf0?\xd3\x15\xb9\x9ce\ -\xefX\xf5\x0bbv{^H\x858?,U\xc8\xb8\ -\x0bOtN$\xee\xfe^\xae\xfa\xfd\xca\x97\xb8p\xc8\ -\xea\xaa\xcb\x9f\xdf\xef\xb7\x1d\xa5\xd3\xc6F\x01\x8b:\xe2\ -\xb8\xd1g5\xf7\x1b\ -\xc3\xfd\xc01k\x98\x07\xcc\xd1\xbc\x0e 5\x86BT\ -f\x0e\xcdb\x22v\x17G\xf8\xe8\xf8\x0ef\x062\x0f\ -p\x91\x1b\x7f\x89\xb0wcv\x13\x0b\xbb\x10\xe7\x05\xda\ -\xf1\xe2\x11\xdb\xdds\x0b\xa5\xb3\xca\x9a\xafr\xe1\xe3\x1f\ -\x9e\xfcG\xb3fU\xcd@\x9b(\xf49\xab\x03\xbe\xa2\ -C>i\x0c\x9f\xb4\x86\xfb\xb0\x9c\x02\x02\x04<\xfe\xba\ -\x5c!$\xc5\xd0(\x00\x1dR\xf5\xca|`|\x07/\ -w\x8bi\xaf\xe8*\xac\x12v\xa3WU\xd1\x05\xab,\ -{\x10\x0b;6v\xd5UG\xd0\x1d\x0fT\xa1#\xf0\ -\xab\x04}\xe5\xe7B\xe6s \xdd\xcf\x1b\xb6\xb1A\x93\ -F\x14\xf0X\x14p\xaf\xd1\xfc\xffX>m4O\x08\ -\x89\xce\x15A\xff\x19\x1a\x05\xf0\xe3\xaf\x85\xd9\xf7\xf2\xb6\ -\xb1m\xbc\xb18Bv\x84`\xbd\xc2\xae;\x96=\x9e\ -\x90\x83\xe3\x81[\x04\xa7\xd09A\xc7\x89\xddv\x86\xec\ -\xc0\x93\xae\xb7\x13\xb6\xc0o\xd2\x0a\x9b<\xaaC>f\ -4\x1f\xb1\x96\xcf\x0a\xc1\x9c\xb5\xd8\x5c\x19\xf4\x87\xa1P\ -\x00\x007\xdf3\x8b5\xfcHm\x9a\xdf\xabN\xa6\xb4\ -\x88\x8bc\xf6p\x8d\xad\xb7\xe8\xc2\xa2\x1a\x11\xcf\xc4\xc3\ --\x9e\xffR\xdd\xe3\xcc\x86L\xd8\xafFg6\x00A\ -\x0b\x82:\x8ba\x9b\xcf\xeb\x90\xf7[\xcb\x87\x81\xa3@\ -\xf0\xd8\xb7\xe7\x8a\xa0\x97\x0c\x8d\x02\xe8&\x02+\x13|\ -ptK\x02\x89\xc0\xb5b\xf6`\x0da\xefl\xbd\xad\ -\xce\xc6+7\xb6\xec^)\xfeR\x05P\x8a\xa1\xb3\xee\ -7\x82\xd1\x10\xb6\xa1\xbdL\x184y<\x0a\xf8\xa8\xd5\ -\xdc\x03|\x16A\xc3Zx0\xb1\x83\x97;\ -\x85\x1b\xb8\x1bk\x09\xbb\x7f\xbe\x11f\xb5e_\xb3\xa2\ -\xa6SQ\xa7\x5c\xf0\xcaP\xa8v,\xbd\xc3\x10\xdd\xf1\ -\xe4\xd1!\xf8\x0d\xf0\xeb4\xfc&\x9f0\x11\xef\xc4\xf2\ -\x91\xa0\xc5\x9c[\xc8\x15\xc1\xf504\xaf\xe3\xcc\xa1Y\ -\x1as\x88\xea$\xbf3\xb2\x857\x96\xc7Y\xb7+\xbd\ -\xbaz\xce\x9a\x8ee\x0f@\xaf\x8e\xd9-\x5c\xb66~\ -\x15R\xc5\x96\xbeX\x05\xaf\x12\xef\xc3#\xd7\xbf\x96\x9c\ -\xabct\xac\x08\xdaK4\x82&\x9f\xd2\x11\x7f\x8e\xe5\ -\x83Bq\xd6j\xc8\xf3\x04\xebgh\x14\x00t\xf2\x00\ -\x96W\x17*\xbcst\x0b5\xa7\xbb\x1d\xb8J\xf8\xba\ -\xc2\xae\xa3\x95\x0c|;\x0a8\xa7C\x9e\x8c\x02\x0a\xd6\ -r;\x16\xb1f\xc7\xdbZt\xbe\xcf\xf1bK_\xac\ -\xc5.~\xee\xde\xf7\x1f\xa3!hBk\x91v\xd0\xe4\ -\xe3:\xe2\x1dX>\x82`\x01\xe0\xb1o\xcb\x15\xc1\xd5\ -\x18:\x05\x80\xa5\x86\xe0-^\x89\x1f*\x8d1\xe2x\ -\xb1\xf5\xd6!6\x0a\x08t\xc0\xbc\x0eyJG}\xd7\xda\x17\xaa\xb9\xb5\xcf\x12F\ -C{\x09\x9a\x8b\x9c\x09\xdb\xbc\xdf\x1a\xfeP\xc0}\x16\ -t\xae\x08\xd6f\xe8\x14@\x97\xb1\xffw\x16\xab\xc1\x19\ -G\x08\x09\xd2\xc1Z\x0bg~t\xed\x17\xa1\xd3T\xb4\ -\x03\xf8\x1b\xe0\x8e\x8b\xff^\xaa8\xa1W\xac\xc5\xb1\xfd\ -JB/\x8f\xed3\x87\x0e\xa1\xb5\x88m-r4\xf4\ -y;\x96?\x93\x8a\x13\xd6`\x8f\xe4\x8a\xe0\x02\x86V\ -\x01\x5c+\x1d\x05P\x02\xde\x0d\xbc\xba\xfb\xe7\xca\x8b\x13\ -z+\xb1\xbd\x22\x17\xfa\x01!hAs\x81\xc0\xafs\ -\x9f\x8e\xf8\x0db\xe5\x9e\x87\x05\xab\x18\x8ay\x00=$\ -\xc2\xf2\x14\x22.\xd4)\x8d\xc6\x82\xef\xac\xce\x08\xe4\xc2\ -?0x%p\x0bx\xed:/h\xcc\xf3\x07a\x8b\ -C\xd6\xf2;7\xbfw\xf6!\xab\xf3\xb1e\x90{\x00\ -\xcc\xbcg\xb6+\xd3\x93\xc0\xf7;\x1e?T\x1egW\ -i$O\xea\x0d\x13:\x80\xc6\x02\xb6\xb5\xc4\xfd:\xe4\ -\xad\xc0_\x91{\x03\x1b[\x01t\x12\x86\xd2\xc2\x0b\xa5\ -\xe2'\x0a\x15\xfeMe\x02\xcf+\xa7\xbd\xb2\x9c~`\ --\xf8\xcbP\x9fg!l\xf1.k\xf9-,G\x85\ -\xdc\xb8\xde\xc0\x86T\x00\x1d\xc1\xc7B\x0d\xf8v\xc7\xe3\ -\xa7*\xe3\xcc\x94\xc6\xe2d_\xcep\x13\x05\xd0\x98C\ -\xb7\x96\xf8'\xa3\xf9y!\xf8$\x16\xbd\x11\x13\x84\x1b\ -N\x01\xcc\x1c\x9a\xc5\xf1 \xf4\xd9+\x04?\xe5U\xf8\ -\x8e\xea\x04\xd5B5\xed\x95\xe5$\x895\xd0<\x07\x8d\ -\x05\x8ej\x9f\xb7Zx\xa7\x80\xe6FS\x02\x1bJ\x01\ -\xacr\xf9_\xac\x1c~\xa98\xc2\x0b\xab\x9bPy\xac\ -\xbfq\xf1\x1bP?\xcbR\xd0\xe4\x7fY\xcbob9\ -\xbe\x91B\x82\x0d\xa1\x00V%\xfaJ\xc0\x1b\x1c\x8f\x9f\ -\xacL0S\x1e\xcb\x8bxr.\x08\x09\xfe\xceD\xfc\ -\xf4\xb9y\xbe4\xbe\x096\x8270\xf4\x0a\xa0\xd3\x1f\ -\x000)\x04\xff\xb7W\xe2\x8d\xd5)FVU\xff\xe5\ -\xe4`44\xe6\xa1\xb9\xc0}:\xe2\xe7\x84\xe0\xc3\x80\ -\x19vO`\xa8S^3\x87f1\x1a\x84`FH\ -\xdeZ\xac\xf1\x1fF6S\xf6Ji\xaf,'k\x08\ -\x19Wz*\x97\xad\x91\xcf\x8b\x8cfN\xc0\xc3\x13\xaf\ -\xbd+Z\xf8\xcb\x8f\xa4\xbd\xbc\xbe1\xb4\x0a\xa03!\ -\x08!\xb9S*\xdeV\x19\xe3\x1bG\xa6q\xd4\xba\xda\ -|r6\x22B\xac\x8cd\x1b\xd3\x01/1\x1ak\xe1\ -\xbe\x89\xd7\xde\x15\x0e\xab\x12\x18\xca\x10`\xe6\xd0,B\ - \x8c\xe1%\xca\xe1\xd7+\x13<\xaf2\x91\xc7\xfb\x17\ -\xb0z~\xe1e\xfej#\x13\xb6`\xe94\x8d\xa0\xc9\ -\xefZ\xcb\x7f\x03\xe6\x84\x18\xbe\xe4\xe0\xd0=\xeb\x99C\ -\xb3 \x10\xd6\xf0\xf5\x8e\xcb\xefT&\xd9_\x1e\xbb\xf4\ -X\xab\xa1e\xf5\xe9=v\xd5\xff\x0dXk\xe3\x93|\ -:\xbf\x16@\xb9\x08\x9ek\xe3\x11\x08\x16\x0c`\xac\xc0\ -\xd8\xf3\xff\xef\x9e\x02\xd4\xfd\xc1\x1b\xe5VF>,\x9f\ -!h/\xf3.k\xf9\x7f\x04<\xcd\x90)\x81\xa1z\ -\x96\x9d\x86\x1ei-\xafr\x5c~\xbd:\x15\x0b\xff\xb0\ -\xd2Uj\xd6vF\x97\x85\x06\x1dXB\xdf\xa2}K\ -\xd86D\x81E\x87\xb63\x04\xc5b\xb5\x8d\x0f\x171\ -\xa0\xa4\xe5\xdf\xbd8\xe2\x19\xfb\x0c\xc6\x80!\x16\xf8\xc8\ -\x08\x22#\x08\xb4\xa0\x11)\x9a\x91\xa4\x11J\x9a\xa1\xa4\ -\x19I\x02-\x09MG9t\x14\xc3P\xbdH\xab\xd0\ -!,\x9f&j-s\x8f5\xfc\x8c\x80c\xc3\xa4\x04\ -\x86\xe6\xb9] \xfc\x1e\xbfY\x9bfw\xa96L\x9f\ -\x90\x95\xf6\xe3xl\x99!lZ\xfc\xa6\xa1\xbd\xa4\xf1\ -\x1b\x86\xb0i\xd0\xa1EG`\x8d\xbd`f\xa1\xbd\xf4\ -\xc7\xe0:\xf0\x86o\x0a\xb9\xf3\xa0\xc1\x5c\xe1\xb2\x16\xd0\ -V\x10\x1a\x81\x1f\x09Z\x91d)P,\x05\x8a\xf9\xb6\ -\xc3R\xa0h\x84\x92\xa8\xa3\x14\x86M!\x98\x08\x96\xcf\ -\xa0\x9b\x8b\xbc\xdf\x1a~B\xc0\xd1aQ\x02C\xf1\x9c\ -:\xc2/\xac\xe5\x9b\x1d\x8f\xd9\xda4{J#i\xaf\ -\xaa\x07t\x04)\x9e\x97o\x09\x1a\x86\xe69MkQ\ -\xd3^\xd2\x84m\x8b\x89bA_\xf1\xd0\xaf\xe1\x89z\ -.|\xcf7\x87\xdc>se\x05p\xd1\x92V\xb0\xc4\ -\xdeBW!,\xf8\x8a\xb3-\x87\x85\x8eR\x08\x8d\x18\ -\x1a\x0f\xc1hX>\x83m\x9d\xe3C\xc6\xf0&\xe0\xf1\ -a\xc8\x09\x0c\xfasY\x1d\xf3\x7fc\xc7\xf2\xcf\x0c\xba\ -\xf0\x0b\x11\x1f\x0b\x16\xf9\x96\xd69Ms!\xa21\xaf\ -\x09\x1a\x86(\xec\x04\xeapCO\xcf\x02\xe5\x02|\xdf\ -\xb7\x86\x1c\xd8\xb5~\x05\xb0\xe6zW\xfdZ[hE\ -\x92\x85\xb6\xc3\xa9\xa6\xcb\xa9\xa6\xcb\x82\xafhEr\xdd\ -c\x16\xb3JG\x09\xe8\xd69\xfe\xda\x18\xfe\xb3\x80\xc7\ -\x06\xdd\x13\x18\xe4\xe7\xd1=\x0b\x00\xa1x\x99\xe3\xf2\xfb\ -\xb5i\xf6\x97F\xd3^\xd5\xf5\xd1\x15\x8e\xc8\xb7\xb4\x16\ -5\xcb\xa7;B\xdf4\xb1\x95\xb7\xbdMdZ\x0b\xb5\ -\x8a\xe5\x07^\x13\xb1g\xdb\x8d)\x80\x8bY=]=\ -\xd0\x82s\x81\xc3Su\x97\xe3\xcb\x1e\xf3m\x87\xc0\x88\ -\x0b\xbeo\x900\x1a\xeag\xb0\xcds\xbc\xcf\x18\xde$\ -\xe0\xc9AV\x02\x03;\x10de\x9f_\xf1b\xe5\xf0\ -\x9b\xd5\xc9\xc1\x13\xfen\x5c\xeeH\xcbDQc\x96C\ ->w\xbf\xa1q.\x16\xfa\x0b2\xef}\x90\x16\xd7\x81\ -\x82g{>\xe3d\xf5\xcf\xf3\x94es)d\xba\x14\ -r`\xbc\xcd\x99\x96\xc3\xb1\xa5\x02O5\x5c\x96\x035\ -p^\x81TP\x9d\x8a\xc3\xcd\xd6\x22\xba\xe3\x09\x9cH\ -{]\xd7\xcb@\x16\x02\xad\x94\xf7\x0anW\x0eo\xab\ -Nrge\x9c\x81y\x93\xba\x02Rr\x0c;\xaa\x01\ -\xcf\x9cl\xf1\xac\xe9&\x8d\xd3\x11\x9f\xff\x92\x88O\x05\ -\xa6\xff\x9fg\xb4\x06\xcf\xbf\xc3PN\xe84eOZ\ -\xc6\x0a\x9a\x1d\xb5\x90\xed\xd5\x90\xa2c\x09\xb5\xc0\xd7\x12\ -\xd3\xff\x8f\xdb3:U\x83RG\x1c\x8c|j\xd6\xf2\ -\xc9\x89\xd7\xde\xd5\x9a\xb8\xfb.\x16\xee\x19\xac\x82\xa1\x81\ -\xf3\x00:\xa3\xbf\x01f\xa4b\xb6<\xce\x0b\xcac\x0c\ -\xc4\xdb\xd3\x15\xfc\xaak\xd8Y\x0b\x98\x19m3Y\x8a\ -\xf0d\xbc'\xefw,bRkq\xdd\xd8\x0bH\xfa\ -\xf3Ka\xd9T\x8c\x98(F\x1c\x18oql\xa9\xc0\ -\x91\xc5\x02g[.\xda\x0e\xc4\xa3D*\xa8M\xa1\xac\ -\xe1\xdf\xb7\x97Y\xb6\x96_\x10PO{]\xd7\xca@\ -y\x00\x9dl?\x166\x09\xc1\xafT\xc6xum*\ -\x9e\xfa\x9bu,Pv\x0c\xfb\xc7\xda|\xd5\xe6&\x07\ -\xc6[\x8cz\x06\xb9\xeam\x7f\xf0q\xc9\xa3O\xc8D\ -\x04\xc0\x02\xd3\x13\x96\xe7\xde\xa6\x13U\x02\x17SP\x96\ -\xa9r\xc4\xcej@\xcd3\x84F\xd0\x8c\xd4@x\x04\ -R\x81[\xc0\x89|\x9e\xa1C\x96\x84\xe0s\x13\xaf\xbd\ -\xcb\x0c\x92\x170\x00\xa2s\x11\x96\x8a\x10\xfcl\xb1\xc6\ -\xb7W&\xb3/\xfc\x968\x0e\xde?\xd6\xe6e;\x97\ -x\xc1\xd6:\x9b\xcb!R\x5c\x18+\x1b\x0b\xad\xb6H\ -t\xe8h\xc1\x8bO%Ns\xcei\xf7\xda\x15\xd7p\ -\xebD\x8b\x97\xef\x5c\xe2y\x9b\xebL\x16#D\xb2\xb7\ -\xe3\xbap\x0aP\x9b\xa2\xe6\x16\xf9\xa9N\x0d\xca\x8a\xa1\ -\x1a\x042.>\xe7\xe9\xdcTe\xe1\xbb\xbc2\xdf_\ -\x9b\xc6S\x19\x0e`, \x05l\xab\x84|\xcd\xb6e\ -^\xb4\xad\xce\xb6\xca\xa5\x82\xdf\xc5Xh\x07\xc9\xbe\xf0\ -\xc5\x02\xc8\x8c\xbc\x01\xdd\xa3\x17\xcb\x8e\xe1\xf6\xc9\x16\xaf\ -\xbci\x89\xe7L5\x18\xf1t\xe6\x95\x80W\x86\xea$\ -\x9b\x95\xcb/\x85>/\xc4\x0e\x8e\x12\xc8\xc8\xe3\xbf2\ -\xdd\xa4\x9f\xb5|\x93[\xe0-\xb5IjN\x86\xbb\xfa\ -,0\xeai\xbejs\x83\x97\xee\x5cbf\xd4\xc7\x91\ -W\xce\xb6[\x03m?Y\xa7\xb7\xe8\xd9\x0bB\x90,\ -\xd0\xbdG5W\xf3\xac\xa9&/\xdf\xb9\xc4\x81\xf16\ -\x05\xd5\xfb\xdd\x8a^R\xaaAu\x82[\xa4\xe2\xe7\xac\ -e\xc7\xa0(\x81\xcc+\x80UI\xbfg(\x87\x9f\xab\ -N\xb2\xcd\xcb\xe80\x0fK\xbc\xa573\xea\xf3u;\ -\x97\xb8c\xb2I\xc51\xebzq\xb5\x01?Hn\xad\ -\x82ly\x00\x17c\x89\xb7>\xa7J\x11/\xdaZ\xe7\ -\xc5\xdb\x97\x99.E\xd9\xcd\x0b\x08(\x8dAq\x84W\ -\x08\xc9\x7f\xe9\x0c\x9c\xcd<\x19}\xfc\x17ba\xab\x94\ -\xfcRy\x9cg\x173\x5c\xe57V\xd0\x7fK\x83\xdb7\xb5\xf0d\ -\xefc\xceV{U\x17`\x02\xb8\x0e8Nz\x09\xb4\ -^;:\x96\xb8f\xe0k\xb6\xd5\xd93\xe2gfw\ -C\xc8\x95P\xe0u\xc0+\xb3\x16\x0adF\x01\xac\xba\ -)\xaf\xf2\xca|kV:\xfc,q\xf7\xde\xf3\xb7\xd4\ -\xd97\xde\xee[\xd6\xb9\xe5\xc7\xe5\xc0I\xe1\xb9\xf1W\ -\xd2H\x00\x0b\x8dv\xef?\xaf\x05j\x9e\xe6\xab\xb7\xd6\ -90\xde\xceL\x95\xa3\xe3Ae\x82\xa9N(\xb0#\ -\xed\xf5\xac&3\x0a\x00\xc0Zv)\x87\x1f\xac\x8cS\ -\xcd\xc2\x81\x9d\x16\xa8\xb9\x86\xaf\xdeZgf\xb4\x7fV\ -\xc5\x12\x17\x01\xd9\x84\x14\x80%\xde\x01\xf0\x9cd.(\ -\x88_4?\x80G\x8fK>\xf8\x8f\x0e\x9f\xb8O\xf5\ -\xe5~v\xbd\xb5\xe7n\xc9\x96\x12(\xd6\xa0P\xe5\x85\ -B\xf0\xbd\xd6\xe2d\xc5\x0b\xc8D\x14\xd8I\xfc)!\ -\xf9\xfeb\x8d\x17f\xe1\xdc\xbe\xd5/\xd2\xee\x11\xbf\xef\ -\xd7\xf2\x03\xd1\xf3\xb1_W\xc2s\xfb\xdb\x09(:_\ -\xda\xc2\xd9E\xc1\xc3\x8fK\xbe\xf0\x88\xe4\xf8II\xbd\ -\x09/{\xbe\xee\xdbg\x8d\xf35\x96\xe7n\xae\xa3\xa4\ -\xe5\xf0\x5c)Q\xefj\xcd\xfb\x11\x87\x02n\xd8\xe6{\ -\xc2\x16\x7f\x8b\xe03\xe9\xae(&\x13\x0a\xa0\xc33\x1d\ -\x8f\xef(\x8f\xa3\xd2n\xf1\xedn1=\x7fKl\xf9\ -\xfb\x8d1\xd0l_0\x01\xac\xef\x14\xbc\xfe\xf4\x01t\ -\x05\xbf\xe9\xc3\x13OK\xbe\xf4\xa8\xe4\xa1\xc7$g\x17\ -\x04Qg\x97C\x08\x18\xa9X$\xf4t\x16\xe1j,\ -\xf1\xac\x81gO5\xd0F\xf0\xf0B1u%\xe0\x16\ -\xa14\xc2n\x1d\xf0Fc\xf8\x91\x99C\xb3\xf5\xb4g\ -\x09\xa6\xae\x00:\xd6\xbf(\x14?P\x1ca\xaf\x9b\xd0\ -x\xaa\xcb\xd1\xb5\x1e\xcf\x99n\xb2\xb7\xcf\x96\xbf\x8b6\ -\xd0J\xb0\x13P\xd0\xdbN\xc0\xae\xd0G\x06\xe6:\xd6\ -\xfe\x8b\x8fH\x9e8)i\xb6;\xdf#\xce+\x1c\xcf\ -\x85m\xd3\xfd\x97\xc6\xee\xb3\xfc\xaa\xcd\x0d,\xf0\xf0|\ -1\xf5m\xc2\xd2(\xf8\x0d\xbe\xd1o\xf0\x01\xe0}3\ -\x87fS\x1d(\x9a\xaa\x02X\x89\x83\x04/q\x8b|\ -K9\x03C=\x1day\xe6T\x93\x83\xe3\xadD\xdc\ -qA\xec\x01$\xd9\x08\x04P*\xde\xb8\x07\xd0\xbd=\ -\xcd6\x1c;\x19[\xfb\x87\x8fJ\xe6\x16\x04\x91\xe9(\ -\x86\x8b\xee\xa1\xb1\xb0e\xd2\xb2sK2\xd5{\xdd\xed\ -\xdb\xe7L7\xf0\xb5\xe0\xf1\xc5tw\xe2\x94\x0b\xe51\ -\xc6#\x9f7\xea\x90O\x0a\xc9\xd3i\xae'u\x0f\xc0\ -\x1a\xaa\xd2\xe1\xfb\xca\xa3L\xa5}r\xaf\x14pp\xa2\ -\xcd-\x13\xadD\x93G\xc6\x80\x9f`\x1f\x80\x10P*\ -\xda\x95\x13\x82\xae\xe9\xdfr\xde\xda\x9fY\x10<\xf4\xb8\ -\xe4\xcb\x8fJ\x8e?}\x91\xb5\x17\x97\xff\xf7\x07\xf7\x18\ -j\xe5\xe4\xcaw-Pq\x0c\xcf\xdd\xdc\xa0\x1dI\x9e\ -j\xb8\xa9n0\x15k\xd0\xae\xf3\xd5\xadE\xbe\x19\xc1\ -\xdb\xd3\xf4\x02RS\x00\xab\xac\xff\xcb\xdd\x22//d\ -\xa0{z\xcf\x88\xcfs\xa6\x9b}\xd9\xe7\xbf\x12\x91\x16\ -\x04A\x82!@\xa7\x11\xe8Z\x14\x80\xec|o\xbd%\ -8zB\xf0\xe5\xafH\x1e9*\x99_\xecX{q\ -\xf5\x04\xa6\xb5P)Yn\xddk\xaeK\xf9\xdc\x08\xdd\ -!-\xcf\xdbR\xe7\x9f\x9e\xac1\xef;\xa9)\x01!\ -\xa1\ -K\x81\xe2\xbe\xd3\x95\xd4v\x06\x84\x84\xd2(\x9e\xdf\xe0\ -\xf5\x91\xcf\xfb\x80#i\xac#5\x0f\xc0\x1aF\xa5\xe2\ -\xbb\x8b5F\xd3\xec\xf3/(\xcb\xb3\xa6\x9al.\x87\ -\xa9\x08\xbf\x00\xc20\xb6\xb2I\xa1\xe4\xa5\xb3\x00\xba\xd6\ -^kx\xea\x8c\xe0\xa3\x9f\x8e\xad\xfd\xbb\xff\xc6\xe5\xde\ -\x07$\x0bKq\x9d\xc2z\xad\xfd\xc5X\x0bc5\xcb\ -\x81\xdd\xe9X\xff\x0b>\xbf\x80[&Z}\xaf\xef\xb8\ -\x1an\x09\x0a\x15n\x13\x92o\x0d[\x884\xbc\x80\xc4\ -=\x80\xd5\xd6\xdf+\xf1u\xc5\x14[}\x05pp\xbc\ -\xcd\xbe\xb1t_\x84 \x14D:\x99#t\xad\x05\xa5\ -\xecJ'`\xf7\x92\xdd\xd8\xfe\x8b\x8fJ\x1e=\x16\xc7\ -\xf6\xe6\x06\xac\xfdZ\xd7\x9d\xd9i\x98\x1cK\xbfw\xbf\ -\xbb=\xf8\xac\xa9&\x0bm\x87y\xbf?U\x89WC\ -\x08(\x8d\xe2\xf8\x0d^c\x0d\x7f\x0e\x1cOz\x0d\xa9\ -x\x00FS\x92\x8ao/\x8e0&S\x8a\xfd-\xb0\ -\xa5\x12r\xeb\xa6\x16J\xa4\xfbJ\xb6\x83N+pB\ -\xd7s\x9cx\x16@dbk\xff\xb1\xcf(\xfe\xe8\xfd\ -\x0e\xef\xfa\x90\xcb\xa7>\xaf8\xbb\xd0\xfb\xaaD\xcf\x8d\ -\xdd\x7f7#S\x9d,0Q\x8cx\xd6t\xba\x03E\ -\xdc\x22xe\xee@p\x17\x22\xf9F\xa1D\xc5\xaf\xfb\ -\xe1\x84\xe0\xab\x9c\x02/M\xab\xe4\xb7\xdb\xe0s\xe7t\ -\x83\x9a\x9b\xfe \x89\xb6\x9fl'\xa0\x14\xf0\xe81\xc9\ -?\xde+x\xf8\xa8d\xe1\x1a2\xf9\xd7\x83\xb505\ -a\x99\xd9\x91\xbe\xf5\xbf\x98\xdd#\x01\xa7\x1am\x0e\xcf\ -\x97R\xb9\xbe\x90P\xacQ\xf0\xeb\xbcF\x87\xbc_H\ -\xe6\x92\xbc~\xe2\x1e\x80\xd1\xb8R\xf1\xbab\x8d-i\ -5\xfct\x87H\xa4\x15\xf7\xaf\xc6\x92l'\xa0\x10\xb0\ -T\x17|\xf0\xe3\x0e\xffr\x7fl\xed\xaf%\x93\x7f\xbd\ -\x9f\xf1\xc0n\xc3h-{\x0a\xc0\x11\x96\xdb'[\xa9\ -\x0e\x13\xf1\xca\xe0\x95\xf8j\x04_\x0b\xc9z\x01\x89+\ -\x00!\xd8\xaf<\xee*\xa6\x94\xf9\xb7\xc0\xb6J\xc0\xc1\ -\x84\x8b}\xaeD\xdbO\xae\x13\x10\xe2\xd2\xe3 \xec\x9f\ -\xc5_\x8d\x05\xca\x85\xd8\xfd\xcfT\xeb\xe9\xaa\xf5\x8dx\ -\x9a\xdb6\xb5(\xa8tT\x80TP\xa81\x22\x15\xaf\ -2\x9aD\x8b\xe1\x13{&3\x87f\x91\x0a\x84\xe0\xdf\ -\x14*\xecN\xebd\x9f\x82\xb2\xdc\xba\xa9\xb5\xee\x03;\ -\xfa\x8d%\x0e\x01\x92T\x00\x89~>\x03\xdb7\x1bv\ -n\xce\xc6\xfd\xbe\x1c\xbbG|\xf6\x8c\xf8\xa9\xad\xb1P\ -\x01\xc7\xe3eBpK\x92\xd7MT)\xeb\x90-\xd2\ -\xe5\x9b\x0b\xd5\xf4\x8c\xc1\xdeQ\x9f\x1d\xd5\xf4]\xff.\ -\xd6\xc6!@V\xd6\xd3k\xa4\x84[g\x0c\xe5b\xb6\ -?\xa3\xdb\x99\xf5\x98V-\x88r\xa1Pa\xbb\x90|\ -\xfd\xd8\xd6\xe4\xc2\x80D\x04q\xd5\xd6\xdf\xd7xe\x9e\ -\xed\xa5\x90o\xe9N\x8b\xb9e\xbc\x85\x93r\xd6\x7f5\ -q#P\x06\x87\xdb\xf7\x00k\xa1V\xb1\x1c\xdcc\xb2\ -0\xdc\xe9\xcak\x056\x95\x22\xf6\x8f\xa57D\xa4P\ -E)\x87\x7f;\x7f\x82\xc9\xa4\xae\x99\x98%6\x9a\x82\ -T|c\xb1B%\x8d~\x7f\x01\xdc<\xe6\xb3)c\ -\x93c\xb5\x89\xb7\x01\x87\x11ka\xcfv\xcb\xe6M6\ -\xd5\xca\xbf\xf5\x12\xbf#m\xb6\xa4\x94\x1cv\x0a\xe0\x16\ -y\xa6\x80\xe7A2^@b\xa2(\x04{\x1c\x8f\x17\ -\xa5q\xb0g|\x90D\xc4\xcch;S\x96\xa8{&\ -`\xdb'\x13\xf3\x0f{\x8d\xe3\xc4\xc9\xbfB\xea=\xa7\ -\xeb\xa3\xbb=|p\xbc\x85+\x93W\x01R\x81We\ -L*\xee\xd2a2[\xf4}W\x003\x87f\x91\x0e\ -\x08\xc1+\xbd\x12\xbb\xd3(\xfcQ\x02\x0eL\xb4S\x8b\ -\xef\xae\x84\xd6\x02?H\xeeL\xc0\xa4\xb0\x166\x8dZ\ -n\xde\x95\xed\xe4\xdf%\xeb\x06v\xd6\x02\xb6U\xd2\xf1\ -\x02\xbc2(\x8f\x97\x0a\x99\xcc\xf0\xd0D<\x80\xa8M\ -M\xba\xbc\xb2P\xc5I\xfa\x90\x0f\x0b\x8c\x15\xa2\xf8\x04\ -\x9fd/\xbd\xbe{\xa3\xe3s\x01\x87\x0d\x0b\xec\xbb\xc9\ -01\x9a\xbd\xbd\xff\xabQP\x96\xfd\xe3\xedT\xb6\x05\ -\x1d\x0f\xdc\x22{\x85L&\x0c\xe8\xab\x02X\xa9\xfc\x93\ -\xecw<\xeeLc\xdc\x97\x14q\xec_\xcb\xd8\xd1Q\ -]\xc2h8\x15@\xc1\x85\xdbf\x0cN\x16\xb5\xeeU\ -\xb0\xc0\xf6j\xc0\xd6J\x90\xf8;#\x04\x14*T\xa4\ -\xe2\x95:\xa4\xef\xa5r}\xf7\x00\x84\x04!x\xaeW\ -b:i\xf7\xbf\x1b\xfb\xef\xce\xd0A\x11\x17\xdc\x1b\xe2\ -\xf8\x7f\xd8\x14\x80\xb1\xb0u\xca\xb2{\xfb\xe0Y\xff.\ -\x9e\xb4\xdc<\xea\xe3\xa5\x90\x0bp\x8b\xa0\x5c\x9e+$\ -\xdb\xa0\xbf^@\xdf\x15\x80\x0e(I\x87\x97\xba\xa5\xe4\ -;\x0f\x05q\x81G\xd6\x0e\x8e\x5cM;H\xf6P\xd0\ -$\x10\xc0-\x09\x8f\xfd\xea5\x16\xd8Z\x0d\x99,E\ -\x89\x17i\xa98\x0c\xd8'$\xcf\xee\xf7\xb5\xfa\xa6\x00\ -V\xed\xfd\xeft<\x9e\x9d\xb4\xfb\x1fgt5\xbb2\ -\x1a\xfbw\x09\x02V\x86l\x0c\x03\xd6B\xb5l\xb9e\ -&\xfb{\xffW\xa3\xa8\x0c\xbbG|T\xc2\xdb\xd6B\ -\x80W\xa2,%_\xa7C\xfa\xda?\x99D\x08\xf0\x1c\ -\xa7\xc8\xce4\xb2\xff;k\x01\x13=<\xc1\xb7\x1f\x04\ -\x11\x89v\x02\xf6\x1bkagg\xec\xd7\xa0\x7f,\x01\ -\xec\xa8\x06TS\xe8\x18uK\xa0\x5c\x9e'$\x9b\xfa\ -y\x9d\xbe*\x80\x85\xa7\x10R\xf2\x1c\xafH1i\x0b\ -WT\x96=#\x01*\xe3f\xa8\xd5\x16\x89\x9e\x0a\xdc\ -o\x94\x8a\x93\x7f\xa5\x0c\x1c\xedv\xa3t\xabGwT\ -\x93\xf7\x22\x95\x07N\x81\xfdBp+\xf4/\x0f\xd0W\ -\x050:\xcd\x98t\xb8\xd3Ix\x14\xbb\xb50Y\x0a\ -\x99,e\xa7\xe6\x7f\xcdu\xd29\x11(\xcb\x8b\xbc\x96\ -\xcf\x93\xa1\xb1_\xbdB\x89\xd8\x93\xf4\x12\xde\x12\x94\x12\ -\xdc\x12\xe3B\xf2\xdcR\x1f;g\xfb\x1b\x02\x08v9\ -\x1e\xfb\x92\x9e\xf7/e:\x0f\xedZ\xb1\xc4\x1e\xc00\ -)\x80\xbd;m&\xc6~\xf5\x92\xa9r\xc4\xa6b\xf2\ -\xc9@\xb7\x88\x94\x0e/h,\xd0\xb7\xfa\xd9\xbe(\x80\ -\x99C\xb3\xdd\xa3\xa0\x9e\xed\x14\xd8\x9c\xe4\xd0\xcfn\xf2\ -/\x0d\xb7\xed\x9a\xd7:d\x9d\x80\x9e\x0b\xcf\xb8Yg\ -f\xecW/\xe8\x9e,\xb4\xbd\x1a$\x9e\xa8u\xe3\ -\xd0\xf2\xaf/\x07!\x80\x83{\xec@\x8f\xfd\xba^*\ -\x8e\xa1\xa0\x0c\x81Q\xc9X9\x01RQ\x80\xd8\x03\xe8\ -%=W\x00\x02J\xd2a*\xd1\x1d\x00\x11\x0fnP\ -\x03P\xff\xdf\xc5\x00-\xbf\xbf\xafO\xd7\xda\x97K\xb0\ -k\x8b\xe1\x19\xfb\x0c\x07\xf6\x18\xa6\xc7-\x87\x8fH\xcc\ -u& \xe3\xb1_\xb1\xfb/\x18\xee\xe2\x9f\xb5(:\ -\x86\xa2cYN\xb0$@*\x5c)\xe3\xceZ\xdd\xc3\ -\x14D\xef\x15\x80\xa4&%SI\x96\x00K\x015o\ -\xb0^Fc\xa1\xd5\x8e\xd7\xdbK5\xd0\xb5\xf6\x8e\x82\ -\xc9\xb1x8\xc7\xed\xfb\x0c\xbb\xb6\x1a*\x9d\xac\x8c\x00\ -Z\x9dY\x84\xd7u\x0d\x0b\xbb\xb6\x1a\xb6M\x0d\xfe\xd8\ -\xafk\xfe\xec\xc4\x05g\x15\xc7p\xa6\xd7\x0f\xef\x0aH\ -\x85\x14\x92\xe9_{5\xfc\xc4=\xbd\xfb\xb9\xbd\xdf\xbd\ -\x15T\xa5C%\xc9\x12`GX\xaa\x09o\xcd\xdc(\ -\xc6t<\x80\x1e\xbdD]k_,\xc0\xce-\x86;\ -\xf6\x1bn\xd9k\x98\x1c\xb38\xb2\xa3\x18V}\x7f\xb3\ -%\xae;\xff\xd0\x1d\xfbU\xf4\x06G\xe1\xf6\x12%-\ -%7\xc1\xfd\xdb8\x07\x80\x90l\xfa\xe1\xdfGV'\ -{\xa7w{\xa6\x00V\x0e\x01\x11\x8c\x0aI%\xa9\x9b\ -c\x01GZ\xca\xee`\xd9\xa2\xf8T\xe0\x1b\xff\xec\xd6\ -\x82#a\xbc\xb3\x1f\x7f\xc7~\xc3\x9e\xed\x86j\xe9\xfc\ -\xf7\x985\xfe]\xbdu}\xa3\xc8\xac\x85\x89Q\xcb\xfe\ -\xdd\x83u\xbf{\x89\x00*\x8eN.\xcb\xdd\xcd\x13\x09\ -\xc6\xbd\x0a\x0e\xd0\xb3\xe3d\xfb\xe3\x01H\xbc$=\x80\ -\x82\xb2\xa9\x1c\xe0p#X\x0b\xc1u\xc6\x90\x17\xc7\xf6\ -\xb7\xddl8\xb0\xdb05nq\xd5\xa5\xd6~\xad\x7f\ -\xdf\x1dEv\xad\xcf\xc9Z\xd8\xbb\xc309\xbe\xf1\x92\ -\x7f]\x04\xe0)\x9bh\xc8)\x14H\xc9\x88\x94xd\ -U\x01t^\xa6\x11\xa1\xfa\x7f\xa4\xd1\x0a\x16\x0a\xca\x0c\ -\xd4\x0e\x80 \x8e\xbf\x83p\xfde\xc0\xd6v\xbc\x9dN\ -l\x7fpO'\xb6\xdf\x12w\xe1un\xc5\xba|C\ -\xcb\xf9Qd\xd7\xaa\xa7=\x17n\xdfgp\xe5\xfa\xae\ -5\xac\x94\x1c\x83\x14\x16m\x93\xb1tB\xc4\xf95!\ -)\x00\xf5^\xfd\xdc\xdez\x00\xb1J,\x0a\x91|\x17\ -\xe0 \xed\x00@,\xfc~xu\x01\xecZ\xfbJ\xd9\ -\xb2{\x9b\xe5\xf6}\x86\x037\x196\x8dZ\x94\xbc\xba\ -\xb5_\x0b\xa3\xa1}\x1d\xf9\x07\xd3\x1d\xfb5\xc0G~\ -\xf5\x8a\x82\xb2H\x01:\xa1\x1b\xd1\xa9\xb0-Ao\x8d\ -kO\x15\x80T`,U!\xfb{\x9a\xc9\xc5\xb8\xca\ -\x0e\xdc\xc9:~\x10\x9f\x0a\xb4\x16]k\xef\xaaX\xe0\ -n\xd9k\xb8\xfdf\xc3\xce-q\xe2\x0d\xd6o\xed/\ -\xa6\xdb\x85\xd8ls]I\xac\x83{\x86s\xec\xd7\xb5\ -\xe2J\x8b\x92\x96\xd0$\xf4\xe2\xc5\x0a\xa0\x8c\xa0\xa7=\ -\x81\xbd\x0d\x01$ pE\x92CnD\xfc0\xc4\x00\ -\xbd\x92\x02h\x07\xf1H\xb0\xd5t\xad}\xb5\x1c\x1f\xac\ -y\xc7~\xc3\xfe\x9b\x0c\xe3#\x16%b\x81\xef\xc5\xa7\ -\x0c\x22A\xdb\xbf\xf6\x87T.\xc4{\xff\x92\x8d\xed\xfe\ -C\x9cx\x96\xf4~\x1b\xf7r\x08\x01H\x5cAo\x8d\ -ko=\x808.tIx\xca\x95\x12\x83\xe7\x01h\ --V\x1a\x81\x8c\x89\xdbj7OXn\x9d\x89c\xfb\ -\x1d\x9b\x0dE\xf7\xbc\x8b\xdfK\x81\x8b\xa2\xb8\x0c\xf9Z\ -0\x06\xb6o\x1e\xee\xb1_\xd7\x82\x12\x16\x99p\xd8)\ -b\xf7\xbf\xa7\xb3\x01{\xeb\x01(@$\xdf\x1b\x92\xf4\ -\xe1\x8d\xbd\xc0\x98\xce\xd1S\x958\xb6\xbf\xe3@\xc7\xda\ -\xd7\xe2\xd8\xd2\xd0?+\x1bF\x10^\xa3\x02\x902\xde\ -\xfb\x1f\xf6\xb1_\xebE\x88\x1b\xeb\xa5\xb8\xf6\x0b\x82\x90\ -\x08z\x9c_\xeb\xa9\x02(T l\xe1$\xed\x01\x08\ -\x06\xab\xa7\xde\x12+\xadW\xbc \xe2\xe0\x1e\xc3\xd6I\ -K\xa1O\xd6\xfeb\x04q\x01Rx\x0d\xe5\xa4\xd6\xc2\ -h\xd5r`\xcf`U[\xf6\x13)R9\xd1Y\xd1\ -c\x99\xed\xe9\x0f\xeb\xc4\xb0\x89\xdf\x96A\x8a\xff!\x16\ -\xa0\x1d[\x0c\xbb\xb7\xb3\x12\xdb'\x19S7\xdb\x10i\ -\xb1\xee\x17\xd8Z\xd8\xb3cc\x8c\xfdZ?6\xf9\x11\ -\xe8\xa2\xf7\xb6\xae\xa7\x9f\xc1\xc41m\xe2\xd28\x88\xe3\ -(]'\xb6 i\x08T\xab-\xaei\x10\x89\xe3\xc4\ -\xee\xffF\x19\xfb\xb5>R1;\x19W\x00\xb1[\xa9\ -m\xc2\xbbD\xddm\xb3\x9c\xf5\xd1\x1dE\xb6\x1e6\xe2\ -\xd8\xaf\xf5a\xd31;=\xbehO\x15\x80\x8e\xfb\xdb\ -\x13?\x93;\xa9j\xaca`\xa5\x0ap\x9d\xcf\xc8\xda\ -\x8d5\xf6k\xbdX\x9b\xc24'\x8b\xa1\xc7QXo\ -\x15@\xecV\xb6m\xc2\xa1\xa26\xd7\xd7\xd8\xb2\x111\ -@\xa3\xb5\xfe#\xc9\x8b\x85\xd8\xfd\xdfHc\xbf\xd6\xc3\ -\xf5T`\xde\xe8\x05;r\xd5\xd3\xb6\xd7\x9e*\x00\xab\ -\xc1Z|\x924\x16\x16B#\xd2r\xc8\x06\x0ec\xa0\ -\xd9Z\xe7\xf7Z\xd8:i\xb9i\x03\x8e\xfd\xba\x1a\xc6\ -\xa6\xe2yFXz:\x86\xa4\xb7\x0a .UkX\ -\xd3[-u5B3\xd8\xc35\x93\xa4;\x87`=\ -\xb7K\x00\x07\xf7\x9a\x0d9\xf6\xebj\xe8\x84\xdf9k\ -\xc1\x1aB\x0b=\x1dI\xdc[\x05\x10\xff\xc7O4\x04\ -\x10\xb1\x02\xd0f\xf0\x0f\xd9\xec7\xdd.\xc4\xb6\x7f\xf5\ -\xef\x8d\xc7~\xd9\x95\xb1_9\x17\xe2k\x99\xf8;g\ -->d\xd8\x03\xe8\xc4)K\xc6\xf4\xae_y=\x04\ -Z\x10\xe5&j]\x84\xa1\xa0\x1d\x5c\xfd\xb5\x8d\xc7~\ -Y\xb6Nn\xbc\xb1_\xeb\xc1\xd7\x22\xb1N@\x88\xbd\ -kkh`Yg\x00\xb7>\xfaQ\xcb\xd0\xb0\x86 \ -\xc9\xa4\x5cd\x04QR]Y\x03N\x10\xc5\x9d\x88W\ -\xbb[\xdd\xb1_\xa5\xbe\x9cJ?\xf8\xb4\xb4\xc4$\x98\ -\x03\xb0\xb1qm\xda\x1e\x1b\xd7\x9e+\x00k\xa9[C\ -3\xa9\xa0Q\x00\xa1\x91\xb4\xa2\x01l\x08H\x18A\xdc\ -\x82|\xb5Qd\xd6\xc2X-\x1e\xfb\x95;V\x97b\ -\x81F(\x13\xbd7\xd6\x80\xd5\x9c\xb36\xa3\x0a\xe0\xc8\ -\xddo\x8e\x17j\xa9\x9b\x88f\xa2\x1e\x80\x85v\xae\x00\ -\xd6\x85\x1f^\xda\x86|1\xd6\xc2\xcc\xce\x8d=\xf6\xeb\ -J\x18\x0b\xf5@%\xba\xf5\xdc\x09\x01\xceEA\x96s\ -\x00\x00\x86\x86\xb5,\xda\x04\x03Gm\x04\x8d\x5c\x01\xac\ -\x8b\xb6/\x88\xf4\x95\x1bY<\x17\x9e\xd1\x19\xfb\x95s\ -)\xa1\x914B\x99h\xd7\x8b\x89\xc0ZN\x9d\xf8\xbe\ -\x0c\xd7\x01\x00X\xf0\x8df>\xc9#\xafb\x97L\xe5\ -\xd6j\x1d\xb4\xdaW>\x90\xd4Z\x98\x9e\xb0\xec\xc9\xc7\ -~\xad\x89 N\x006\x1368Z\xa3\x8d\xe1\xe4\x81\ -\xf7\xf7\xf6\xe7\xf6\xfcS\x98\x88\xb65\x9c\xb2IV\x02\ -XX\x0ad^\x12\xbc\x0e\x9a\xed\xab\x9f\x07\x90\x8f\xfd\ -\xba2\xcdP\xe2k\x99\xe8\x16\xa0\x89\xf0\xad\xe1\xc9^\ -\x9e\x0a\x04}P\x00\xdbo'4\x9a\x13\xbd^\xe8\x15\ -\x11\xb1\x07\x10\xe8\xbc\x16\xe0JXb\x0f\xe0r\x05,\ -\x16(\x15\xcf\x8f\xfd\xcaY\x9b\xe5P%7\x0b\x90\xd8\ -+\xd3!M,\xa7z\xfd\xb3{\xfa\x9c\x8f\xdc\xfdf\ -\xe6\x9e\x00k8a\xa2d\xab\x01\x1b\xa1\xa4\x19\xe6\xaf\ -\xed\x95\xb0\xc4\x1e\xc0\xe5\x92W\xd6\xc0\x8e\xcd\x86\x1d[\ -\xf2\xec\xff\xe5\xb0\xc0B[%^\x03`\x22\xe6\xac\xc9\ -\xb8\x02\x80x&\x80\xb5\x9c\xd0\x11\xeddnOg{\ -K\x0b\x16\x83D\x87\x11\x0f\x1c\xc6\xc4\xc3@.\xf7\xee\ -J\x09\xb7\xee5\x94\x0by{\xf5\xe5\x88\x8c\xe0\x9c\xef\ -$z\x83\x8c\x06\xa39k-\xe7\xe0\xfc\x8e[/\xe8\ -\xbd\xc9\x8c\xdb\xa4N\x99\x88F\x92;\x01\x91\x15\xcc\xb5\ -\x9d\xfc\xc5\xbd\x02\xda\xc4\xc3@\xd6\xc2\xdax>aw\ -\xecW\xce\xa5\x08\xa0\x19I\xce\xf9*\xf1\x1d\x00\xa39\ -jt\xef\x0e\x04\xe9\xd2\x17\x9f\xd9\x1aN\xe9\x90SI\ -*\x00\x80\xb3-\x07_\xe7\xaf\xefZ\x08\xba\x8d@k\ -\xff}|\xe4W>\xf6\xebj,\x07\x8aV\x94l\x02\ -P\x87`4O|\xe3\x1bz_b\xdf\x1f\x05`9\ -g4'\x13M\x04\x02\x8b\xbeC#T\xb9\x05\xbb\x0c\ -a\xe7<\x80\xb5p\x1c\xb8\xed\xe6|\xec\xd7\xd58\xdb\ -r\x12M\x00\x02\xe8\x80\xd0\x1a\x1e\xfa\xc8{{\xeb\xfe\ -C\x9f\x14\x80\x89h\x98\x88#\xba\xa75KWF\x00\ -\xadH0\xd7\xca\xdf\xe0\xcb\x11\x84\xacy\x1c\x99\xb5\xf1\ -y\x83\xf9\xd8\xaf+\x13Y\xc1|\xdbI\xbc\x020\x0a\ -X\xb2\x86\xc7\xfa\xe1Q\xf7E\x01\x94F\x89\x8c\xe6\xe1\ -(Hv' \xb2\x82\xd3-7\x9f\x0dp\x19\xba\x0a\ -\xe0b,\xb0\xef&\xc3\xc4H\xbe\xf7\x7f9\x04\xf1\xfe\ -\xff\xd9\xb6\x93l\xfc\xafA\x87\ -\xfe\xd7\x11\x8fY\xcb\x5c?~~\x7f6\xce\xe3\xe9%\ -\xc7\x22\x9f\xa7M\x82y\x00\x01,\x05\x8a\xf9v\x1e\x06\ -\xac\x85\x1f\xc4\xe7\x01\xac~\x83W\xc6~m\xcd\xad\xff\ -\x950\xc0\xc9\x86\x9b|\xfc\x1f'\x00\x0f\x1f}\x0f\xcd\ -~\xfc\xfc\xbeU\xceX\xc3\x09\x1d\xf1P\x92y\x00\x80\ -\xc0\x08\x9ejx\xf9\xcb\xbc\x06\xad6q#\xd0\xaa?\ -\xcb\xc7~\xad\x0f?\x92\x9cN!\xbf\x14\xb6\x09\x8c\xe6\ -\xf0\xcdo\xe8\xcf\xcf\xef\x9b\x02\xf0\xeb4u\xc4\xe7C\ -?\xf9\xf7\xea\xe9\x86K3aWm\x10h\xb4.\xec\ -\x038?\xf6K\xe7\xf7\xea\x0a\x08`\xae\xedp\xcew\ -\x92\xad\xff\xd7\x10\x05\xccay\xd0\xda\xde\xef\x00@\x1f\ -\x15@y\x1ck\x0d\x9f\x8b\xda\xc9\xce\x06\x10\xc09_\ -q&\xdf\x0d\xb8\x80\x95>\x80\x8b\x14\xc0\xae\xad\x96m\ -S\xf9\xde\xff\x950\xc0\x93u\x8f \xe1\x1a\x13\x1d\x82\ -\x0ex\xd4Z\x8e\xf6\xeb\x1a}Q\x00+\xc3A\x0c\x87\ -C\x9f\xe3I\xe6\x01 \x0e\x03\x9e\xac{\x89\xd6k\x0f\ -\x02-\xff\xc2>\x00\xa5\xe2\xe4_\xd1M{e\xd9E\ -\x10\x0f\xff8\xbe\x9c|X\x19\xf9\xa0#\xee\xd7!\x0b\ -\xfd\xbaF\x7f\xbbg,'u\xc8\x03\xd1:\xa6\xd0\xf6\ -\x9a\x93\x0d\x8f\xe5 /\x0a\xeab\xec\x85}\x00\xd6\xc2\ -\xf8\x88\xe5\xc0M\xf9\xde\xff\xd5x\xba\xe9\xb2\x1c$\x1f\ -R\x86m|\xa3\xf9\xac[\xea\x9f\x83\xd6W\x05\xe0\x16\ -i\x9a\x88O\x84\xedd\xeb\x01\xba\xbb\x01'\x1a\xf9D\ -\xcb.\xda@\xb3%V4@w\xec\xd7\xa6|\xec\xd7\ -\x15\x09\x8c\xe0\xe8R\x81(\xe1Y\x13FC\xe4s\xd6\ -\x1a\xbeL\x9f\xe2\x7f\xe8\xb3\x02\xd0\x11X\xc3\xbdA\x8b\ -9\x93\xa8\x0a\x00m\xe1\x89%/\xef\x0d\xe8\x10\xe9N\ -\x1f@\xe7vxn\xec\xfe\xe7c\xbf.\x8f\x00N7\ -]\x9en$\x9b\xfc\x83\xd8\xfd\x8f\x02\x1e\xa0\x8f\xf1?\ -\xf4;\x04 \xce\x03D\x01\x0f\xe8DO\x0a\xe8<\xbc\ -\x96\xcb\xa9\xa6\xbb\xe1\xc3\x00\x01h}\xfe<\x80\xee\xd8\ -\xaf\xbd\xf9\xd8\xaf+\xa2-<\xb6X\xa0\xad\x93\xd7\x92\ -A\x0bt\xc8\xa7\x1e\xffE\x16\xfby\x9d\xbe}\xb2\xae\ -\xcb\x12\xfa,\x98\x90\x7f\x0dzz\x9c\xc1\xfa\xf0u:\ -\xee[\x16\x89\xf4\x85\xe7\x01\x1c\xc8\xc7~]\x11\x01,\ -\xf8\x0e'\xea\xc9gH\xad\x81\xb0\xc5\xbc5\xfc\xe3\xcc\ -/\xf6\xf7Z}Wm\x85\x0a\xd6h\xfe1h%;\ -)\xb8\xcb\x93u\x8f\xf9v\x9e\x0c\xf4\x83\xf8L\x80\xee\ -\xd8\xaf\xdb\xf2\xb1_W\xc4\x10[\xffz\x0a\xdd\xa5Q\ -\x00\x91\xcfW\xac\xe50\xf4/\xfe\x87\x04\x14\x00\x80\xb5\ -|!l\xf3`\x94B\x18\xd0\x08%G\x16\x8b\x1bz\ -KP\x10\xc7\xffA\xf6\xebr\ -X\xe0\xf1\xa5\x02K)l#\x1b\x03~\x83ec\xf8\ -\xb8\xd7\xc7\xed\xbf.\x89x\x00n\x11c4\x1f\xf1\x1b\ -\xc9\xef\x06\x08\xa0\x1e*\x1e^(n\xe8\x5c@\x10\xc4\ -y\x80\xd1\x8a\xe5\xc0\xee\xbc\xee\xefr\x08\xe2\xae\xbf#\ -\xe7\xd2\xb1\xfeQ\x1b\xc26\xf7[\xc3\xbfB\x7f\xdd\x7f\ -H@\x01\xac:2\xec\xf3\x91\xcf\x97\xa3\xc4F\x85\x9e\ -\xc7\x02\xc7\x96\x0a\x9cin\x5c/\xa0\x1d\x08\xb4\x86=\ -;,\x9b7\xe5\xc9\xbf\xcb\xa1-<\xbcPL\xc5\xfa\ -\x03\xf8\x0d\xd0!\x7f\xff\xf8\xcf\xf4\xdf\xfd\x87\x84<\x00\ -\x80\x93\x0fpV\x87\xfc\x8d\xdfH\xb6(\x08:\xb9\x80\ -H\xf2\xc0|iC\xd6\x05X\xe2a \xdd\x13\x7f\xf3\ -\xb1_k#\x803-\x97\xa3K\xe9X\x7f\x13A\xd0\ -\xe4\xa45\xfc\xdd\xcc[\xfbo\xfd!!\x05p\xe4\xee\ -7\xb3\xfd\x0e\xb0\x86\xbf\xf7\x9b<\x95t\x8b0\xc4\x0f\ -\xf7\xc9e\x8f'\x96\x0b\x1b\xd2\x0b\x10\x02&\xc7\xf3\xb1\ -_W\x224\x82\x87\xe6\x8b\xd4S\xea$\xf5\x1b\x10\xb6\ -\xf9\x14\xf0@R\xd7Lt'\xc8Z\x0eG>\xff\x90\ -F2\x10\xe2\xb2\xce\x07\xe6J,o\xc0V\xe1\xa2g\ -\xb9e\x8fa|4w\xff\xd7B\x00O,{\x1c]\ -J\xc7@X\x03~\x9d\x86\xd1\x1c\x12\xb2\xf7\xe3\xbf/\ -G\xa2\x0a@*\xdaF\xf3\xa1\xf62\xf54j\x02\x04\ -p\xb6\xed\xf0\xd0|i\xc3m\x0bN\x8e[\x9euP\ -\xe7c\xbf\xd6\xa0\x1b\x22>8_\x22Hx\xe2O\x97\ -\xb0\x0dA\x8b\x07\xb0\xfc3$\xe3\xfeC\x82\x0a`U\ -\x8b\xf0?\x85m\xbe\x10\xa6\x90\x0c\x84\xb8+\xee\xe1\x85\ -\x22'\x1b\xde\x86\xf1\x02,q\xe9\xef\xf6\xe9\xdc\xfa\xaf\ -\x85\xb1\xf0\xd0|)\xbd\xb2q\x0b\xed:F\x87|\xf0\ -5\xdf\xc6SI^:\xf1b\xb0'?\xcfi\x1d\xf2\ -\xc1\xf62Q\x1aoc\xf7t\x97/\x9c)\xd3\xd8@\ -\xc3C\x0b^<\xfb?\xe7B\x04p\xaa\xe9\xf2\xd0B\ -z\xc5b\xa1\x0f\xfe2G\xac\xe1\x83\xef\xbd'\xd9k\ -'\xae\x00v\xdd\x09\xd6\xf0!\xbf\xc1ca\x0as\x02\ - ~\xe8'\x9b.\x0f\xcf\x17\xf3\x11\xe2\x1b\x98\xb8F\ -D\xf2\xf93\xe5\xc4\xa7\xfd\xae\xc6o@\x14\xf0W\xd6\ -\xf4\xbf\xf4\xf7b\x12U\x00\xabj\x02\x1e\x8d|>\xe0\ -\xd7\xd3\xf3H\x8d\x85\xc3\xf3%\x9e\xa8o\x9cP \xe7\ -Bt\xe7\x1d8\x91\xe2;\xa0\x03h/\xf3\xb45|\ -@:\xc9o\x91'\xee\x01\x1c\xb9\xfb\xcdHEd\x0d\ -\xefo/\xf3T\xd2\xfd\x01]V\x87\x02\x8b\xf9\xe4\xa0\ -\x0d\xc9\xd1\xa5B\xec\x05\xa6\xb8\x86v\x1d\xa26\xff\x1b\ -\xf8\x1c$k\xfd!\x05\x05\xb0\x82\xe5\xfe\xc8\xe7\xaf\xdb\ -\xcb\xa9\xad`e\xe0\xc3\x17\xce\x94S\xcb\xfe\xe6$\x8f\ - >\xe3\xef\xbe\xd3\x15Z:=\xd7_\x87\xd0Z\xe2\ -\x94\xd1\xfc\x89\x90\xa4\x92\x16OM\x01\x08\x85o\x0c\xef\ -n/q\x22-/\xa0\xcb\x91\xc5\x02\x0f\xcd\x17\x13=\ -\xf3-'\x1d\xbaq\xff\xe7NWX\xf0\xd3\xf5\xfc\xda\ -\xcb\x10\xb5\xf9\x08\xf0iH\xde\xfaCJ\x0a`\xe5\x83\ -Z>\x13\xfa|\xa0\xbd\x94\xc6*\xce\x13\x1a\xc1\x17\xcf\ -\x96y2\xcf\x07\x0c5\x82\xb8\x18\xec\xfe3\x15\x9e\x5c\ -N\xf7YG\x01\xb4\x169e\x0c\xef\x12\x92\x14\xc6\xe5\ -\xc4\xa4:\x13BH|\xabyWk\x89\xc7\xd3\xaa\x0b\ -\x80\xf3\xf9\x80\xcf\x9e\xaap\xa6\xb5q\x1b\x86\x86\x1dm\ -\x05\x87\xe7J<\xba\x90N\xad\xffj\xda\xcb\x10\xfa|\ - \xe9\xc2\x9f\x8bIM\x01\xac\xfa\xc0\xf7E>\xf7\xb4\ -\x16I\xd5\x05\xef\x9e\xfe\xf2\xd9S\x95\xd4:\xc1r\xfa\ -\xcb\xa3\xe7\x0a|\xe1l9\xf5\xb6\xf0\xc8\x87\xf6\x22'\ -\xac\xe6\x9dB\x92\xd2fxL\xeaS\xa1\x84$\xb2\x86\ -w\xb4\x97\xf9B\x98R\x8f\xc0\xcaZ\x80\x13u\x8f\xcf\ -\x9e\xaa\xe4'\x0c\x0f\x19\xc7\x96<\xee;]\xc1\xd7\x22\ -\xd5\xe7j-4\x17\xb1\xa1\xcf!\xe0^H\xcf\xfaC\ -\xca\x0a`Uy\xf0C:\xe4\x1d\x8d\x05\xfc\xa4\x07\x86\ -\xac\xc5\xe3K\x05\xee\xcd\x95\xc0P\xd0U\xea\xff\xfat\ -5\xb5.\xbf\xd5\x84-h/\xf1\xa05\xfc\xa1\x90\xa4\ -\xd0\x17{!\xa9{\x00G\xee~3\xd2\xc1Z\xc3\xbb\ -\xfd\x06\x1f\xf7\x13\xeb\x83\xba<\xd6\xc2#\x0bE>w\ -\xbaB;e\x8b\x91sc<\xd5p\xf9\xf4\xd3\xd9\x08\ -\xeb\xac\x81\xe69\xb4\x0e\xf9\x93(\xe0AH\xd7\xfaC\ -\x06\x14@\x17!9c4oo,0\x97\xf6\xb6 \ -\xc4\x0d4\x8f,\x14\xf9\xec\xa9*\xad\xdc\x13\x188\xba\ -5\x1e\x9f:Ye.#\xf3 \xdb\xcb\xd0\xae\xf3\xcf\ -\xd6\xf2n\xb7\x88M[\xf8!#\x0a`\xd5\xb6\xe0\xdf\ -\x86m\xfe\xa2y\x0e\x9b\x85=yc\xe1\xe1\xf9b\x9e\ -\x13\x180\xba\xbd\x1e\x9f\xcc\x90\xf0\xeb\x10\x9a\xe7X4\ -\x11o\x13\x82\xe3i\xaf\xa7K&\x14@\x17!i[\ -\xc3\xffh-\xf2\x85\xa0\x91\xf6jb,\xf0\xc8\xb9\x22\ -\x9f:Y\xcd\x0f\x1b\x1d\x10N6\x5c>\xf9T5;\ -[\xba\x16\x9a\xe7 h\xf1>\xe0\xef }\xd7\xbfK\ -f\x14@\xf7\x86\x08\xc1a\x1d\xf2\xb6\xc6\xc2\xc4k\xef\x02x\xccD\xec\x16\x82g\x16\xca\x90\ -\x95'\xb9\x18(\xce\xb6\x1d\xc6\x0a\x9a\x9a\x9b\x8f\xd6\xce\ -\x0a\x828\x5c{\xf4\x5c\x91\xcf<]\xcdTs\x97\x89\ -`\xf9\x0c\xad\xa0\xc5/\x0b\xf8\x1bDv\xac?dL\ -\x01@G\x09\xdc}\x97o-GM\xc8\x8bU\x81)\ -'C\xa7|\xd7C\xc5\xe9\xa6K\xc95\x8cz\x1a\x99\ -\x957m\x83\x22\x80\xb6\x16<0W\xe6\xbe\xd3\x19\xcb\ -\xd5X\xa8\xcfC{\x91\xf7Y\xcb\xaf\x0bA+K\xc2\ -\x0f\x19\x0a\x01.\xa62\xc6\xfdQ\xc8\x7fk\xcc\xb3\x9c\ -\xc6\x14\xe1\xcb\x11\x1f\x1a\xa9\xf8\xe4SU\x0e\xcf\x97\x08\ -M\xbeM\x98\x16\x82\xd8+\xfb\xe4\xc9\x1a\xf7ep\xcb\ -\xb6\xdd\x80\xd69\xbel4\xbf*`>\xed\xf5\xacE\ -\xe6<\x00\x88\xbd\x80\xda\xab\xee\x028bBv`y\ -\x96WF\x88\x8c<]A\xdc@t\xaa\xe9\xd2\x8c\x14\ -\x13\x05MAe`\xdbb\x83 \x88\xf32\xc7\xeb\x1e\ -\x9f~\xba\xca\xf1e\x0fKf\x22E .\xf7]>\ -\xcdR\xe4\xf3\xf3&\xe0o\xa5\x9b-\xd7\xbfK&\x15\ -\x00\xc0\xc4k\xefB\x08\x02kyP\x87\xcdB\xe4\xf33\x02>,2\xb6\xe5\xb7\ -\x16Y\xbd\x97\x970sh\x16k\xd9\xa7\x1c\xdeQ\x9d\ -\xe4\x85\x95\xf1\xec\xae\xde\x02J\xc0t9\xe4\xf6M-\ -vT\x03\x5c\x99\x1f\xcaq\xad\xd4C\xc5#\x0bE\x1e\ -Y\x88\xcf\xeb\xcb2:\x84\xc5\x93\xb4\xfc:\xff\x15\x98\ -E\xe0g]\xf8a\x00<\x80.\x9d\x02\xa1yk8\ -\xa2\x03^\xa4<69\x85\xb4W\xb56\x82X\x09,\ -\x87\x8a\x13u\x8f\xc5@QT\x96\x92k\xf3\xa3\xb9\xae\ -\x82\x00\x02-xl\xa9\xc8g\x9e\xae\xf0\xd8R1\xf5\ -\x1e\xfe\xaba4\xd4\xcf`\xdau\xdem-\xbf$\x04\ -\x8dA\x10~\x18 \x05\xd0)\x10B\x08\x8e\x1b\xcd\xbc\ -\x0ex\xb1S\xa4\x92\xc5\xa4`\x17A<\x86j\xae\xed\ -\xf0d\xdd\xa3\x11JJ\x8e\xa1\xe8\x98\xbc\x80\xe8\x22\x04\ -\x10j\xc1\x93\x0d\x8f{OUyp\xbe\xc4R\xa8V\ -\xfe.\xabX\x0b\x8d9h.\xf0\xd7\xd6\xf0\x93\x02N\ -#\xe2\xf7u\x10\x18\x18\x05\x00+;\x03\x16x\xd0D\ -X\x13\xf1\x22\xb7\x88+3~\xe4U\xb7n\xe0l\xcb\ -\xe5x\xbd\xc0r\xa0(8\x86\xd2\x06W\x04b\xd5\xbd\ -9Q\xf7\xf8\xdc\xe9\x0a_\x9a+3\xd7v2\x9b\xe4\ -\xbb\x80N\x93Oc\x8e\xcf\x19\xcd\x8f\x0ax4k\xa5\ -\xbeWc\xa0\x14\x00\xac$\x05\x8d\x85/\xea\x90\x92\x89\ -\xf8*\xaf\x84#3\xfeI\xba/s\xd0Q\x04O\xd6\ -\x0b,\x05\x0eJ@\xc918\x22\xdb\x96\xae\x1f\xf7\xa2\ -\x19I\x9eX\xf6\xb8\xffL,\xf8g\x07E\xf0;\xb4\ -\x96a\xf9\x0c\x87u\xc4\x9bt\xc0g\xa43X\xc2\x0f\ -\x03\xfc\xceu\x92\x82\x13B\xf2\x1b\xa5\x11\xbe\xab6\x8d\ -R\x19\xf7\x04V\xd3M\x08\x16\x95ek%`\xcf\xa8\ -\xcf\xd6rH\xd95+9\x84a\xa2\xfb\xa2EVp\ -\xceW\x1c_\xf6xb\xd9c\xae\xe5\x10\x0d\x90\xd0w\ -i/\xc3\xf2i\x9e\x0c}\xfe\x13\xf0\xfeA\xc8\xf8\xaf\ -\xc5\x00\x89\xcc\xa5\x888)\xf8\x96\xd6\x22\x8e\x10|G\ -m\x1a\x95uO`\xd5\xda\x01\xf0\xb5\xe0\xf1\xa5\x02\xc7\ -\xeb\x1e\xe3\x05\xcd\x8ej\xc0\xaeZ\xc0D1\xc2\xedl\ -!\x0e\xaa2\xe8~Fm\xa1\x19)N5\x1d\x9eX\ -*p\xaa\xe9P\x0f\xd5J\xf9\xee\xa0\x09\xbf\xdf\x80\xe5\ -\xd3<\x1d\xfa\xfc\x94\x10|\x10\x06S\xf8a\xf0\xee\xfd\ -\x05\xcc\x1c\x9a\x05\x0b\x16vJ\xc9o\x96Fyum\ -\x0a\x99\xf5\x9c\xc0\xe5\xe8\x0aD\xd91L\x96\x22\xb6U\ -\x03\xa6K\x11c\x85\x08O\xd9\x95\x87\x95e\x85\xb0\xda\ -\xd27C\xc9\x99\x96\xc3S\x0d\x8fS\x0d\x87\xa5@\xad\ -\x8c\xe4\x1e\xd4\x17/h\xc2\xd2)\xe6\xc2\x16oA\xf0\ -\x87\x90\xdd2\xdf\xf50\xa8\xcfa\x85N(\x00\xb0K\ -J\xfe{i\x8cW\xd7&\x07W\x09@G\xc0-H\ -\x01\x05\xc70Q\x8c\x98.EL\x97CF\x0b\x9a\x8a\ -cp\xa4\xbd\xe0\xe1\xa5\xa1\x14.\xbe\xbe\xaf\x05\xf5P\ -q\xa6\xe9p\xaa\xe9r\xa6\xe5R\x0f%\xa1\x19l\xa1\ -\xef\xd2\x11\xfe\x85\xa0\xc5\xcf\x09\xf8\xff\x10\x04\x83,\xfc\ -0\xf8\xcf\x04\xb8\xc0\x13\xd8-$o-\x8f\xf2-\xb5\ -)\xd4 +\x81.]\xc1\x16\x80+-E\xc70^\ -\xd0L\x14#6\x95\x22F\xbcX!\xb8\xca\xa2\x84\xbd\ -\xe4\x81\xde\x88b\x10k\xfc\xdev\xbe\x22#\xf0\xb5\xa0\ -\x19I\x16\xda\x0e\x0b\xbe\xc3\xd9Vl\xe5[\x91D\xdb\ -\xb5\x7f\xc6\xa0\xd2\x15\xfe\xb0\xc5/\x02\xbf?(\x85>\ -WcX\x9e\x0f{\xdf3\xdb}AwJ\xc9[\x8b\ -#\xbc\xa66\x85\x93\xe5:\x81\xeb\xa1\xeb\x1d\x08\x01J\ -Z\x8a\xcaRv\x0c5OS\xf34#\x9e\xa6\xea\xc6\ -[\x8cE\x15{\x0aJ\x80\x14\x16q\x85\x9d\x86\xae\xa2\ -\xb06.i66\x9e\xb2\x13\x1aI` \xd0\x92v\ -$\xa9\x87\x92\xe5@\xc5\xff\x0fca\xf7\xf5y\x81\x87\ -!z\xa9:tb\xfe\xf9\xb0\xc5\x7f\xb5\xf0?\xc4\x90\ -\x08?\x0c\xd9\xb3Z\x15\x0el\x15\x82_(\xd6\xf8\xf7\ -\xb5i\xdc,M\x14\xea5v\xf5/D\x5c\x82\xac\x84\ -\xc5S\x96\x822\x14\x94\xc5S\x86\xa2\xb2\xb8\xd2\xe2*\ -\x8b\xc0\x22\xc5\xf9\xa3\xd8\x8c\x15h\x13\xc7\xed\x91\x16\x04\ -F\x10h\x11\x0b\xbf\x16\x84V\x10\x19A\xd4Q\x0ev\ -\xb5[\xc2\x90\xbdD\x17\xe17`\xf9\x14g\x826?\ -'\xe0\x8f\x87\xc5\xf2w\x19\xbag\xb7*\x1c\x98\x12\x82\ -\xb7x\x15\xfe\xc3\xc84\xe5,\xcd\x12H\x02{\xc9/\ -\xb8\xe0i_\xb2\xd5\xb8V\xac \xd6\xfc\xe5\x86\xa1\xb5\ -\x04\xf53<\x19\xfa\xfc\x82\x80w\x22\x08\x87I\xf8a\ -\x88\x9f\xeb\xcc{f\xb1P\x15\xf0&\xaf\xccO\xd6\xa6\ -\xa8y\x95\xb4W\x953\x08X\x0b\xadE\xa8\x9f\xe5\xb1\ -(\xe0\xc7;[}z\xd8\x84\x1f\x06\xb0\x12p\xbd,\ -\xfc\xe5G\x98\xb8\xfb\xae\x00\xb8W\x87\x9c\x0d\xdb\ -\xabC\xfe\xb3\x10|\x180\xc3(\xfc0\xc4\x0a\x00V\ -z\x07B\xe0>\xady\xff\x9d\xd8\xe5_\xda(.\xff\xc5lX\x05\x00\ -\x9d\x90\xc0\x00\x82M\xc0w\xbb\x05\xdeT\x1eggi\ -4\xf7\x06\x86\x91\xa0\x05\x8dy\x02\xbf\xce\xdf\x19\xcd\xaf\ -\x09\xc1\xa7\x19\xd2\xfd\xfd\xf5\xb2\xa1\x15@\x97\x99C\xb3\ -\x00\xcaZ\xbeV*~\xa2P\xe5\xe5\x95q\xbc\xdc\x1b\ -\x18\x0e\x8c\x8e\x0b{\x9a\xe78\x11\xf9\xfc\x9e\xb5\xfc\x11\ -\x96S\x1b!\xcb\x7f5r\x05\xd0a\xe6=\xb3\x98\xb8\ -\xc9fZ\xc0w;\x05~\xa44\xc6\xee\xd2\x08\x0c[\ -C\xd1\x86\xc1\xaeX\xfd\xb6_\xe7\xc3\xc6\xf0;\xc0'\ -\x80h\xa3\xc5\xfa\x97#W\x00\x17\xb1\xf7=\xb17\x00\ -\xa3#\xfe\x02\xcb\xc7\x80\xa7 w\xf5o\x94\ -\x5c\x01\xf4\x90\x8e\x22\x90\xc0v\x04/S\x8a\xff\xcb)\ -\xf2\xb5\xc5\x1a\x9b\xbc28\x85\xdc+X\x0f:\x8a\xdd\ -|\xbfA\x104\xf9\x8a\x0e\xf8\x98\xd1\xfc\x15\xf09`\ -\xc1Zx\xfcu\xb9\xe0\xf7\x82\xfcu\xec\x03{\xfel\ -\x16\xe5\x82\x8e\xa8\x09\xc1\xf3\x84\xe2\xdf:.\xafpK\ -\xec+T(w\xbd\x82\x5c\x19\x9c\xc7h\x88\xda\xe07\ -1A\x93\xa7#\x9f\xcf\x98\x88\xbf\xb1\x96\x8f[\xc3Q\ -!\x88\x16\x16a\xe1\x87r\xc1\xef%\xf9+\xd8g\xf6\ -\xfcy\xbc\x85(\x04\xdb\x11\xbcP*^\xe5\x14x\xae\ -[d\x97W\xc6s\x8bq\xa9\xf1FT\x06&\x82\xd0\ -\x87\xb0\x85\x0e\x9a\xcc\x87>\xf7\x9b\x88\x7f\xb0\x86\xbf\xb7\ -\x96\x07\xdd2\xf5\xe5\x13p\xf2Gr\xa1\xef\x17\x1b\xf0\ -\xb5K\x87\xdd\xef\x9e\xc5-A\xe4S\x02v\x09\xc9\x0b\ -\xa4\xe2\xc5\x8e\xc7W;\x05vy%\xaaN\x11\x1c\xb7\ -\xd3\x898\x84O\xc6\x9a\xd8\xd2\x87m\x08\xdbDA\x8b\ -S\x91\xcf\x97;B\xff/\xd6\xf2\x80\xd1,\x08\x09G\ -\xbf#\x17\xfa$\x106\x1e\x91\xb3r\xee\xa4\xd8\x88\xa6\ -(av\xfe\xd1,\xc5\x09\x08\x9bxB\xb0]H\x9e\ -!%\xcf\x97\x0e\xcfV.\xb7\xba\x056\xbb%\x8a\x8e\ -\xd7\xf1\x0e\xd4`z\x08\xd6\xc6V>\x0a \xf21a\ -\x9bF\xe4sL\x87|\xd1h>a\x0d\xf7Z\xcb\xc3\ -X\x96\x11\xd8<\xaeO\x9e\xae\x02\xe8\xf6\xbb\x85\xb9\x02\ -H\x9e\x9d\xef\x98E\x87\x88B\x85*\x82]B\xf2\x0c\ -\xa9\xb8S*nW.{\x95\xcbf\xb7@Uy(\ -\xc7\x8b=\x84\xac)\x05k\xe2/\x1d\xc5\xa3\xb6\x22\x9f\ -(\x0a\xa8\xeb\x80\x13:\xe4Q\xa3\xb9\xd7\x18\xee\xb7\x86\ -\xc3X\x9e\x1e\xdfE\xfb\xe4\x03p\xf2\x07s\xa1O\x92\ -K\x0c\xbe\xb5\x17\x1e\x09\x93+\x80\xf4\xb9\xe9]\xb3\x04\ -MD\xa1FU\x08\xa6\x84dFH\x0eJ\xc53\xa4\ -\xc3\x8ct\xd8\xa5\x1c\xc6\x94KM\xb9x\xcaAH\xa7\ -\xa3\x18d\xe7\x0b.\x7f\xba'k\xfc9\x5c\xfe$Q\ -\xdb9\x14\xd4\xc4V\xbd\xeb\xca\x9b\x08L\x84\xd5\x11-\ -\x1d\xd0\xd0\x11gu\xc4\x13V\xf3\x15\xa3y\xd8\x1a\xbe\ -l-\x8fY\xc3\x19kh\x02\x1c{C.\xf0i\xd2\ -\x91w\x8f\xf8\x91\x86\xb9\xb4\x0f\x00\x9b\x7fw\x96\xe9\xdb\ -`\xf9\x04.\x82\xb2\x90L\x0a\xc1\xb4\x10\xdc$$;\ -\x84d\xafTl\x93\x8a-R1&\x14\xa3RQ\x90\ -\x0aOH\x1c)QH\xa4\x10H!8\x7f\xa6\xdf\xaa\ -\xf3\x01\xbbB\x8e\xc5X\x83\xb5\xf1\xff\xb55DF\x13\ -ZC\xdbh\xeaF\xb3d5\xf3\xc62g5'\xac\ -\xe1)c8j-'0\x85@`\xe3\x83\x80\x01\ -\x1f\x8b\x01\x9a\xd6\xd2\xc6\xb2d\xa1\x8e\xe1\x9c\xb5\x9c\x03\ -\xceY\xcb\x925,Z\xcb\xb25\xf8: \xd8\xf7\x0a\ -\xcc\xa7\xff'\xb4~9\x17\xf4A\xe4\xff\x00!\xbf\xbf\ -E\xaax=\x13\x00\x00\x00\x00IEND\xaeB`\ -\x82\ -" - -qt_resource_name = b"\ -\x00\x09\ -\x0alxC\ -\x00r\ -\x00e\x00s\x00o\x00u\x00r\x00c\x00e\x00s\ -\x00\x13\ -\x0f\xce\x16\xa7\ -\x00b\ -\x00t\x00n\x00_\x00d\x00o\x00n\x00a\x00t\x00e\x00C\x00C\x00_\x00L\x00G\x00.\x00p\ -\x00n\x00g\ -\x00\x08\ -\x0aaB\x7f\ -\x00i\ -\x00c\x00o\x00n\x00.\x00i\x00c\x00o\ -" - -qt_resource_struct = b"\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\ -\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00D\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x91\ -\x00\x00\x01}N\xa9\xa5H\ -\x00\x00\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01}N\xa9\xa5\x04\ -" - -def qInitResources(): - QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) - -def qCleanupResources(): - QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) - -qInitResources() diff --git a/src/screen_region.py b/src/screen_region.py deleted file mode 100644 index a43178d0..00000000 --- a/src/screen_region.py +++ /dev/null @@ -1,310 +0,0 @@ -from __future__ import annotations -from typing import Callable, cast, TYPE_CHECKING -if TYPE_CHECKING: - from AutoSplit import AutoSplit - -from PyQt6 import QtCore, QtGui, QtTest, QtWidgets -from win32 import win32gui -import os -import ctypes -import ctypes.wintypes -import cv2 -import numpy as np - -import capture_windows -import error_messages - -user32 = ctypes.windll.user32 - - -def selectRegion(self: AutoSplit): - # Create a screen selector widget - selector = SelectRegionWidget() - - # Need to wait until the user has selected a region using the widget before moving on with - # selecting the window settings - while selector.height <= 0 and selector.width <= 0: - QtTest.QTest.qWait(1) - - # Width and Height of the spinBox - self.widthSpinBox.setValue(selector.width) - self.heightSpinBox.setValue(selector.height) - - # Grab the window handle from the coordinates selected by the widget - self.hwnd = cast(int, win32gui.WindowFromPoint((selector.left, selector.top))) - # Want to pull the parent window from the window handle - # By using GetAncestor we are able to get the parent window instead - # of the owner window. - GetAncestor = cast(Callable[[int, int], int], ctypes.windll.user32.GetAncestor) - GA_ROOT = 2 - - while win32gui.IsChild(win32gui.GetParent(self.hwnd), self.hwnd): - self.hwnd = GetAncestor(self.hwnd, GA_ROOT) - - if self.hwnd != 0 or win32gui.GetWindowText(self.hwnd) != '': - self.hwnd_title = win32gui.GetWindowText(self.hwnd) - - # Convert the Desktop Coordinates to Window Coordinates - DwmGetWindowAttribute = ctypes.windll.dwmapi.DwmGetWindowAttribute - DWMWA_EXTENDED_FRAME_BOUNDS = 9 - - # Pull the window's coordinates relative to desktop into rect - DwmGetWindowAttribute(self.hwnd, - ctypes.wintypes.DWORD(DWMWA_EXTENDED_FRAME_BOUNDS), - ctypes.byref(self.rect), - ctypes.sizeof(self.rect) - ) - - # On Windows 10 the windows have offsets due to invisible pixels not accounted for in DwmGetWindowAttribute - # TODO: Since this occurs on Windows 10, is DwmGetWindowAttribute even required over GetWindowRect alone? - # Research needs to be done to figure out why it was used it over win32gui in the first place... - # I have a feeling it was due to a misunderstanding and not getting the correct parent window before. - offset_left = self.rect.left - win32gui.GetWindowRect(self.hwnd)[0] - offset_top = self.rect.top - win32gui.GetWindowRect(self.hwnd)[1] - - self.rect.left = selector.left - (self.rect.left - offset_left) - self.rect.top = selector.top - (self.rect.top - offset_top) - self.rect.right = self.rect.left + selector.width - self.rect.bottom = self.rect.top + selector.height - - # Delete that widget since it is no longer used from here on out - del selector - - self.xSpinBox.setValue(self.rect.left) - self.ySpinBox.setValue(self.rect.top) - - # check if live image needs to be turned on or just set a single image - self.checkLiveImage() - - -def selectWindow(self: AutoSplit): - # Create a screen selector widget - selector = SelectWindowWidget() - - # Need to wait until the user has selected a region using the widget before moving on with - # selecting the window settings - while selector.x == -1 and selector.y == -1: - QtTest.QTest.qWait(1) - - # Grab the window handle from the coordinates selected by the widget - self.hwnd = cast(int, win32gui.WindowFromPoint((selector.x, selector.y))) - - del selector - - if self.hwnd == 0: - return - - # Want to pull the parent window from the window handle - # By using GetAncestor we are able to get the parent window instead - # of the owner window. - GetAncestor = cast(Callable[[int, int], int], ctypes.windll.user32.GetAncestor) - GA_ROOT = 2 - while win32gui.IsChild(win32gui.GetParent(self.hwnd), self.hwnd): - self.hwnd = GetAncestor(self.hwnd, GA_ROOT) - - if self.hwnd != 0 or win32gui.GetWindowText(self.hwnd) != '': - self.hwnd_title = win32gui.GetWindowText(self.hwnd) - - # getting window bounds - # on windows there are some invisble pixels that are not accounted for - # also the top bar with the window name is not accounted for - # I hardcoded the x and y coordinates to fix this - # This is not an ideal solution because it assumes every window will have a top bar - rect = win32gui.GetClientRect(self.hwnd) - self.rect.left = 8 - self.rect.top = 31 - self.rect.right = 8 + rect[2] - self.rect.bottom = 31 + rect[3] - - self.widthSpinBox.setValue(rect[2]) - self.heightSpinBox.setValue(rect[3]) - self.xSpinBox.setValue(self.rect.left) - self.ySpinBox.setValue(self.rect.top) - - self.checkLiveImage() - - -def alignRegion(self: AutoSplit): - # check to see if a region has been set - if self.hwnd == 0 or win32gui.GetWindowText(self.hwnd) == '': - error_messages.regionError() - return - # This is the image used for aligning the capture region - # to the best fit for the user. - template_filename = QtWidgets.QFileDialog.getOpenFileName( - self, - "Select Reference Image", - "", - "Image Files (*.png *.jpg *.jpeg *.jpe *.jp2 *.bmp *.tiff *.tif *.dib *.webp *.pbm *.pgm *.ppm *.sr *.ras)")[0] - - # return if the user presses cancel - if template_filename == '': - return - - template = cv2.imread(template_filename, cv2.IMREAD_COLOR) - - # shouldn't need this, but just for caution, throw a type error if file is not a valid image file - if template is None: - error_messages.alignRegionImageTypeError() - return - - # Obtaining the capture of a region which contains the - # subregion being searched for to align the image. - capture = capture_windows.capture_region(self.hwnd, self.rect) - capture = cv2.cvtColor(capture, cv2.COLOR_BGRA2BGR) - - # Obtain the best matching point for the template within the - # capture. This assumes that the template is actually smaller - # than the dimensions of the capture. Since we are using SQDIFF - # the best match will be the min_val which is located at min_loc. - # The best match found in the image, set everything to 0 by default - # so that way the first match will overwrite these values - best_match = 0.0 - best_height = 0 - best_width = 0 - best_loc = (0, 0) - - # This tests 50 images scaled from 20% to 300% of the original template size - for scale in np.linspace(0.2, 3, num=56): - width = int(template.shape[1] * scale) - height = int(template.shape[0] * scale) - - # The template can not be larger than the capture - if width > capture.shape[1] or height > capture.shape[0]: - continue - - resized = cv2.resize(template, (width, height)) - - result = cv2.matchTemplate(capture, resized, cv2.TM_SQDIFF) - min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) - - # The maximum value for SQ_DIFF is dependent on the size of the template - # we need this value to normalize it from 0.0 to 1.0 - max_error = resized.size * 255 * 255 - similarity = 1 - (min_val / max_error) - - # Check if the similarity was good enough to get alignment - if similarity > best_match: - best_match = similarity - best_width = width - best_height = height - best_loc = min_loc - - # Go ahead and check if this satisfies our requirement before setting the region - # We don't want a low similarity image to be aligned. - if best_match < 0.9: - error_messages.alignmentNotMatchedError() - return - - # The new region can be defined by using the min_loc point and the - # height and width of the template. - self.rect.left = self.rect.left + best_loc[0] - self.rect.top = self.rect.top + best_loc[1] - self.rect.right = self.rect.left + best_width - self.rect.bottom = self.rect.top + best_height - - self.xSpinBox.setValue(self.rect.left) - self.ySpinBox.setValue(self.rect.top) - self.widthSpinBox.setValue(best_width) - self.heightSpinBox.setValue(best_height) - - -def validateBeforeComparison(self: AutoSplit, show_error: bool = True, check_empty_directory: bool = True): - error = None - if not self.split_image_directory: - error = error_messages.splitImageDirectoryError - elif not os.path.isdir(self.split_image_directory): - error = error_messages.splitImageDirectoryNotFoundError - elif check_empty_directory and not os.listdir(self.split_image_directory): - error = error_messages.splitImageDirectoryEmpty - elif self.hwnd <= 0 or win32gui.GetWindowText(self.hwnd) == '': - error = error_messages.regionError - if error and show_error: - error() - return not error - - -class BaseSelectWidget(QtWidgets.QWidget): - # We need to pull the monitor information to correctly draw the geometry covering all portions - # of the user's screen. These parameters create the bounding box with left, top, width, and height - SM_XVIRTUALSCREEN: int = user32.GetSystemMetrics(76) - SM_YVIRTUALSCREEN: int = user32.GetSystemMetrics(77) - SM_CXVIRTUALSCREEN: int = user32.GetSystemMetrics(78) - SM_CYVIRTUALSCREEN: int = user32.GetSystemMetrics(79) - - def __init__(self): - super().__init__() - self.setGeometry( - self.SM_XVIRTUALSCREEN, - self.SM_YVIRTUALSCREEN, - self.SM_CXVIRTUALSCREEN, - self.SM_CYVIRTUALSCREEN) - self.setWindowTitle(' ') - self.setWindowOpacity(0.5) - self.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint) - self.show() - - def keyPressEvent(self, event: QtGui.QKeyEvent): - if event.key() == QtCore.Qt.Key.Key_Escape: - self.close() - - -# Widget to select a window and obtain its bounds -class SelectWindowWidget(BaseSelectWidget): - x: int = -1 - y: int = -1 - - def mouseReleaseEvent(self, event: QtGui.QMouseEvent): - self.x = int(event.position().x()) + self.SM_XVIRTUALSCREEN - self.y = int(event.position().y()) + self.SM_YVIRTUALSCREEN - self.close() - - -# Widget for dragging screen region -# https://github.com/harupy/snipping-tool -class SelectRegionWidget(BaseSelectWidget): - height: int = 0 - width: int = 0 - left: int = -1 - top: int = -1 - right: int = -1 - bottom: int = -1 - __begin = QtCore.QPoint() - __end = QtCore.QPoint() - - def __init__(self): - super().__init__() - QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.CrossCursor)) - - def paintEvent(self, event: QtGui.QPaintEvent): - if self.__begin != self.__end: - qPainter = QtGui.QPainter(self) - qPainter.setPen(QtGui.QPen(QtGui.QColor('red'), 2)) - qPainter.setBrush(QtGui.QColor('opaque')) - qPainter.drawRect(QtCore.QRect(self.__begin, self.__end)) - - def mousePressEvent(self, event: QtGui.QMouseEvent): - self.__begin = event.position().toPoint() - self.__end = self.__begin - self.update() - - def mouseMoveEvent(self, event: QtGui.QMouseEvent): - self.__end = event.position().toPoint() - self.update() - - def mouseReleaseEvent(self, event: QtGui.QMouseEvent): - # The coordinates are pulled relative to the top left of the set geometry, - # so the added virtual screen offsets convert them back to the virtual screen coordinates - self.left = min(self.__begin.x(), self.__end.x()) + self.SM_XVIRTUALSCREEN - self.top = min(self.__begin.y(), self.__end.y()) + self.SM_YVIRTUALSCREEN - self.right = max(self.__begin.x(), self.__end.x()) + self.SM_XVIRTUALSCREEN - self.bottom = max(self.__begin.y(), self.__end.y()) + self.SM_YVIRTUALSCREEN - - self.height = self.bottom - self.top - self.width = self.right - self.left - if self.__begin != self.__end: - self.close() - - def close(self): - QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor)) - super().close() diff --git a/src/settings_file.py b/src/settings_file.py deleted file mode 100644 index 26f15b2d..00000000 --- a/src/settings_file.py +++ /dev/null @@ -1,310 +0,0 @@ -from __future__ import annotations -from typing import TYPE_CHECKING, List, Union -if TYPE_CHECKING: - from AutoSplit import AutoSplit - -from win32 import win32gui -from PyQt6 import QtWidgets -import os -import sys -import keyboard -import pickle - -from hotkeys import _hotkey_action -import error_messages - -# Get the directory of either AutoSplit.exe or AutoSplit.py -auto_split_directory = os.path.dirname(sys.executable if getattr(sys, 'frozen', False) else os.path.abspath(__file__)) - - -def getSaveSettingsValues(self: AutoSplit): - # get values to be able to save settings - self.x = self.xSpinBox.value() - self.y = self.ySpinBox.value() - self.width = self.widthSpinBox.value() - self.height = self.heightSpinBox.value() - self.similarity_threshold = self.similaritythresholdDoubleSpinBox.value() - self.comparison_index = self.comparisonmethodComboBox.currentIndex() - self.pause = self.pauseDoubleSpinBox.value() - self.fps_limit = self.fpslimitSpinBox.value() - self.split_key = self.splitLineEdit.text() - self.reset_key = self.resetLineEdit.text() - self.skip_split_key = self.skipsplitLineEdit.text() - self.undo_split_key = self.undosplitLineEdit.text() - self.pause_key = self.pausehotkeyLineEdit.text() - - if self.groupDummySplitsCheckBox.isChecked(): - self.group_dummy_splits_undo_skip_setting = 1 - else: - self.group_dummy_splits_undo_skip_setting = 0 - - if self.loopCheckBox.isChecked(): - self.loop_setting = 1 - else: - self.loop_setting = 0 - - if self.autostartonresetCheckBox.isChecked(): - self.auto_start_on_reset_setting = 1 - else: - self.auto_start_on_reset_setting = 0 - - -def haveSettingsChanged(self: AutoSplit): - self.getSaveSettingsValues() - current_save_settings = [ - self.split_image_directory, - self.similarity_threshold, - self.comparison_index, - self.pause, - self.fps_limit, - self.split_key, - self.reset_key, - self.skip_split_key, - self.undo_split_key, - self.pause_key, - self.x, - self.y, - self.width, - self.height, - self.hwnd_title, - 0, - 0, - self.group_dummy_splits_undo_skip_setting, - self.loop_setting, - self.auto_start_on_reset_setting] - - # One small caveat in this: if you load a settings file from an old version, but dont change settings, - # the current save settings and last load settings will have different # of elements and it will ask - # the user to save changes upon closing even though there were none - return current_save_settings not in (self.last_loaded_settings, self.last_saved_settings) - - -def saveSettings(self: AutoSplit): - if self.last_successfully_loaded_settings_file_path == None: - self.saveSettingsAs() - else: - self.getSaveSettingsValues() - self.last_saved_settings = [ - self.split_image_directory, - self.similarity_threshold, - self.comparison_index, - self.pause, - self.fps_limit, - self.split_key, - self.reset_key, - self.skip_split_key, - self.undo_split_key, - self.pause_key, - self.x, - self.y, - self.width, - self.height, - self.hwnd_title, - 0, - 0, - self.group_dummy_splits_undo_skip_setting, - self.loop_setting, - self.auto_start_on_reset_setting] - # save settings to a .pkl file - with open(self.last_successfully_loaded_settings_file_path, 'wb') as f: - pickle.dump(self.last_saved_settings, f) - - -def saveSettingsAs(self: AutoSplit): - # User picks save destination - self.save_settings_file_path = QtWidgets.QFileDialog.getSaveFileName( - self, - "Save Settings As", - os.path.join(auto_split_directory, "settings.pkl"), - "PKL (*.pkl)")[0] - - # If user cancels save destination window, don't save settings - if not self.save_settings_file_path: - return - - self.getSaveSettingsValues() - self.last_saved_settings = [ - self.split_image_directory, - self.similarity_threshold, - self.comparison_index, - self.pause, - self.fps_limit, - self.split_key, - self.reset_key, - self.skip_split_key, - self.undo_split_key, - self.pause_key, - self.x, - self.y, - self.width, - self.height, - self.hwnd_title, - 0, - 0, - self.group_dummy_splits_undo_skip_setting, - self.loop_setting, - self.auto_start_on_reset_setting] - - # save settings to a .pkl file - with open(self.save_settings_file_path, 'wb') as f: - pickle.dump(self.last_saved_settings, f) - - #wording is kinda off here but this needs to be here for an edge case: for when a file has never loaded, but you - #save file as successfully. - self.last_successfully_loaded_settings_file_path = self.save_settings_file_path - - -def loadSettings(self: AutoSplit, load_settings_on_open: bool = False, load_settings_from_livesplit: bool = False): - if load_settings_on_open: - - settings_files = [] - for file in os.listdir(auto_split_directory): - if file.endswith(".pkl"): - settings_files.append(file) - - # find all .pkls in AutoSplit folder, error if there is none or more than 1 - if len(settings_files) < 1: - error_messages.noSettingsFileOnOpenError() - self.last_loaded_settings = None - return - elif len(settings_files) > 1: - error_messages.tooManySettingsFilesOnOpenError() - self.last_loaded_settings = None - return - else: - self.load_settings_file_path = os.path.join(auto_split_directory, settings_files[0]) - - elif not load_settings_on_open and not load_settings_from_livesplit: - - self.load_settings_file_path = QtWidgets.QFileDialog.getOpenFileName( - self, - "Load Settings", - os.path.join(auto_split_directory, "settings.pkl"), - "PKL (*.pkl)")[0] - - if self.load_settings_file_path == '': - return - - try: - with open(self.load_settings_file_path, 'rb') as f: - settings: List[Union[str, int]] = pickle.load(f) - settings_count = len(settings) - if settings_count < 18: - if not load_settings_from_livesplit: - error_messages.oldVersionSettingsFileError() - return - # v1.3-1.4 settings. Add default pause_key and auto_start_on_reset_setting - if settings_count == 18: - settings.insert(9, '') - settings.insert(20, 0) - # v1.5 settings - elif settings_count != 20: - if not load_settings_from_livesplit: - error_messages.invalidSettingsError() - return - self.last_loaded_settings = [ - self.split_image_directory, - self.similarity_threshold, - self.comparison_index, - self.pause, - self.fps_limit, - self.split_key, - self.reset_key, - self.skip_split_key, - self.undo_split_key, - self.pause_key, - self.x, - self.y, - self.width, - self.height, - self.hwnd_title, - _, - _, - self.group_dummy_splits_undo_skip_setting, - self.loop_setting, - self.auto_start_on_reset_setting] = settings - except (FileNotFoundError, MemoryError, pickle.UnpicklingError): - # HACK / Workaround: Executing the error QMessageBox from the auto-controlled Worker Thread makes it hangs. - # I don't like this solution as we should probably ensure the Worker works nicely with PyQt instead, - # but in the mean time, this will do. - if not load_settings_from_livesplit: - error_messages.invalidSettingsError() - return - - self.splitimagefolderLineEdit.setText(self.split_image_directory) - self.similaritythresholdDoubleSpinBox.setValue(self.similarity_threshold) - self.pauseDoubleSpinBox.setValue(self.pause) - self.fpslimitSpinBox.setValue(self.fps_limit) - self.xSpinBox.setValue(self.x) - self.ySpinBox.setValue(self.y) - self.widthSpinBox.setValue(self.width) - self.heightSpinBox.setValue(self.height) - self.comparisonmethodComboBox.setCurrentIndex(self.comparison_index) - self.hwnd = win32gui.FindWindow(None, self.hwnd_title) - - # set custom checkbox's accordingly - self.groupDummySplitsCheckBox.setChecked(self.group_dummy_splits_undo_skip_setting == 1) - self.loopCheckBox.setChecked(self.loop_setting == 1) - self.autostartonresetCheckBox.setChecked(self.auto_start_on_reset_setting == 1) - self.autostartonresetCheckBox.setChecked(self.auto_start_on_reset_setting == 1) - - # TODO: Reuse code from hotkeys rather than duplicating here - # try to set hotkeys from when user last closed the window - try: - keyboard.unhook_key(self.split_hotkey) - # pass if the key is an empty string (hotkey was never set) - except (AttributeError, KeyError): - pass - try: - self.splitLineEdit.setText(self.split_key) - if not self.is_auto_controlled: - self.split_hotkey = keyboard.hook_key(self.split_key, lambda e: _hotkey_action(e, self.split_key, self.startAutoSplitter)) - except (ValueError, KeyError): - pass - - try: - keyboard.unhook_key(self.reset_hotkey) - except (AttributeError, KeyError): - pass - try: - self.resetLineEdit.setText(self.reset_key) - if not self.is_auto_controlled: - self.reset_hotkey = keyboard.hook_key(self.reset_key, lambda e: _hotkey_action(e, self.reset_key, self.startReset)) - except (ValueError, KeyError): - pass - - try: - keyboard.unhook_key(self.skip_split_hotkey) - except (AttributeError, KeyError): - pass - try: - self.skipsplitLineEdit.setText(self.skip_split_key) - if not self.is_auto_controlled: - self.skip_split_hotkey = keyboard.hook_key(self.skip_split_key, lambda e: _hotkey_action(e, self.skip_split_key, self.startSkipSplit)) - except (ValueError, KeyError): - pass - - try: - keyboard.unhook_key(self.undo_split_hotkey) - except (AttributeError, KeyError): - pass - try: - self.undosplitLineEdit.setText(self.undo_split_key) - if not self.is_auto_controlled: - self.undo_split_hotkey = keyboard.hook_key(self.undo_split_key, lambda e: _hotkey_action(e, self.undo_split_key, self.startUndoSplit)) - except (ValueError, KeyError): - pass - - try: - keyboard.unhook_key(self.pause_hotkey) - except (AttributeError, KeyError): - pass - try: - self.pausehotkeyLineEdit.setText(self.pause_key) - if not self.is_auto_controlled: - self.pause_hotkey = keyboard.hook_key(self.pause_key, lambda e: _hotkey_action(e, self.pause_key, self.startPause)) - except (ValueError, KeyError): - pass - - self.last_successfully_loaded_settings_file_path = self.load_settings_file_path - self.checkLiveImage() diff --git a/src/split_parser.py b/src/split_parser.py index cfcf63fc..250c866d 100644 --- a/src/split_parser.py +++ b/src/split_parser.py @@ -1,70 +1,93 @@ -def threshold_from_filename(filename): +from __future__ import annotations + +import os +from typing import TYPE_CHECKING, TypeVar + +import error_messages +from AutoSplitImage import RESET_KEYWORD, START_KEYWORD, AutoSplitImage, ImageType +from utils import is_valid_image + +if TYPE_CHECKING: + from AutoSplit import AutoSplit + +[ + DUMMY_FLAG, + BELOW_FLAG, + PAUSE_FLAG, + *_, +] = [1 << i for i in range(31)] # 32 bits of flags + +T = TypeVar("T", str, int, float) + +# Note, the following symbols cannot be used in a filename: +# / \ : * ? " < > | + + +def __value_from_filename( + filename: str, + delimiters: str, + default_value: T, +) -> T: + if len(delimiters) != 2: # noqa: PLR2004 + raise ValueError("delimiters parameter must contain exactly 2 characters") + try: + value_type = type(default_value) + value = value_type(filename.split(delimiters[0], 1)[1].split(delimiters[1])[0]) + except (IndexError, ValueError): + return default_value + else: + return value + + +def threshold_from_filename(filename: str): """ Retrieve the threshold from the filename, if there is no threshold or the threshold - doesn't meet the requirements of being between 0.0 and 1.0, then None is returned. + doesn't meet the requirements of being [0, 1], then None is returned. @param filename: String containing the file's name @return: A valid threshold, if not then None """ - # Check to make sure there is a valid floating point number between # parentheses of the filename - try: - threshold = float(filename.split('(', 1)[1].split(')')[0]) - except: - return None + value = __value_from_filename(filename, "()", -1.0) # Check to make sure if it is a valid threshold - if (threshold > 1.0 or threshold < 0.0): - return None - else: - return threshold + return value if 0 <= value <= 1 else None + -def pause_from_filename(filename): +def pause_from_filename(filename: str): """ Retrieve the pause time from the filename, if there is no pause time or the pause time - isn't a valid number, then None is returned + isn't a valid positive number or 0, then None is returned. @param filename: String containing the file's name @return: A valid pause time, if not then None """ - # Check to make sure there is a valid pause time between brackets # of the filename - try: - pause = float(filename.split('[', 1)[1].split(']')[0]) - except: - return None + value = __value_from_filename(filename, "[]", -1.0) # Pause times should always be positive or zero - if (pause < 0.0): - return None - else: - return pause + return value if value >= 0 else None -def delay_from_filename(filename): + +def delay_time_from_filename(filename: str): """ Retrieve the delay time from the filename, if there is no delay time or the delay time - isn't a valid number, then 0 is returned + isn't a valid positive number or 0 number, then None is returned. @param filename: String containing the file's name - @return: A valid delay time, if not then 0 + @return: A valid delay time, if not then none """ - # Check to make sure there is a valid delay time between brackets # of the filename - try: - delay = float(filename.split('#', 1)[1].split('#')[0]) - except: - return 0.0 + value = __value_from_filename(filename, "##", -1) # Delay times should always be positive or zero - if (delay < 0): - return 0.0 - else: - return delay + return value if value >= 0 else None + -def loop_from_filename(filename): +def loop_from_filename(filename: str): """ Retrieve the number of loops from filename, if there is no loop number or the loop number isn't valid, then 1 is returned. @@ -72,60 +95,62 @@ def loop_from_filename(filename): @param filename: String containing the file's name @return: A valid loop number, if not then 1 """ - # Check to make sure there is a valid delay time between brackets # of the filename - try: - loop = int(filename.split('@', 1)[1].split('@')[0]) - except: - return 1 + value = __value_from_filename(filename, "@@", 1) - # Delay times should always be positive or zero - if (loop < 1): - return 1 - else: - return loop + # Loop should always be positive + return value if value >= 1 else 1 + + +def comparison_method_from_filename(filename: str): + """ + Retrieve the comparison method index from filename, if there is no comparison method or the index isn't valid, + then None is returned. + @param filename: String containing the file's name + @return: A valid comparison method index, if not then none + """ + # Check to make sure there is a valid delay time between brackets + # of the filename + value = __value_from_filename(filename, "^^", -1) -DUMMY_FLAG = 1 << 0 -MASK_FLAG = 1 << 1 #Legacy flag. Allows support for {md}, {mp}, or {mb} flags previously required to detect transparancy. -BELOW_FLAG = 1 << 2 -PAUSE_FLAG = 1 << 3 + # Comparison method should always be positive or zero + return value if value >= 0 else None -def flags_from_filename(filename): +def flags_from_filename(filename: str): """ - Retrieve the flags from the filename, if there are no flags then 0 is returned + Retrieve the flags from the filename, if there are no flags then 0 is returned. @param filename: String containing the file's name @return: The flags as an integer, if invalid flags are found it returns 0 - """ + list of flags: + "d" = dummy, do nothing when this split is found + "b" = below threshold, after threshold is met, split when it goes below the threhsold. + "p" = pause, hit pause key when this split is found """ - List of flags: - 'd' = dummy, do nothing when this split is found - 'b' = below threshold, after threshold is met, split when it goes below the threhsold. - 'p' = pause, hit pause key when this split is found - """ - # Check to make sure there are flags between curly braces # of the filename - try: - flags_str = filename.split('{', 1)[1].split('}')[0] - except: + flags_str = __value_from_filename(filename, "{}", "") + + if not flags_str: return 0 flags = 0x00 - for c in flags_str: - if c.upper() == 'D': + for flag_str in flags_str: + character = flag_str.upper() + if character == "D": flags |= DUMMY_FLAG - elif c.upper() == 'M': - flags |= MASK_FLAG - elif c.upper() == 'B': + elif character == "B": flags |= BELOW_FLAG - elif c.upper() == 'P': + elif character == "P": flags |= PAUSE_FLAG + # Legacy flags + elif character == "M": + continue else: # An invalid flag was caught, this filename was written incorrectly # return 0. We don't want to interpret any misleading filenames @@ -138,21 +163,61 @@ def flags_from_filename(filename): return flags -def is_reset_image(filename): - """ - Checks if the image is used for resetting - - @param filename: String containing the file's name - @return: True if its a reset image - """ - return ('RESET' in filename.upper()) - -def is_start_auto_splitter_image(filename): - """ - Checks if the image is used to start AutoSplit - - @param filename: String containing the file's name - @return: True if its a reset image - """ - return ('START_AUTO_SPLITTER' in filename.upper()) +def __pop_image_type(split_image: list[AutoSplitImage], image_type: ImageType): + for image in split_image: + if image.image_type == image_type: + split_image.remove(image) + return image + + return None + + +def parse_and_validate_images(autosplit: AutoSplit): + # Get split images + all_images = [ + AutoSplitImage(os.path.join(autosplit.settings_dict["split_image_directory"], image_name)) + for image_name + in os.listdir(autosplit.settings_dict["split_image_directory"]) + ] + + # Find non-split images and then remove them from the list + autosplit.start_image = __pop_image_type(all_images, ImageType.START) + autosplit.reset_image = __pop_image_type(all_images, ImageType.RESET) + autosplit.split_images = all_images + + # Make sure that each of the images follows the guidelines for correct format + # according to all of the settings selected by the user. + for image in autosplit.split_images: + # Test for image without transparency + if not is_valid_image(image.byte_array): + autosplit.gui_changes_on_reset() + return False + + # error out if there is a {p} flag but no pause hotkey set and is not auto controlled. + if ( + not autosplit.settings_dict["pause_hotkey"] + and image.check_flag(PAUSE_FLAG) + and not autosplit.is_auto_controlled + ): + autosplit.gui_changes_on_reset() + error_messages.pause_hotkey() + return False + + # Check that there's only one reset image + if image.image_type == ImageType.RESET: + # If there is no reset hotkey set but a reset image is present, and is not auto controlled, throw an error. + if not autosplit.settings_dict["reset_hotkey"] and not autosplit.is_auto_controlled: + autosplit.gui_changes_on_reset() + error_messages.reset_hotkey() + return False + autosplit.gui_changes_on_reset() + error_messages.multiple_keyword_images(RESET_KEYWORD) + return False + + # Check that there's only one start image + if image.image_type == ImageType.START: + autosplit.gui_changes_on_reset() + error_messages.multiple_keyword_images(START_KEYWORD) + return False + return True diff --git a/src/update_checker.py b/src/update_checker.py deleted file mode 100644 index 693b769b..00000000 --- a/src/update_checker.py +++ /dev/null @@ -1,77 +0,0 @@ -# Form implementation generated from reading ui file '.\res\update_checker.ui' -# -# Created by: PyQt6 UI code generator 6.1.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_UpdateChecker(object): - def setupUi(self, UpdateChecker): - UpdateChecker.setObjectName("UpdateChecker") - UpdateChecker.setEnabled(True) - UpdateChecker.resize(313, 133) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(UpdateChecker.sizePolicy().hasHeightForWidth()) - UpdateChecker.setSizePolicy(sizePolicy) - UpdateChecker.setMinimumSize(QtCore.QSize(313, 133)) - UpdateChecker.setMaximumSize(QtCore.QSize(313, 133)) - font = QtGui.QFont() - font.setPointSize(9) - UpdateChecker.setFont(font) - UpdateChecker.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.labelUpdateStatus = QtWidgets.QLabel(UpdateChecker) - self.labelUpdateStatus.setGeometry(QtCore.QRect(17, 10, 281, 16)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.labelUpdateStatus.sizePolicy().hasHeightForWidth()) - self.labelUpdateStatus.setSizePolicy(sizePolicy) - self.labelUpdateStatus.setText("") - self.labelUpdateStatus.setObjectName("labelUpdateStatus") - self.labelCurrentVersion = QtWidgets.QLabel(UpdateChecker) - self.labelCurrentVersion.setGeometry(QtCore.QRect(17, 30, 91, 16)) - self.labelCurrentVersion.setObjectName("labelCurrentVersion") - self.labelLatestVersion = QtWidgets.QLabel(UpdateChecker) - self.labelLatestVersion.setGeometry(QtCore.QRect(17, 50, 81, 16)) - self.labelLatestVersion.setObjectName("labelLatestVersion") - self.labelGoToDownload = QtWidgets.QLabel(UpdateChecker) - self.labelGoToDownload.setGeometry(QtCore.QRect(17, 76, 241, 16)) - self.labelGoToDownload.setText("") - self.labelGoToDownload.setObjectName("labelGoToDownload") - self.pushButtonLeft = QtWidgets.QPushButton(UpdateChecker) - self.pushButtonLeft.setGeometry(QtCore.QRect(150, 100, 75, 24)) - self.pushButtonLeft.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.pushButtonLeft.setText("") - self.pushButtonLeft.setObjectName("pushButtonLeft") - self.pushButtonRight = QtWidgets.QPushButton(UpdateChecker) - self.pushButtonRight.setGeometry(QtCore.QRect(230, 100, 75, 24)) - self.pushButtonRight.setText("") - self.pushButtonRight.setObjectName("pushButtonRight") - self.labelCurrentVersionNumber = QtWidgets.QLabel(UpdateChecker) - self.labelCurrentVersionNumber.setGeometry(QtCore.QRect(120, 30, 181, 16)) - self.labelCurrentVersionNumber.setText("") - self.labelCurrentVersionNumber.setObjectName("labelCurrentVersionNumber") - self.labelLatestVersionNumber = QtWidgets.QLabel(UpdateChecker) - self.labelLatestVersionNumber.setGeometry(QtCore.QRect(120, 50, 181, 16)) - self.labelLatestVersionNumber.setText("") - self.labelLatestVersionNumber.setObjectName("labelLatestVersionNumber") - self.checkBoxDoNotAskMeAgain = QtWidgets.QCheckBox(UpdateChecker) - self.checkBoxDoNotAskMeAgain.setGeometry(QtCore.QRect(17, 100, 141, 20)) - self.checkBoxDoNotAskMeAgain.setObjectName("checkBoxDoNotAskMeAgain") - - self.retranslateUi(UpdateChecker) - self.pushButtonRight.clicked.connect(UpdateChecker.close) - QtCore.QMetaObject.connectSlotsByName(UpdateChecker) - - def retranslateUi(self, UpdateChecker): - _translate = QtCore.QCoreApplication.translate - UpdateChecker.setWindowTitle(_translate("UpdateChecker", "Update Checker")) - self.labelCurrentVersion.setText(_translate("UpdateChecker", "Current Version:")) - self.labelLatestVersion.setText(_translate("UpdateChecker", "Latest Version:")) - self.checkBoxDoNotAskMeAgain.setText(_translate("UpdateChecker", "Do not ask me again")) diff --git a/src/user_profile.py b/src/user_profile.py new file mode 100644 index 00000000..5d86ef8d --- /dev/null +++ b/src/user_profile.py @@ -0,0 +1,215 @@ +from __future__ import annotations + +import os +from typing import TYPE_CHECKING, TypedDict, cast + +import toml +from PySide6 import QtCore, QtWidgets + +import error_messages +from capture_method import CAPTURE_METHODS, CaptureMethodEnum, Region, change_capture_method +from gen import design +from hotkeys import HOTKEYS, remove_all_hotkeys, set_hotkey +from utils import auto_split_directory + +if TYPE_CHECKING: + from AutoSplit import AutoSplit + + +class UserProfileDict(TypedDict): + split_hotkey: str + reset_hotkey: str + undo_split_hotkey: str + skip_split_hotkey: str + pause_hotkey: str + toggle_auto_reset_image_hotkey: str + fps_limit: int + live_capture_region: bool + enable_auto_reset: bool + capture_method: str | CaptureMethodEnum + capture_device_id: int + capture_device_name: str + default_comparison_method: int + default_similarity_threshold: float + default_delay_time: int + default_pause_time: float + loop_splits: bool + split_image_directory: str + captured_window_title: str + capture_region: Region + + +DEFAULT_PROFILE = UserProfileDict( + split_hotkey="", + reset_hotkey="", + undo_split_hotkey="", + skip_split_hotkey="", + pause_hotkey="", + toggle_auto_reset_image_hotkey="", + fps_limit=60, + live_capture_region=True, + enable_auto_reset=True, + capture_method=CAPTURE_METHODS.get_method_by_index(0), + capture_device_id=0, + capture_device_name="", + default_comparison_method=0, + default_similarity_threshold=0.95, + default_delay_time=0, + default_pause_time=10, + loop_splits=False, + split_image_directory="", + captured_window_title="", + capture_region=Region(x=0, y=0, width=1, height=1), +) + + +def have_settings_changed(autosplit: AutoSplit): + return autosplit.settings_dict not in (autosplit.last_loaded_settings, autosplit.last_saved_settings) + + +def save_settings(autosplit: AutoSplit): + """@return: The save settings filepath. Or None if "Save Settings As" is cancelled.""" + return __save_settings_to_file(autosplit, autosplit.last_successfully_loaded_settings_file_path) \ + if autosplit.last_successfully_loaded_settings_file_path \ + else save_settings_as(autosplit) + + +def save_settings_as(autosplit: AutoSplit): + """@return: The save settings filepath selected. Empty if cancelled.""" + # User picks save destination + save_settings_file_path = cast( + str, # https://bugreports.qt.io/browse/PYSIDE-2285 + QtWidgets.QFileDialog.getSaveFileName( + autosplit, + "Save Settings As", + autosplit.last_successfully_loaded_settings_file_path + or os.path.join(auto_split_directory, "settings.toml"), + "TOML (*.toml)", + )[0], + ) + + # If user cancels save destination window, don't save settings + if not save_settings_file_path: + return "" + + return __save_settings_to_file(autosplit, save_settings_file_path) + + +def __save_settings_to_file(autosplit: AutoSplit, save_settings_file_path: str): + autosplit.last_saved_settings = autosplit.settings_dict + # Save settings to a .toml file + with open(save_settings_file_path, "w", encoding="utf-8") as file: + toml.dump(autosplit.last_saved_settings, file) + autosplit.last_successfully_loaded_settings_file_path = save_settings_file_path + return save_settings_file_path + + +def __load_settings_from_file(autosplit: AutoSplit, load_settings_file_path: str): + if load_settings_file_path.endswith(".pkl"): + autosplit.show_error_signal.emit(error_messages.old_version_settings_file) + return False + try: + with open(load_settings_file_path, encoding="utf-8") as file: + # Casting here just so we can build an actual UserProfileDict once we're done validating + # Fallback to default settings if some are missing from the file. This happens when new settings are added. + loaded_settings = cast( + UserProfileDict, { + **DEFAULT_PROFILE, + **toml.load(file), + }, + ) + # TODO: Data Validation / fallbacks ? + autosplit.settings_dict = UserProfileDict(**loaded_settings) + autosplit.last_loaded_settings = autosplit.settings_dict + + autosplit.x_spinbox.setValue(autosplit.settings_dict["capture_region"]["x"]) + autosplit.y_spinbox.setValue(autosplit.settings_dict["capture_region"]["y"]) + autosplit.width_spinbox.setValue(autosplit.settings_dict["capture_region"]["width"]) + autosplit.height_spinbox.setValue(autosplit.settings_dict["capture_region"]["height"]) + autosplit.split_image_folder_input.setText(autosplit.settings_dict["split_image_directory"]) + except (FileNotFoundError, MemoryError, TypeError, toml.TomlDecodeError): + autosplit.show_error_signal.emit(error_messages.invalid_settings) + return False + + remove_all_hotkeys() + if not autosplit.is_auto_controlled: + for hotkey, hotkey_name in [(hotkey, f"{hotkey}_hotkey") for hotkey in HOTKEYS]: + if autosplit.settings_dict[hotkey_name]: # pyright: ignore[reportGeneralTypeIssues] + set_hotkey( + autosplit, + hotkey, + cast(str, autosplit.settings_dict[hotkey_name]), # pyright: ignore[reportGeneralTypeIssues] + ) + + change_capture_method(cast(CaptureMethodEnum, autosplit.settings_dict["capture_method"]), autosplit) + if autosplit.settings_dict["capture_method"] != CaptureMethodEnum.VIDEO_CAPTURE_DEVICE: + autosplit.capture_method.recover_window(autosplit.settings_dict["captured_window_title"], autosplit) + if not autosplit.capture_method.check_selected_region_exists(autosplit): + autosplit.live_image.setText( + "Reload settings after opening" + + f"\n{autosplit.settings_dict['captured_window_title']!r}" + + "\nto automatically load Capture Region", + ) + + return True + + +def load_settings(autosplit: AutoSplit, from_path: str = ""): + load_settings_file_path = from_path or cast( + str, # https://bugreports.qt.io/browse/PYSIDE-2285 + QtWidgets.QFileDialog.getOpenFileName( + autosplit, + "Load Profile", + os.path.join(auto_split_directory, "settings.toml"), + "TOML (*.toml)", + )[0], + ) + if not (load_settings_file_path and __load_settings_from_file(autosplit, load_settings_file_path)): + return + + autosplit.last_successfully_loaded_settings_file_path = load_settings_file_path + # TODO: Should this check be in `__load_start_image` ? + if not autosplit.is_running: + autosplit.load_start_image_signal.emit() + + +def load_settings_on_open(autosplit: AutoSplit): + settings_files = [ + file for file + in os.listdir(auto_split_directory) + if file.endswith(".toml") + ] + + # Find all .tomls in AutoSplit folder, error if there is not exactly 1 + error = None + if len(settings_files) < 1: + error = error_messages.no_settings_file_on_open + elif len(settings_files) > 1: + error = error_messages.too_many_settings_files_on_open + if error: + change_capture_method(CAPTURE_METHODS.get_method_by_index(0), autosplit) + error() + return + + load_settings(autosplit, os.path.join(auto_split_directory, settings_files[0])) + + +def load_check_for_updates_on_open(autosplit: AutoSplit): + """ + Retrieve the "Check For Updates On Open" QSettings and set the checkbox state + These are only global settings values. They are not *toml settings values. + """ + value = QtCore \ + .QSettings("AutoSplit", "Check For Updates On Open") \ + .value("check_for_updates_on_open", True, type=bool) + # Type not infered by PySide6 + # TODO: Report this issue upstream + autosplit.action_check_for_updates_on_open.setChecked(value) # pyright: ignore[reportGeneralTypeIssues] + + +def set_check_for_updates_on_open(design_window: design.Ui_MainWindow, value: bool): + """Sets the "Check For Updates On Open" QSettings value and the checkbox state.""" + design_window.action_check_for_updates_on_open.setChecked(value) + QtCore \ + .QSettings("AutoSplit", "Check For Updates On Open") \ + .setValue("check_for_updates_on_open", value) diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 00000000..4dd16158 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,173 @@ +from __future__ import annotations + +import asyncio +import ctypes +import ctypes.wintypes +import os +import sys +from collections.abc import Callable, Iterable +from enum import IntEnum +from platform import version +from threading import Thread +from typing import Any, TypeVar, cast + +import cv2 +from typing_extensions import TypeGuard +from win32 import win32gui +from winsdk.windows.ai.machinelearning import LearningModelDevice, LearningModelDeviceKind +from winsdk.windows.media.capture import MediaCapture + +from gen.build_vars import AUTOSPLIT_BUILD_NUMBER, AUTOSPLIT_GITHUB_REPOSITORY + +DWMWA_EXTENDED_FRAME_BOUNDS = 9 +MAXBYTE = 255 +RGB_CHANNEL_COUNT = 3 +"""How many channels in an RGB image""" +RGBA_CHANNEL_COUNT = 4 +"""How many channels in an RGB image""" + + +class ImageShape(IntEnum): + X = 0 + Y = 1 + Alpha = 2 + + +class ColorChannel(IntEnum): + Red = 0 + Green = 1 + Blue = 2 + Alpha = 3 + + +def decimal(value: int | float): + # NOTE: The coeficient (1000) has to be above what's mathematically necessary (100) + # because of python float rounding errors (ie: xx.99999999999999) + return f"{int(value * 1000) / 1000:.2f}" + + +def is_digit(value: str | int | None): + """Checks if `value` is a single-digit string from 0-9.""" + if value is None: + return False + try: + return 0 <= int(value) <= 9 # noqa: PLR2004 + except (ValueError, TypeError): + return False + + +def is_valid_image(image: cv2.Mat | None) -> TypeGuard[cv2.Mat]: + return image is not None and bool(image.size) + + +def is_valid_hwnd(hwnd: int): + """Validate the hwnd points to a valid window and not the desktop or whatever window obtained with `""`.""" + if not hwnd: + return False + if sys.platform == "win32": + # TODO: Fix stubs, IsWindow should return a boolean + return bool(win32gui.IsWindow(hwnd) and win32gui.GetWindowText(hwnd)) + return True + + +T = TypeVar("T") + + +def first(iterable: Iterable[T]) -> T: + """@return: The first element of a collection. Dictionaries will return the first key.""" + return next(iter(iterable)) + + +def get_window_bounds(hwnd: int) -> tuple[int, int, int, int]: + extended_frame_bounds = ctypes.wintypes.RECT() + ctypes.windll.dwmapi.DwmGetWindowAttribute( + hwnd, + DWMWA_EXTENDED_FRAME_BOUNDS, + ctypes.byref(extended_frame_bounds), + ctypes.sizeof(extended_frame_bounds), + ) + + window_rect = win32gui.GetWindowRect(hwnd) + window_left_bounds = cast(int, extended_frame_bounds.left) - window_rect[0] + window_top_bounds = cast(int, extended_frame_bounds.top) - window_rect[1] + window_width = cast(int, extended_frame_bounds.right) - cast(int, extended_frame_bounds.left) + window_height = cast(int, extended_frame_bounds.bottom) - cast(int, extended_frame_bounds.top) + return window_left_bounds, window_top_bounds, window_width, window_height + + +def open_file(file_path: str | bytes | os.PathLike[str] | os.PathLike[bytes]): + os.startfile(file_path) # noqa: S606 + + +def get_or_create_eventloop(): + try: + return asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + return asyncio.get_event_loop() + + +def get_direct3d_device(): + # Note: Must create in the same thread (can't use a global) otherwise when ran from LiveSplit it will raise: + # OSError: The application called an interface that was marshalled for a different thread + media_capture = MediaCapture() + + async def init_mediacapture(): + await (media_capture.initialize_async() or asyncio.sleep(0)) + asyncio.run(init_mediacapture()) + direct_3d_device = media_capture.media_capture_settings and \ + media_capture.media_capture_settings.direct3_d11_device + if not direct_3d_device: + try: + # May be problematic? https://github.com/pywinrt/python-winsdk/issues/11#issuecomment-1315345318 + direct_3d_device = LearningModelDevice(LearningModelDeviceKind.DIRECT_X_HIGH_PERFORMANCE).direct3_d11_device + # TODO: Unknown potential error, I don't have an older Win10 machine to test. + except BaseException: # noqa: S110,BLE001 + pass + if not direct_3d_device: + raise OSError("Unable to initialize a Direct3D Device.") + return direct_3d_device + + +def try_get_direct3d_device(): + try: + return get_direct3d_device() + except OSError: + return None + + +def fire_and_forget(func: Callable[..., Any]): + """ + Runs synchronous function asynchronously without waiting for a response. + + Uses threads on Windows because ~~`RuntimeError: There is no current event loop in thread 'MainThread'.`~~ + Because maybe asyncio has issues. Unsure. See alpha.5 and https://github.com/Avasam/AutoSplit/issues/36 + + Uses asyncio on Linux because of a `Segmentation fault (core dumped)` + """ + def wrapped(*args: Any, **kwargs: Any): + if sys.platform == "win32": + thread = Thread(target=func, args=args, kwargs=kwargs) + thread.start() + return thread + return get_or_create_eventloop().run_in_executor(None, func, *args, *kwargs) + + return wrapped + + +# Environment specifics +WINDOWS_BUILD_NUMBER = int(version().split(".")[-1]) if sys.platform == "win32" else -1 +FIRST_WIN_11_BUILD = 22000 +"""AutoSplit Version number""" +WGC_MIN_BUILD = 17134 +"""https://docs.microsoft.com/en-us/uwp/api/windows.graphics.capture.graphicscapturepicker#applies-to""" +FROZEN = hasattr(sys, "frozen") +"""Running from build made by PyInstaller""" +auto_split_directory = os.path.dirname(sys.executable if FROZEN else os.path.abspath(__file__)) +"""The directory of either the AutoSplit executable or AutoSplit.py""" + +# Shared strings +# Check `excludeBuildNumber` during workflow dispatch build generate a clean version number +AUTOSPLIT_VERSION = "2.0.0-beta.3" + (f"-{AUTOSPLIT_BUILD_NUMBER}" if AUTOSPLIT_BUILD_NUMBER else "") +GITHUB_REPOSITORY = AUTOSPLIT_GITHUB_REPOSITORY diff --git a/typings/cv2/Error.pyi b/typings/cv2/Error.pyi new file mode 100644 index 00000000..40853539 --- /dev/null +++ b/typings/cv2/Error.pyi @@ -0,0 +1,110 @@ +BAD_ALIGN: int +BAD_ALPHA_CHANNEL: int +BAD_CALL_BACK: int +BAD_COI: int +BAD_DATA_PTR: int +BAD_DEPTH: int +BAD_IMAGE_SIZE: int +BAD_MODEL_OR_CH_SEQ: int +BAD_NUM_CHANNEL1U: int +BAD_NUM_CHANNELS: int +BAD_OFFSET: int +BAD_ORDER: int +BAD_ORIGIN: int +BAD_ROISIZE: int +BAD_STEP: int +BAD_TILE_SIZE: int +BadAlign: int +BadAlphaChannel: int +BadCOI: int +BadCallBack: int +BadDataPtr: int +BadDepth: int +BadImageSize: int +BadModelOrChSeq: int +BadNumChannel1U: int +BadNumChannels: int +BadOffset: int +BadOrder: int +BadOrigin: int +BadROISize: int +BadStep: int +BadTileSize: int +GPU_API_CALL_ERROR: int +GPU_NOT_SUPPORTED: int +GpuApiCallError: int +GpuNotSupported: int +HEADER_IS_NULL: int +HeaderIsNull: int +MASK_IS_TILED: int +MaskIsTiled: int +OPEN_CLAPI_CALL_ERROR: int +OPEN_CLDOUBLE_NOT_SUPPORTED: int +OPEN_CLINIT_ERROR: int +OPEN_CLNO_AMDBLAS_FFT: int +OPEN_GL_API_CALL_ERROR: int +OPEN_GL_NOT_SUPPORTED: int +OpenCLApiCallError: int +OpenCLDoubleNotSupported: int +OpenCLInitError: int +OpenCLNoAMDBlasFft: int +OpenGlApiCallError: int +OpenGlNotSupported: int +STS_ASSERT: int +STS_AUTO_TRACE: int +STS_BACK_TRACE: int +STS_BAD_ARG: int +STS_BAD_FLAG: int +STS_BAD_FUNC: int +STS_BAD_MASK: int +STS_BAD_MEM_BLOCK: int +STS_BAD_POINT: int +STS_BAD_SIZE: int +STS_DIV_BY_ZERO: int +STS_ERROR: int +STS_FILTER_OFFSET_ERR: int +STS_FILTER_STRUCT_CONTENT_ERR: int +STS_INPLACE_NOT_SUPPORTED: int +STS_INTERNAL: int +STS_KERNEL_STRUCT_CONTENT_ERR: int +STS_NOT_IMPLEMENTED: int +STS_NO_CONV: int +STS_NO_MEM: int +STS_NULL_PTR: int +STS_OBJECT_NOT_FOUND: int +STS_OK: int +STS_OUT_OF_RANGE: int +STS_PARSE_ERROR: int +STS_UNMATCHED_FORMATS: int +STS_UNMATCHED_SIZES: int +STS_UNSUPPORTED_FORMAT: int +STS_VEC_LENGTH_ERR: int +StsAssert: int +StsAutoTrace: int +StsBackTrace: int +StsBadArg: int +StsBadFlag: int +StsBadFunc: int +StsBadMask: int +StsBadMemBlock: int +StsBadPoint: int +StsBadSize: int +StsDivByZero: int +StsError: int +StsFilterOffsetErr: int +StsFilterStructContentErr: int +StsInplaceNotSupported: int +StsInternal: int +StsKernelStructContentErr: int +StsNoConv: int +StsNoMem: int +StsNotImplemented: int +StsNullPtr: int +StsObjectNotFound: int +StsOk: int +StsOutOfRange: int +StsParseError: int +StsUnmatchedFormats: int +StsUnmatchedSizes: int +StsUnsupportedFormat: int +StsVecLengthErr: int diff --git a/typings/cv2/__init__.pyi b/typings/cv2/__init__.pyi new file mode 100644 index 00000000..3a7ad7f6 --- /dev/null +++ b/typings/cv2/__init__.pyi @@ -0,0 +1,22 @@ +from cv2 import ( + Error as Error, + data as data, + gapi as gapi, + mat_wrapper as mat_wrapper, + misc as misc, + utils as utils, + version as version, +) +from cv2.cv2 import * # noqa: F403 +from cv2.mat_wrapper import Mat as WrappedMat, _NDArray +from typing_extensions import TypeAlias + +__all__: list[str] = [] + + +def bootstrap() -> None: ... + + +Mat: TypeAlias = WrappedMat | _NDArray +# TODO: Make Mat generic with int or float +_MatF: TypeAlias = WrappedMat | _NDArray diff --git a/typings/cv2/cv2.pyi b/typings/cv2/cv2.pyi new file mode 100644 index 00000000..e46c09fe --- /dev/null +++ b/typings/cv2/cv2.pyi @@ -0,0 +1,5247 @@ +from collections.abc import Sequence +from typing import ClassVar, Union, overload + +from _typeshed import Incomplete +from cv2 import Mat, _MatF +from cv2.gapi.streaming import queue_capacity +from typing_extensions import TypeAlias + +# Y047 & Y018 (Unused TypeAlias and TypeVar): Helper types reused everywhere. +# The noqa comments won't be necessary when types in this module are more complete and use the aliases + +# Function argument types +# Convertable to boolean +_Boolean: TypeAlias = bool | int | None +# "a scalar" +_NumericScalar: TypeAlias = float | bool | None +# cv::Scalar +_Scalar: TypeAlias = Mat | _NumericScalar | Sequence[_NumericScalar] +# cv::TermCriteria +_TermCriteria: TypeAlias = Union[tuple[int, int, float], Sequence[float]] +# cv::Point +_Point: TypeAlias = Union[tuple[int, int], Sequence[int]] +# cv::Size +_Size: TypeAlias = Union[tuple[int, int], Sequence[int]] +# cv::Range +_Range: TypeAlias = Union[tuple[int, int], Sequence[int]] +# cv::Point +_PointFloat: TypeAlias = Union[tuple[float, float], Sequence[float]] +# cv::Size +_SizeFloat: TypeAlias = Union[tuple[float, float], Sequence[float]] +# cv::Rect +_Rect: TypeAlias = Union[tuple[int, int, int, int], Sequence[int]] +# cv::Rect +_RectFloat: TypeAlias = Union[tuple[int, int, int, int], Sequence[int]] +# cv::RotatedRect +_RotatedRect: TypeAlias = Union[tuple[_PointFloat, _SizeFloat, float], Sequence[_PointFloat | _SizeFloat | float]] +_RotatedRectResult: TypeAlias = tuple[tuple[float, float], tuple[float, float], float] +# cv:UMat, cv::InputArray, cv::OutputArray and cv::InputOutputArray +_UMat: TypeAlias = UMat | _MatF | _NumericScalar + +# TODO: Complete types until all the aliases below are gone! +# These are temporary placeholder return types, as were in the docstrings signatures from microsoft/python-type-stubs +# This is often (but not always) a sign that a TypeVar should be used to return the same type as a param. +# retval is equivalent to Unknown +_flow: TypeAlias = Incomplete +_image: TypeAlias = Incomplete +_edgeList: TypeAlias = Incomplete +_leadingEdgeList: TypeAlias = Incomplete +_triangleList: TypeAlias = Incomplete +_matches_info: TypeAlias = Incomplete +_arg3: TypeAlias = Incomplete +_outputBlobs: TypeAlias = Incomplete +_layersTypes: TypeAlias = Incomplete +_detections: TypeAlias = Incomplete +_results: TypeAlias = Incomplete +_corners: TypeAlias = Incomplete +_pts: TypeAlias = Incomplete +_dst: TypeAlias = Incomplete +_markers: TypeAlias = Incomplete +_masks: TypeAlias = Incomplete +_window: TypeAlias = Incomplete +_edges: TypeAlias = Incomplete +_lowerBound: TypeAlias = Incomplete +_circles: TypeAlias = Incomplete +_lines: TypeAlias = Incomplete +_hu: TypeAlias = Incomplete +_points2f: TypeAlias = Incomplete +_keypoints: TypeAlias = Incomplete +_mean: TypeAlias = Incomplete +_eigenvectors: TypeAlias = Incomplete +_eigenvalues: TypeAlias = Incomplete +_result: TypeAlias = Incomplete +_mtxR: TypeAlias = Incomplete +_mtxQ: TypeAlias = Incomplete +_Qx: TypeAlias = Incomplete +_Qy: TypeAlias = Incomplete +_Qz: TypeAlias = Incomplete +_jacobian: TypeAlias = Incomplete +_w: TypeAlias = Incomplete +_u: TypeAlias = Incomplete +_vt: TypeAlias = Incomplete +_approxCurve: TypeAlias = Incomplete +_img: TypeAlias = Incomplete +_dist: TypeAlias = Incomplete +_nidx: TypeAlias = Incomplete +_points: TypeAlias = Incomplete +_pyramid: TypeAlias = Incomplete +_covar: TypeAlias = Incomplete +_nextPts: TypeAlias = Incomplete +_status: TypeAlias = Incomplete +_err: TypeAlias = Incomplete +_cameraMatrix: TypeAlias = Incomplete +_distCoeffs: TypeAlias = Incomplete +_rvecs: TypeAlias = Incomplete +_tvecs: TypeAlias = Incomplete +_stdDeviationsIntrinsics: TypeAlias = Incomplete +_stdDeviationsExtrinsics: TypeAlias = Incomplete +_perViewErrors: TypeAlias = Incomplete +_newObjPoints: TypeAlias = Incomplete +_stdDeviationsObjPoints: TypeAlias = Incomplete +_R_cam2gripper: TypeAlias = Incomplete +_t_cam2gripper: TypeAlias = Incomplete +_fovx: TypeAlias = Incomplete +_fovy: TypeAlias = Incomplete +_focalLength: TypeAlias = Incomplete +_principalPoint: TypeAlias = Incomplete +_aspectRatio: TypeAlias = Incomplete +_magnitude: TypeAlias = Incomplete +_angle: TypeAlias = Incomplete +_pt1: TypeAlias = Incomplete +_pt2: TypeAlias = Incomplete +_pos: TypeAlias = Incomplete +_m: TypeAlias = Incomplete +_rvec3: TypeAlias = Incomplete +_tvec3: TypeAlias = Incomplete +_dr3dr1: TypeAlias = Incomplete +_dr3dt1: TypeAlias = Incomplete +_dr3dr2: TypeAlias = Incomplete +_dr3dt2: TypeAlias = Incomplete +_dt3dr1: TypeAlias = Incomplete +_dt3dt1: TypeAlias = Incomplete +_dt3dr2: TypeAlias = Incomplete +_dt3dt2: TypeAlias = Incomplete +_labels: TypeAlias = Incomplete +_stats: TypeAlias = Incomplete +_centroids: TypeAlias = Incomplete +_dstmap1: TypeAlias = Incomplete +_dstmap2: TypeAlias = Incomplete +_hull: TypeAlias = Incomplete +_convexityDefects: TypeAlias = Incomplete +_newPoints1: TypeAlias = Incomplete +_newPoints2: TypeAlias = Incomplete +_grayscale: TypeAlias = Incomplete +_color_boost: TypeAlias = Incomplete +_R1: TypeAlias = Incomplete +_R2: TypeAlias = Incomplete +_t: TypeAlias = Incomplete +_rotations: TypeAlias = Incomplete +_translations: TypeAlias = Incomplete +_normals: TypeAlias = Incomplete +_rotMatrix: TypeAlias = Incomplete +_transVect: TypeAlias = Incomplete +_rotMatrixX: TypeAlias = Incomplete +_rotMatrixY: TypeAlias = Incomplete +_rotMatrixZ: TypeAlias = Incomplete +_eulerAngles: TypeAlias = Incomplete +_outImage: TypeAlias = Incomplete +_outImg: TypeAlias = Incomplete +_inliers: TypeAlias = Incomplete +_out: TypeAlias = Incomplete +_sharpness: TypeAlias = Incomplete +_possibleSolutions: TypeAlias = Incomplete +_buf: TypeAlias = Incomplete +_meta: TypeAlias = Incomplete +_centers: TypeAlias = Incomplete +_contours: TypeAlias = Incomplete +_hierarchy: TypeAlias = Incomplete +_mask: TypeAlias = Incomplete +_idx: TypeAlias = Incomplete +_warpMatrix: TypeAlias = Incomplete +_line: TypeAlias = Incomplete +_rect: TypeAlias = Incomplete +_kx: TypeAlias = Incomplete +_ky: TypeAlias = Incomplete +_validPixROI: TypeAlias = Incomplete +_patch: TypeAlias = Incomplete +_baseLine: TypeAlias = Incomplete +_bgdModel: TypeAlias = Incomplete +_fgdModel: TypeAlias = Incomplete +_rectList: TypeAlias = Incomplete +_weights: TypeAlias = Incomplete +_mats: TypeAlias = Incomplete +_map1: TypeAlias = Incomplete +_map2: TypeAlias = Incomplete +_sum: TypeAlias = Incomplete +_sqsum: TypeAlias = Incomplete +_tilted: TypeAlias = Incomplete +_p12: TypeAlias = Incomplete +_iM: TypeAlias = Incomplete +_bestLabels: TypeAlias = Incomplete +_dABdA: TypeAlias = Incomplete +_dABdB: TypeAlias = Incomplete +_stddev: TypeAlias = Incomplete +_center: TypeAlias = Incomplete +_radius: TypeAlias = Incomplete +_triangle: TypeAlias = Incomplete +_c: TypeAlias = Incomplete +_a: TypeAlias = Incomplete +_dst1: TypeAlias = Incomplete +_dst2: TypeAlias = Incomplete +_response: TypeAlias = Incomplete +_x: TypeAlias = Incomplete +_y: TypeAlias = Incomplete +_imagePoints: TypeAlias = Incomplete +_R: TypeAlias = Incomplete +_R3: TypeAlias = Incomplete +_P1: TypeAlias = Incomplete +_P2: TypeAlias = Incomplete +_P3: TypeAlias = Incomplete +_Q: TypeAlias = Incomplete +_roi1: TypeAlias = Incomplete +_roi2: TypeAlias = Incomplete +_3dImage: TypeAlias = Incomplete +_intersectingRegion: TypeAlias = Incomplete +_blend: TypeAlias = Incomplete +_boundingBoxes: TypeAlias = Incomplete +_mtx: TypeAlias = Incomplete +_roots: TypeAlias = Incomplete +_z: TypeAlias = Incomplete +_rvec: TypeAlias = Incomplete +_tvec: TypeAlias = Incomplete +_reprojectionError: TypeAlias = Incomplete +_dx: TypeAlias = Incomplete +_dy: TypeAlias = Incomplete +_mv: TypeAlias = Incomplete +_cameraMatrix1: TypeAlias = Incomplete +_distCoeffs1: TypeAlias = Incomplete +_cameraMatrix2: TypeAlias = Incomplete +_distCoeffs2: TypeAlias = Incomplete +_T: TypeAlias = Incomplete +_E: TypeAlias = Incomplete +_F: TypeAlias = Incomplete +_validPixROI1: TypeAlias = Incomplete +_validPixROI2: TypeAlias = Incomplete +_H1: TypeAlias = Incomplete +_H2: TypeAlias = Incomplete +_points4D: TypeAlias = Incomplete +_disparity: TypeAlias = Incomplete +_triangulatedPoints: TypeAlias = Incomplete + +__version__: str + +ACCESS_FAST: int +ACCESS_MASK: int +ACCESS_READ: int +ACCESS_RW: int +ACCESS_WRITE: int +ADAPTIVE_THRESH_GAUSSIAN_C: int +ADAPTIVE_THRESH_MEAN_C: int +AGAST_FEATURE_DETECTOR_AGAST_5_8: int +AGAST_FEATURE_DETECTOR_AGAST_7_12D: int +AGAST_FEATURE_DETECTOR_AGAST_7_12S: int +AGAST_FEATURE_DETECTOR_NONMAX_SUPPRESSION: int +AGAST_FEATURE_DETECTOR_OAST_9_16: int +AGAST_FEATURE_DETECTOR_THRESHOLD: int +AKAZE_DESCRIPTOR_KAZE: int +AKAZE_DESCRIPTOR_KAZE_UPRIGHT: int +AKAZE_DESCRIPTOR_MLDB: int +AKAZE_DESCRIPTOR_MLDB_UPRIGHT: int +AgastFeatureDetector_AGAST_5_8: int +AgastFeatureDetector_AGAST_7_12d: int +AgastFeatureDetector_AGAST_7_12s: int +AgastFeatureDetector_NONMAX_SUPPRESSION: int +AgastFeatureDetector_OAST_9_16: int +AgastFeatureDetector_THRESHOLD: int +BORDER_CONSTANT: int +BORDER_DEFAULT: int +BORDER_ISOLATED: int +BORDER_REFLECT: int +BORDER_REFLECT101: int +BORDER_REFLECT_101: int +BORDER_REPLICATE: int +BORDER_TRANSPARENT: int +BORDER_WRAP: int +CALIB_CB_ACCURACY: int +CALIB_CB_ADAPTIVE_THRESH: int +CALIB_CB_ASYMMETRIC_GRID: int +CALIB_CB_CLUSTERING: int +CALIB_CB_EXHAUSTIVE: int +CALIB_CB_FAST_CHECK: int +CALIB_CB_FILTER_QUADS: int +CALIB_CB_LARGER: int +CALIB_CB_MARKER: int +CALIB_CB_NORMALIZE_IMAGE: int +CALIB_CB_SYMMETRIC_GRID: int +CALIB_FIX_ASPECT_RATIO: int +CALIB_FIX_FOCAL_LENGTH: int +CALIB_FIX_INTRINSIC: int +CALIB_FIX_K1: int +CALIB_FIX_K2: int +CALIB_FIX_K3: int +CALIB_FIX_K4: int +CALIB_FIX_K5: int +CALIB_FIX_K6: int +CALIB_FIX_PRINCIPAL_POINT: int +CALIB_FIX_S1_S2_S3_S4: int +CALIB_FIX_TANGENT_DIST: int +CALIB_FIX_TAUX_TAUY: int +CALIB_HAND_EYE_ANDREFF: int +CALIB_HAND_EYE_DANIILIDIS: int +CALIB_HAND_EYE_HORAUD: int +CALIB_HAND_EYE_PARK: int +CALIB_HAND_EYE_TSAI: int +CALIB_NINTRINSIC: int +CALIB_RATIONAL_MODEL: int +CALIB_ROBOT_WORLD_HAND_EYE_LI: int +CALIB_ROBOT_WORLD_HAND_EYE_SHAH: int +CALIB_SAME_FOCAL_LENGTH: int +CALIB_THIN_PRISM_MODEL: int +CALIB_TILTED_MODEL: int +CALIB_USE_EXTRINSIC_GUESS: int +CALIB_USE_INTRINSIC_GUESS: int +CALIB_USE_LU: int +CALIB_USE_QR: int +CALIB_ZERO_DISPARITY: int +CALIB_ZERO_TANGENT_DIST: int +CAP_ANDROID: int +CAP_ANY: int +CAP_ARAVIS: int +CAP_AVFOUNDATION: int +CAP_CMU1394: int +CAP_DC1394: int +CAP_DSHOW: int +CAP_FFMPEG: int +CAP_FIREWARE: int +CAP_FIREWIRE: int +CAP_GIGANETIX: int +CAP_GPHOTO2: int +CAP_GSTREAMER: int +CAP_IEEE1394: int +CAP_IMAGES: int +CAP_INTELPERC: int +CAP_INTELPERC_DEPTH_GENERATOR: int +CAP_INTELPERC_DEPTH_MAP: int +CAP_INTELPERC_GENERATORS_MASK: int +CAP_INTELPERC_IMAGE: int +CAP_INTELPERC_IMAGE_GENERATOR: int +CAP_INTELPERC_IR_GENERATOR: int +CAP_INTELPERC_IR_MAP: int +CAP_INTELPERC_UVDEPTH_MAP: int +CAP_INTEL_MFX: int +CAP_MSMF: int +CAP_OPENCV_MJPEG: int +CAP_OPENNI: int +CAP_OPENNI2: int +CAP_OPENNI2_ASTRA: int +CAP_OPENNI2_ASUS: int +CAP_OPENNI_ASUS: int +CAP_OPENNI_BGR_IMAGE: int +CAP_OPENNI_DEPTH_GENERATOR: int +CAP_OPENNI_DEPTH_GENERATOR_BASELINE: int +CAP_OPENNI_DEPTH_GENERATOR_FOCAL_LENGTH: int +CAP_OPENNI_DEPTH_GENERATOR_PRESENT: int +CAP_OPENNI_DEPTH_GENERATOR_REGISTRATION: int +CAP_OPENNI_DEPTH_GENERATOR_REGISTRATION_ON: int +CAP_OPENNI_DEPTH_MAP: int +CAP_OPENNI_DISPARITY_MAP: int +CAP_OPENNI_DISPARITY_MAP_32F: int +CAP_OPENNI_GENERATORS_MASK: int +CAP_OPENNI_GRAY_IMAGE: int +CAP_OPENNI_IMAGE_GENERATOR: int +CAP_OPENNI_IMAGE_GENERATOR_OUTPUT_MODE: int +CAP_OPENNI_IMAGE_GENERATOR_PRESENT: int +CAP_OPENNI_IR_GENERATOR: int +CAP_OPENNI_IR_GENERATOR_PRESENT: int +CAP_OPENNI_IR_IMAGE: int +CAP_OPENNI_POINT_CLOUD_MAP: int +CAP_OPENNI_QVGA_30HZ: int +CAP_OPENNI_QVGA_60HZ: int +CAP_OPENNI_SXGA_15HZ: int +CAP_OPENNI_SXGA_30HZ: int +CAP_OPENNI_VALID_DEPTH_MASK: int +CAP_OPENNI_VGA_30HZ: int +CAP_PROP_APERTURE: int +CAP_PROP_ARAVIS_AUTOTRIGGER: int +CAP_PROP_AUDIO_BASE_INDEX: int +CAP_PROP_AUDIO_DATA_DEPTH: int +CAP_PROP_AUDIO_POS: int +CAP_PROP_AUDIO_SAMPLES_PER_SECOND: int +CAP_PROP_AUDIO_SHIFT_NSEC: int +CAP_PROP_AUDIO_STREAM: int +CAP_PROP_AUDIO_SYNCHRONIZE: int +CAP_PROP_AUDIO_TOTAL_CHANNELS: int +CAP_PROP_AUDIO_TOTAL_STREAMS: int +CAP_PROP_AUTOFOCUS: int +CAP_PROP_AUTO_EXPOSURE: int +CAP_PROP_AUTO_WB: int +CAP_PROP_BACKEND: int +CAP_PROP_BACKLIGHT: int +CAP_PROP_BITRATE: int +CAP_PROP_BRIGHTNESS: int +CAP_PROP_BUFFERSIZE: int +CAP_PROP_CHANNEL: int +CAP_PROP_CODEC_EXTRADATA_INDEX: int +CAP_PROP_CODEC_PIXEL_FORMAT: int +CAP_PROP_CONTRAST: int +CAP_PROP_CONVERT_RGB: int +CAP_PROP_DC1394_MAX: int +CAP_PROP_DC1394_MODE_AUTO: int +CAP_PROP_DC1394_MODE_MANUAL: int +CAP_PROP_DC1394_MODE_ONE_PUSH_AUTO: int +CAP_PROP_DC1394_OFF: int +CAP_PROP_EXPOSURE: int +CAP_PROP_EXPOSUREPROGRAM: int +CAP_PROP_FOCUS: int +CAP_PROP_FORMAT: int +CAP_PROP_FOURCC: int +CAP_PROP_FPS: int +CAP_PROP_FRAME_COUNT: int +CAP_PROP_FRAME_HEIGHT: int +CAP_PROP_FRAME_WIDTH: int +CAP_PROP_GAIN: int +CAP_PROP_GAMMA: int +CAP_PROP_GIGA_FRAME_HEIGH_MAX: int +CAP_PROP_GIGA_FRAME_OFFSET_X: int +CAP_PROP_GIGA_FRAME_OFFSET_Y: int +CAP_PROP_GIGA_FRAME_SENS_HEIGH: int +CAP_PROP_GIGA_FRAME_SENS_WIDTH: int +CAP_PROP_GIGA_FRAME_WIDTH_MAX: int +CAP_PROP_GPHOTO2_COLLECT_MSGS: int +CAP_PROP_GPHOTO2_FLUSH_MSGS: int +CAP_PROP_GPHOTO2_PREVIEW: int +CAP_PROP_GPHOTO2_RELOAD_CONFIG: int +CAP_PROP_GPHOTO2_RELOAD_ON_CHANGE: int +CAP_PROP_GPHOTO2_WIDGET_ENUMERATE: int +CAP_PROP_GSTREAMER_QUEUE_LENGTH: int +CAP_PROP_GUID: int +CAP_PROP_HUE: int +CAP_PROP_HW_ACCELERATION: int +CAP_PROP_HW_ACCELERATION_USE_OPENCL: int +CAP_PROP_HW_DEVICE: int +CAP_PROP_IMAGES_BASE: int +CAP_PROP_IMAGES_LAST: int +CAP_PROP_INTELPERC_DEPTH_CONFIDENCE_THRESHOLD: int +CAP_PROP_INTELPERC_DEPTH_FOCAL_LENGTH_HORZ: int +CAP_PROP_INTELPERC_DEPTH_FOCAL_LENGTH_VERT: int +CAP_PROP_INTELPERC_DEPTH_LOW_CONFIDENCE_VALUE: int +CAP_PROP_INTELPERC_DEPTH_SATURATION_VALUE: int +CAP_PROP_INTELPERC_PROFILE_COUNT: int +CAP_PROP_INTELPERC_PROFILE_IDX: int +CAP_PROP_IOS_DEVICE_EXPOSURE: int +CAP_PROP_IOS_DEVICE_FLASH: int +CAP_PROP_IOS_DEVICE_FOCUS: int +CAP_PROP_IOS_DEVICE_TORCH: int +CAP_PROP_IOS_DEVICE_WHITEBALANCE: int +CAP_PROP_IRIS: int +CAP_PROP_ISO_SPEED: int +CAP_PROP_LRF_HAS_KEY_FRAME: int +CAP_PROP_MODE: int +CAP_PROP_MONOCHROME: int +CAP_PROP_OPENNI2_MIRROR: int +CAP_PROP_OPENNI2_SYNC: int +CAP_PROP_OPENNI_APPROX_FRAME_SYNC: int +CAP_PROP_OPENNI_BASELINE: int +CAP_PROP_OPENNI_CIRCLE_BUFFER: int +CAP_PROP_OPENNI_FOCAL_LENGTH: int +CAP_PROP_OPENNI_FRAME_MAX_DEPTH: int +CAP_PROP_OPENNI_GENERATOR_PRESENT: int +CAP_PROP_OPENNI_MAX_BUFFER_SIZE: int +CAP_PROP_OPENNI_MAX_TIME_DURATION: int +CAP_PROP_OPENNI_OUTPUT_MODE: int +CAP_PROP_OPENNI_REGISTRATION: int +CAP_PROP_OPENNI_REGISTRATION_ON: int +CAP_PROP_OPEN_TIMEOUT_MSEC: int +CAP_PROP_ORIENTATION_AUTO: int +CAP_PROP_ORIENTATION_META: int +CAP_PROP_PAN: int +CAP_PROP_POS_AVI_RATIO: int +CAP_PROP_POS_FRAMES: int +CAP_PROP_POS_MSEC: int +CAP_PROP_PVAPI_BINNINGX: int +CAP_PROP_PVAPI_BINNINGY: int +CAP_PROP_PVAPI_DECIMATIONHORIZONTAL: int +CAP_PROP_PVAPI_DECIMATIONVERTICAL: int +CAP_PROP_PVAPI_FRAMESTARTTRIGGERMODE: int +CAP_PROP_PVAPI_MULTICASTIP: int +CAP_PROP_PVAPI_PIXELFORMAT: int +CAP_PROP_READ_TIMEOUT_MSEC: int +CAP_PROP_RECTIFICATION: int +CAP_PROP_ROLL: int +CAP_PROP_SAR_DEN: int +CAP_PROP_SAR_NUM: int +CAP_PROP_SATURATION: int +CAP_PROP_SETTINGS: int +CAP_PROP_SHARPNESS: int +CAP_PROP_SPEED: int +CAP_PROP_STREAM_OPEN_TIME_USEC: int +CAP_PROP_TEMPERATURE: int +CAP_PROP_TILT: int +CAP_PROP_TRIGGER: int +CAP_PROP_TRIGGER_DELAY: int +CAP_PROP_VIDEO_STREAM: int +CAP_PROP_VIDEO_TOTAL_CHANNELS: int +CAP_PROP_VIEWFINDER: int +CAP_PROP_WB_TEMPERATURE: int +CAP_PROP_WHITE_BALANCE_BLUE_U: int +CAP_PROP_WHITE_BALANCE_RED_V: int +CAP_PROP_XI_ACQ_BUFFER_SIZE: int +CAP_PROP_XI_ACQ_BUFFER_SIZE_UNIT: int +CAP_PROP_XI_ACQ_FRAME_BURST_COUNT: int +CAP_PROP_XI_ACQ_TIMING_MODE: int +CAP_PROP_XI_ACQ_TRANSPORT_BUFFER_COMMIT: int +CAP_PROP_XI_ACQ_TRANSPORT_BUFFER_SIZE: int +CAP_PROP_XI_AEAG: int +CAP_PROP_XI_AEAG_LEVEL: int +CAP_PROP_XI_AEAG_ROI_HEIGHT: int +CAP_PROP_XI_AEAG_ROI_OFFSET_X: int +CAP_PROP_XI_AEAG_ROI_OFFSET_Y: int +CAP_PROP_XI_AEAG_ROI_WIDTH: int +CAP_PROP_XI_AE_MAX_LIMIT: int +CAP_PROP_XI_AG_MAX_LIMIT: int +CAP_PROP_XI_APPLY_CMS: int +CAP_PROP_XI_AUTO_BANDWIDTH_CALCULATION: int +CAP_PROP_XI_AUTO_WB: int +CAP_PROP_XI_AVAILABLE_BANDWIDTH: int +CAP_PROP_XI_BINNING_HORIZONTAL: int +CAP_PROP_XI_BINNING_PATTERN: int +CAP_PROP_XI_BINNING_SELECTOR: int +CAP_PROP_XI_BINNING_VERTICAL: int +CAP_PROP_XI_BPC: int +CAP_PROP_XI_BUFFERS_QUEUE_SIZE: int +CAP_PROP_XI_BUFFER_POLICY: int +CAP_PROP_XI_CC_MATRIX_00: int +CAP_PROP_XI_CC_MATRIX_01: int +CAP_PROP_XI_CC_MATRIX_02: int +CAP_PROP_XI_CC_MATRIX_03: int +CAP_PROP_XI_CC_MATRIX_10: int +CAP_PROP_XI_CC_MATRIX_11: int +CAP_PROP_XI_CC_MATRIX_12: int +CAP_PROP_XI_CC_MATRIX_13: int +CAP_PROP_XI_CC_MATRIX_20: int +CAP_PROP_XI_CC_MATRIX_21: int +CAP_PROP_XI_CC_MATRIX_22: int +CAP_PROP_XI_CC_MATRIX_23: int +CAP_PROP_XI_CC_MATRIX_30: int +CAP_PROP_XI_CC_MATRIX_31: int +CAP_PROP_XI_CC_MATRIX_32: int +CAP_PROP_XI_CC_MATRIX_33: int +CAP_PROP_XI_CHIP_TEMP: int +CAP_PROP_XI_CMS: int +CAP_PROP_XI_COLOR_FILTER_ARRAY: int +CAP_PROP_XI_COLUMN_FPN_CORRECTION: int +CAP_PROP_XI_COOLING: int +CAP_PROP_XI_COUNTER_SELECTOR: int +CAP_PROP_XI_COUNTER_VALUE: int +CAP_PROP_XI_DATA_FORMAT: int +CAP_PROP_XI_DEBOUNCE_EN: int +CAP_PROP_XI_DEBOUNCE_POL: int +CAP_PROP_XI_DEBOUNCE_T0: int +CAP_PROP_XI_DEBOUNCE_T1: int +CAP_PROP_XI_DEBUG_LEVEL: int +CAP_PROP_XI_DECIMATION_HORIZONTAL: int +CAP_PROP_XI_DECIMATION_PATTERN: int +CAP_PROP_XI_DECIMATION_SELECTOR: int +CAP_PROP_XI_DECIMATION_VERTICAL: int +CAP_PROP_XI_DEFAULT_CC_MATRIX: int +CAP_PROP_XI_DEVICE_MODEL_ID: int +CAP_PROP_XI_DEVICE_RESET: int +CAP_PROP_XI_DEVICE_SN: int +CAP_PROP_XI_DOWNSAMPLING: int +CAP_PROP_XI_DOWNSAMPLING_TYPE: int +CAP_PROP_XI_EXPOSURE: int +CAP_PROP_XI_EXPOSURE_BURST_COUNT: int +CAP_PROP_XI_EXP_PRIORITY: int +CAP_PROP_XI_FFS_ACCESS_KEY: int +CAP_PROP_XI_FFS_FILE_ID: int +CAP_PROP_XI_FFS_FILE_SIZE: int +CAP_PROP_XI_FRAMERATE: int +CAP_PROP_XI_FREE_FFS_SIZE: int +CAP_PROP_XI_GAIN: int +CAP_PROP_XI_GAIN_SELECTOR: int +CAP_PROP_XI_GAMMAC: int +CAP_PROP_XI_GAMMAY: int +CAP_PROP_XI_GPI_LEVEL: int +CAP_PROP_XI_GPI_MODE: int +CAP_PROP_XI_GPI_SELECTOR: int +CAP_PROP_XI_GPO_MODE: int +CAP_PROP_XI_GPO_SELECTOR: int +CAP_PROP_XI_HDR: int +CAP_PROP_XI_HDR_KNEEPOINT_COUNT: int +CAP_PROP_XI_HDR_T1: int +CAP_PROP_XI_HDR_T2: int +CAP_PROP_XI_HEIGHT: int +CAP_PROP_XI_HOUS_BACK_SIDE_TEMP: int +CAP_PROP_XI_HOUS_TEMP: int +CAP_PROP_XI_HW_REVISION: int +CAP_PROP_XI_IMAGE_BLACK_LEVEL: int +CAP_PROP_XI_IMAGE_DATA_BIT_DEPTH: int +CAP_PROP_XI_IMAGE_DATA_FORMAT: int +CAP_PROP_XI_IMAGE_DATA_FORMAT_RGB32_ALPHA: int +CAP_PROP_XI_IMAGE_IS_COLOR: int +CAP_PROP_XI_IMAGE_PAYLOAD_SIZE: int +CAP_PROP_XI_IS_COOLED: int +CAP_PROP_XI_IS_DEVICE_EXIST: int +CAP_PROP_XI_KNEEPOINT1: int +CAP_PROP_XI_KNEEPOINT2: int +CAP_PROP_XI_LED_MODE: int +CAP_PROP_XI_LED_SELECTOR: int +CAP_PROP_XI_LENS_APERTURE_VALUE: int +CAP_PROP_XI_LENS_FEATURE: int +CAP_PROP_XI_LENS_FEATURE_SELECTOR: int +CAP_PROP_XI_LENS_FOCAL_LENGTH: int +CAP_PROP_XI_LENS_FOCUS_DISTANCE: int +CAP_PROP_XI_LENS_FOCUS_MOVE: int +CAP_PROP_XI_LENS_FOCUS_MOVEMENT_VALUE: int +CAP_PROP_XI_LENS_MODE: int +CAP_PROP_XI_LIMIT_BANDWIDTH: int +CAP_PROP_XI_LUT_EN: int +CAP_PROP_XI_LUT_INDEX: int +CAP_PROP_XI_LUT_VALUE: int +CAP_PROP_XI_MANUAL_WB: int +CAP_PROP_XI_OFFSET_X: int +CAP_PROP_XI_OFFSET_Y: int +CAP_PROP_XI_OUTPUT_DATA_BIT_DEPTH: int +CAP_PROP_XI_OUTPUT_DATA_PACKING: int +CAP_PROP_XI_OUTPUT_DATA_PACKING_TYPE: int +CAP_PROP_XI_RECENT_FRAME: int +CAP_PROP_XI_REGION_MODE: int +CAP_PROP_XI_REGION_SELECTOR: int +CAP_PROP_XI_ROW_FPN_CORRECTION: int +CAP_PROP_XI_SENSOR_BOARD_TEMP: int +CAP_PROP_XI_SENSOR_CLOCK_FREQ_HZ: int +CAP_PROP_XI_SENSOR_CLOCK_FREQ_INDEX: int +CAP_PROP_XI_SENSOR_DATA_BIT_DEPTH: int +CAP_PROP_XI_SENSOR_FEATURE_SELECTOR: int +CAP_PROP_XI_SENSOR_FEATURE_VALUE: int +CAP_PROP_XI_SENSOR_MODE: int +CAP_PROP_XI_SENSOR_OUTPUT_CHANNEL_COUNT: int +CAP_PROP_XI_SENSOR_TAPS: int +CAP_PROP_XI_SHARPNESS: int +CAP_PROP_XI_SHUTTER_TYPE: int +CAP_PROP_XI_TARGET_TEMP: int +CAP_PROP_XI_TEST_PATTERN: int +CAP_PROP_XI_TEST_PATTERN_GENERATOR_SELECTOR: int +CAP_PROP_XI_TIMEOUT: int +CAP_PROP_XI_TRANSPORT_PIXEL_FORMAT: int +CAP_PROP_XI_TRG_DELAY: int +CAP_PROP_XI_TRG_SELECTOR: int +CAP_PROP_XI_TRG_SOFTWARE: int +CAP_PROP_XI_TRG_SOURCE: int +CAP_PROP_XI_TS_RST_MODE: int +CAP_PROP_XI_TS_RST_SOURCE: int +CAP_PROP_XI_USED_FFS_SIZE: int +CAP_PROP_XI_WB_KB: int +CAP_PROP_XI_WB_KG: int +CAP_PROP_XI_WB_KR: int +CAP_PROP_XI_WIDTH: int +CAP_PROP_ZOOM: int +CAP_PVAPI: int +CAP_PVAPI_DECIMATION_2OUTOF16: int +CAP_PVAPI_DECIMATION_2OUTOF4: int +CAP_PVAPI_DECIMATION_2OUTOF8: int +CAP_PVAPI_DECIMATION_OFF: int +CAP_PVAPI_FSTRIGMODE_FIXEDRATE: int +CAP_PVAPI_FSTRIGMODE_FREERUN: int +CAP_PVAPI_FSTRIGMODE_SOFTWARE: int +CAP_PVAPI_FSTRIGMODE_SYNCIN1: int +CAP_PVAPI_FSTRIGMODE_SYNCIN2: int +CAP_PVAPI_PIXELFORMAT_BAYER16: int +CAP_PVAPI_PIXELFORMAT_BAYER8: int +CAP_PVAPI_PIXELFORMAT_BGR24: int +CAP_PVAPI_PIXELFORMAT_BGRA32: int +CAP_PVAPI_PIXELFORMAT_MONO16: int +CAP_PVAPI_PIXELFORMAT_MONO8: int +CAP_PVAPI_PIXELFORMAT_RGB24: int +CAP_PVAPI_PIXELFORMAT_RGBA32: int +CAP_QT: int +CAP_REALSENSE: int +CAP_UEYE: int +CAP_UNICAP: int +CAP_V4L: int +CAP_V4L2: int +CAP_VFW: int +CAP_WINRT: int +CAP_XIAPI: int +CAP_XINE: int +CASCADE_DO_CANNY_PRUNING: int +CASCADE_DO_ROUGH_SEARCH: int +CASCADE_FIND_BIGGEST_OBJECT: int +CASCADE_SCALE_IMAGE: int +CCL_BBDT: int +CCL_BOLELLI: int +CCL_DEFAULT: int +CCL_GRANA: int +CCL_SAUF: int +CCL_SPAGHETTI: int +CCL_WU: int +CC_STAT_AREA: int +CC_STAT_HEIGHT: int +CC_STAT_LEFT: int +CC_STAT_MAX: int +CC_STAT_TOP: int +CC_STAT_WIDTH: int +CHAIN_APPROX_NONE: int +CHAIN_APPROX_SIMPLE: int +CHAIN_APPROX_TC89_KCOS: int +CHAIN_APPROX_TC89_L1: int +CIRCLES_GRID_FINDER_PARAMETERS_ASYMMETRIC_GRID: int +CIRCLES_GRID_FINDER_PARAMETERS_SYMMETRIC_GRID: int +CMP_EQ: int +CMP_GE: int +CMP_GT: int +CMP_LE: int +CMP_LT: int +CMP_NE: int +COLORMAP_AUTUMN: int +COLORMAP_BONE: int +COLORMAP_CIVIDIS: int +COLORMAP_COOL: int +COLORMAP_DEEPGREEN: int +COLORMAP_HOT: int +COLORMAP_HSV: int +COLORMAP_INFERNO: int +COLORMAP_JET: int +COLORMAP_MAGMA: int +COLORMAP_OCEAN: int +COLORMAP_PARULA: int +COLORMAP_PINK: int +COLORMAP_PLASMA: int +COLORMAP_RAINBOW: int +COLORMAP_SPRING: int +COLORMAP_SUMMER: int +COLORMAP_TURBO: int +COLORMAP_TWILIGHT: int +COLORMAP_TWILIGHT_SHIFTED: int +COLORMAP_VIRIDIS: int +COLORMAP_WINTER: int +COLOR_BAYER_BG2BGR: int +COLOR_BAYER_BG2BGRA: int +COLOR_BAYER_BG2BGR_EA: int +COLOR_BAYER_BG2BGR_VNG: int +COLOR_BAYER_BG2GRAY: int +COLOR_BAYER_BG2RGB: int +COLOR_BAYER_BG2RGBA: int +COLOR_BAYER_BG2RGB_EA: int +COLOR_BAYER_BG2RGB_VNG: int +COLOR_BAYER_BGGR2BGR: int +COLOR_BAYER_BGGR2BGRA: int +COLOR_BAYER_BGGR2BGR_EA: int +COLOR_BAYER_BGGR2BGR_VNG: int +COLOR_BAYER_BGGR2GRAY: int +COLOR_BAYER_BGGR2RGB: int +COLOR_BAYER_BGGR2RGBA: int +COLOR_BAYER_BGGR2RGB_EA: int +COLOR_BAYER_BGGR2RGB_VNG: int +COLOR_BAYER_GB2BGR: int +COLOR_BAYER_GB2BGRA: int +COLOR_BAYER_GB2BGR_EA: int +COLOR_BAYER_GB2BGR_VNG: int +COLOR_BAYER_GB2GRAY: int +COLOR_BAYER_GB2RGB: int +COLOR_BAYER_GB2RGBA: int +COLOR_BAYER_GB2RGB_EA: int +COLOR_BAYER_GB2RGB_VNG: int +COLOR_BAYER_GBRG2BGR: int +COLOR_BAYER_GBRG2BGRA: int +COLOR_BAYER_GBRG2BGR_EA: int +COLOR_BAYER_GBRG2BGR_VNG: int +COLOR_BAYER_GBRG2GRAY: int +COLOR_BAYER_GBRG2RGB: int +COLOR_BAYER_GBRG2RGBA: int +COLOR_BAYER_GBRG2RGB_EA: int +COLOR_BAYER_GBRG2RGB_VNG: int +COLOR_BAYER_GR2BGR: int +COLOR_BAYER_GR2BGRA: int +COLOR_BAYER_GR2BGR_EA: int +COLOR_BAYER_GR2BGR_VNG: int +COLOR_BAYER_GR2GRAY: int +COLOR_BAYER_GR2RGB: int +COLOR_BAYER_GR2RGBA: int +COLOR_BAYER_GR2RGB_EA: int +COLOR_BAYER_GR2RGB_VNG: int +COLOR_BAYER_GRBG2BGR: int +COLOR_BAYER_GRBG2BGRA: int +COLOR_BAYER_GRBG2BGR_EA: int +COLOR_BAYER_GRBG2BGR_VNG: int +COLOR_BAYER_GRBG2GRAY: int +COLOR_BAYER_GRBG2RGB: int +COLOR_BAYER_GRBG2RGBA: int +COLOR_BAYER_GRBG2RGB_EA: int +COLOR_BAYER_GRBG2RGB_VNG: int +COLOR_BAYER_RG2BGR: int +COLOR_BAYER_RG2BGRA: int +COLOR_BAYER_RG2BGR_EA: int +COLOR_BAYER_RG2BGR_VNG: int +COLOR_BAYER_RG2GRAY: int +COLOR_BAYER_RG2RGB: int +COLOR_BAYER_RG2RGBA: int +COLOR_BAYER_RG2RGB_EA: int +COLOR_BAYER_RG2RGB_VNG: int +COLOR_BAYER_RGGB2BGR: int +COLOR_BAYER_RGGB2BGRA: int +COLOR_BAYER_RGGB2BGR_EA: int +COLOR_BAYER_RGGB2BGR_VNG: int +COLOR_BAYER_RGGB2GRAY: int +COLOR_BAYER_RGGB2RGB: int +COLOR_BAYER_RGGB2RGBA: int +COLOR_BAYER_RGGB2RGB_EA: int +COLOR_BAYER_RGGB2RGB_VNG: int +COLOR_BGR2BGR555: int +COLOR_BGR2BGR565: int +COLOR_BGR2BGRA: int +COLOR_BGR2GRAY: int +COLOR_BGR2HLS: int +COLOR_BGR2HLS_FULL: int +COLOR_BGR2HSV: int +COLOR_BGR2HSV_FULL: int +COLOR_BGR2LAB: int +COLOR_BGR2LUV: int +COLOR_BGR2Lab: int +COLOR_BGR2Luv: int +COLOR_BGR2RGB: int +COLOR_BGR2RGBA: int +COLOR_BGR2XYZ: int +COLOR_BGR2YCR_CB: int +COLOR_BGR2YCrCb: int +COLOR_BGR2YUV: int +COLOR_BGR2YUV_I420: int +COLOR_BGR2YUV_IYUV: int +COLOR_BGR2YUV_YV12: int +COLOR_BGR5552BGR: int +COLOR_BGR5552BGRA: int +COLOR_BGR5552GRAY: int +COLOR_BGR5552RGB: int +COLOR_BGR5552RGBA: int +COLOR_BGR5652BGR: int +COLOR_BGR5652BGRA: int +COLOR_BGR5652GRAY: int +COLOR_BGR5652RGB: int +COLOR_BGR5652RGBA: int +COLOR_BGRA2BGR: int +COLOR_BGRA2BGR555: int +COLOR_BGRA2BGR565: int +COLOR_BGRA2GRAY: int +COLOR_BGRA2RGB: int +COLOR_BGRA2RGBA: int +COLOR_BGRA2YUV_I420: int +COLOR_BGRA2YUV_IYUV: int +COLOR_BGRA2YUV_YV12: int +COLOR_BayerBG2BGR: int +COLOR_BayerBG2BGRA: int +COLOR_BayerBG2BGR_EA: int +COLOR_BayerBG2BGR_VNG: int +COLOR_BayerBG2GRAY: int +COLOR_BayerBG2RGB: int +COLOR_BayerBG2RGBA: int +COLOR_BayerBG2RGB_EA: int +COLOR_BayerBG2RGB_VNG: int +COLOR_BayerBGGR2BGR: int +COLOR_BayerBGGR2BGRA: int +COLOR_BayerBGGR2BGR_EA: int +COLOR_BayerBGGR2BGR_VNG: int +COLOR_BayerBGGR2GRAY: int +COLOR_BayerBGGR2RGB: int +COLOR_BayerBGGR2RGBA: int +COLOR_BayerBGGR2RGB_EA: int +COLOR_BayerBGGR2RGB_VNG: int +COLOR_BayerGB2BGR: int +COLOR_BayerGB2BGRA: int +COLOR_BayerGB2BGR_EA: int +COLOR_BayerGB2BGR_VNG: int +COLOR_BayerGB2GRAY: int +COLOR_BayerGB2RGB: int +COLOR_BayerGB2RGBA: int +COLOR_BayerGB2RGB_EA: int +COLOR_BayerGB2RGB_VNG: int +COLOR_BayerGBRG2BGR: int +COLOR_BayerGBRG2BGRA: int +COLOR_BayerGBRG2BGR_EA: int +COLOR_BayerGBRG2BGR_VNG: int +COLOR_BayerGBRG2GRAY: int +COLOR_BayerGBRG2RGB: int +COLOR_BayerGBRG2RGBA: int +COLOR_BayerGBRG2RGB_EA: int +COLOR_BayerGBRG2RGB_VNG: int +COLOR_BayerGR2BGR: int +COLOR_BayerGR2BGRA: int +COLOR_BayerGR2BGR_EA: int +COLOR_BayerGR2BGR_VNG: int +COLOR_BayerGR2GRAY: int +COLOR_BayerGR2RGB: int +COLOR_BayerGR2RGBA: int +COLOR_BayerGR2RGB_EA: int +COLOR_BayerGR2RGB_VNG: int +COLOR_BayerGRBG2BGR: int +COLOR_BayerGRBG2BGRA: int +COLOR_BayerGRBG2BGR_EA: int +COLOR_BayerGRBG2BGR_VNG: int +COLOR_BayerGRBG2GRAY: int +COLOR_BayerGRBG2RGB: int +COLOR_BayerGRBG2RGBA: int +COLOR_BayerGRBG2RGB_EA: int +COLOR_BayerGRBG2RGB_VNG: int +COLOR_BayerRG2BGR: int +COLOR_BayerRG2BGRA: int +COLOR_BayerRG2BGR_EA: int +COLOR_BayerRG2BGR_VNG: int +COLOR_BayerRG2GRAY: int +COLOR_BayerRG2RGB: int +COLOR_BayerRG2RGBA: int +COLOR_BayerRG2RGB_EA: int +COLOR_BayerRG2RGB_VNG: int +COLOR_BayerRGGB2BGR: int +COLOR_BayerRGGB2BGRA: int +COLOR_BayerRGGB2BGR_EA: int +COLOR_BayerRGGB2BGR_VNG: int +COLOR_BayerRGGB2GRAY: int +COLOR_BayerRGGB2RGB: int +COLOR_BayerRGGB2RGBA: int +COLOR_BayerRGGB2RGB_EA: int +COLOR_BayerRGGB2RGB_VNG: int +COLOR_COLORCVT_MAX: int +COLOR_GRAY2BGR: int +COLOR_GRAY2BGR555: int +COLOR_GRAY2BGR565: int +COLOR_GRAY2BGRA: int +COLOR_GRAY2RGB: int +COLOR_GRAY2RGBA: int +COLOR_HLS2BGR: int +COLOR_HLS2BGR_FULL: int +COLOR_HLS2RGB: int +COLOR_HLS2RGB_FULL: int +COLOR_HSV2BGR: int +COLOR_HSV2BGR_FULL: int +COLOR_HSV2RGB: int +COLOR_HSV2RGB_FULL: int +COLOR_LAB2BGR: int +COLOR_LAB2LBGR: int +COLOR_LAB2LRGB: int +COLOR_LAB2RGB: int +COLOR_LBGR2LAB: int +COLOR_LBGR2LUV: int +COLOR_LBGR2Lab: int +COLOR_LBGR2Luv: int +COLOR_LRGB2LAB: int +COLOR_LRGB2LUV: int +COLOR_LRGB2Lab: int +COLOR_LRGB2Luv: int +COLOR_LUV2BGR: int +COLOR_LUV2LBGR: int +COLOR_LUV2LRGB: int +COLOR_LUV2RGB: int +COLOR_Lab2BGR: int +COLOR_Lab2LBGR: int +COLOR_Lab2LRGB: int +COLOR_Lab2RGB: int +COLOR_Luv2BGR: int +COLOR_Luv2LBGR: int +COLOR_Luv2LRGB: int +COLOR_Luv2RGB: int +COLOR_M_RGBA2RGBA: int +COLOR_RGB2BGR: int +COLOR_RGB2BGR555: int +COLOR_RGB2BGR565: int +COLOR_RGB2BGRA: int +COLOR_RGB2GRAY: int +COLOR_RGB2HLS: int +COLOR_RGB2HLS_FULL: int +COLOR_RGB2HSV: int +COLOR_RGB2HSV_FULL: int +COLOR_RGB2LAB: int +COLOR_RGB2LUV: int +COLOR_RGB2Lab: int +COLOR_RGB2Luv: int +COLOR_RGB2RGBA: int +COLOR_RGB2XYZ: int +COLOR_RGB2YCR_CB: int +COLOR_RGB2YCrCb: int +COLOR_RGB2YUV: int +COLOR_RGB2YUV_I420: int +COLOR_RGB2YUV_IYUV: int +COLOR_RGB2YUV_YV12: int +COLOR_RGBA2BGR: int +COLOR_RGBA2BGR555: int +COLOR_RGBA2BGR565: int +COLOR_RGBA2BGRA: int +COLOR_RGBA2GRAY: int +COLOR_RGBA2M_RGBA: int +COLOR_RGBA2RGB: int +COLOR_RGBA2YUV_I420: int +COLOR_RGBA2YUV_IYUV: int +COLOR_RGBA2YUV_YV12: int +COLOR_RGBA2mRGBA: int +COLOR_XYZ2BGR: int +COLOR_XYZ2RGB: int +COLOR_YCR_CB2BGR: int +COLOR_YCR_CB2RGB: int +COLOR_YCrCb2BGR: int +COLOR_YCrCb2RGB: int +COLOR_YUV2BGR: int +COLOR_YUV2BGRA_I420: int +COLOR_YUV2BGRA_IYUV: int +COLOR_YUV2BGRA_NV12: int +COLOR_YUV2BGRA_NV21: int +COLOR_YUV2BGRA_UYNV: int +COLOR_YUV2BGRA_UYVY: int +COLOR_YUV2BGRA_Y422: int +COLOR_YUV2BGRA_YUNV: int +COLOR_YUV2BGRA_YUY2: int +COLOR_YUV2BGRA_YUYV: int +COLOR_YUV2BGRA_YV12: int +COLOR_YUV2BGRA_YVYU: int +COLOR_YUV2BGR_I420: int +COLOR_YUV2BGR_IYUV: int +COLOR_YUV2BGR_NV12: int +COLOR_YUV2BGR_NV21: int +COLOR_YUV2BGR_UYNV: int +COLOR_YUV2BGR_UYVY: int +COLOR_YUV2BGR_Y422: int +COLOR_YUV2BGR_YUNV: int +COLOR_YUV2BGR_YUY2: int +COLOR_YUV2BGR_YUYV: int +COLOR_YUV2BGR_YV12: int +COLOR_YUV2BGR_YVYU: int +COLOR_YUV2GRAY_420: int +COLOR_YUV2GRAY_I420: int +COLOR_YUV2GRAY_IYUV: int +COLOR_YUV2GRAY_NV12: int +COLOR_YUV2GRAY_NV21: int +COLOR_YUV2GRAY_UYNV: int +COLOR_YUV2GRAY_UYVY: int +COLOR_YUV2GRAY_Y422: int +COLOR_YUV2GRAY_YUNV: int +COLOR_YUV2GRAY_YUY2: int +COLOR_YUV2GRAY_YUYV: int +COLOR_YUV2GRAY_YV12: int +COLOR_YUV2GRAY_YVYU: int +COLOR_YUV2RGB: int +COLOR_YUV2RGBA_I420: int +COLOR_YUV2RGBA_IYUV: int +COLOR_YUV2RGBA_NV12: int +COLOR_YUV2RGBA_NV21: int +COLOR_YUV2RGBA_UYNV: int +COLOR_YUV2RGBA_UYVY: int +COLOR_YUV2RGBA_Y422: int +COLOR_YUV2RGBA_YUNV: int +COLOR_YUV2RGBA_YUY2: int +COLOR_YUV2RGBA_YUYV: int +COLOR_YUV2RGBA_YV12: int +COLOR_YUV2RGBA_YVYU: int +COLOR_YUV2RGB_I420: int +COLOR_YUV2RGB_IYUV: int +COLOR_YUV2RGB_NV12: int +COLOR_YUV2RGB_NV21: int +COLOR_YUV2RGB_UYNV: int +COLOR_YUV2RGB_UYVY: int +COLOR_YUV2RGB_Y422: int +COLOR_YUV2RGB_YUNV: int +COLOR_YUV2RGB_YUY2: int +COLOR_YUV2RGB_YUYV: int +COLOR_YUV2RGB_YV12: int +COLOR_YUV2RGB_YVYU: int +COLOR_YUV420P2BGR: int +COLOR_YUV420P2BGRA: int +COLOR_YUV420P2GRAY: int +COLOR_YUV420P2RGB: int +COLOR_YUV420P2RGBA: int +COLOR_YUV420SP2BGR: int +COLOR_YUV420SP2BGRA: int +COLOR_YUV420SP2GRAY: int +COLOR_YUV420SP2RGB: int +COLOR_YUV420SP2RGBA: int +COLOR_YUV420p2BGR: int +COLOR_YUV420p2BGRA: int +COLOR_YUV420p2GRAY: int +COLOR_YUV420p2RGB: int +COLOR_YUV420p2RGBA: int +COLOR_YUV420sp2BGR: int +COLOR_YUV420sp2BGRA: int +COLOR_YUV420sp2GRAY: int +COLOR_YUV420sp2RGB: int +COLOR_YUV420sp2RGBA: int +COLOR_mRGBA2RGBA: int +CONTOURS_MATCH_I1: int +CONTOURS_MATCH_I2: int +CONTOURS_MATCH_I3: int +COVAR_COLS: int +COVAR_NORMAL: int +COVAR_ROWS: int +COVAR_SCALE: int +COVAR_SCRAMBLED: int +COVAR_USE_AVG: int +CV_16S: int +CV_16SC1: int +CV_16SC2: int +CV_16SC3: int +CV_16SC4: int +CV_16U: int +CV_16UC1: int +CV_16UC2: int +CV_16UC3: int +CV_16UC4: int +CV_32F: int +CV_32FC1: int +CV_32FC2: int +CV_32FC3: int +CV_32FC4: int +CV_32S: int +CV_32SC1: int +CV_32SC2: int +CV_32SC3: int +CV_32SC4: int +CV_64F: int +CV_64FC1: int +CV_64FC2: int +CV_64FC3: int +CV_64FC4: int +CV_8S: int +CV_8SC1: int +CV_8SC2: int +CV_8SC3: int +CV_8SC4: int +CV_8U: int +CV_8UC1: int +CV_8UC2: int +CV_8UC3: int +CV_8UC4: int +CirclesGridFinderParameters_ASYMMETRIC_GRID: int +CirclesGridFinderParameters_SYMMETRIC_GRID: int +DCT_INVERSE: int +DCT_ROWS: int +DECOMP_CHOLESKY: int +DECOMP_EIG: int +DECOMP_LU: int +DECOMP_NORMAL: int +DECOMP_QR: int +DECOMP_SVD: int +DESCRIPTOR_MATCHER_BRUTEFORCE: int +DESCRIPTOR_MATCHER_BRUTEFORCE_HAMMING: int +DESCRIPTOR_MATCHER_BRUTEFORCE_HAMMINGLUT: int +DESCRIPTOR_MATCHER_BRUTEFORCE_L1: int +DESCRIPTOR_MATCHER_BRUTEFORCE_SL2: int +DESCRIPTOR_MATCHER_FLANNBASED: int +DFT_COMPLEX_INPUT: int +DFT_COMPLEX_OUTPUT: int +DFT_INVERSE: int +DFT_REAL_OUTPUT: int +DFT_ROWS: int +DFT_SCALE: int +DISOPTICAL_FLOW_PRESET_FAST: int +DISOPTICAL_FLOW_PRESET_MEDIUM: int +DISOPTICAL_FLOW_PRESET_ULTRAFAST: int +DISOpticalFlow_PRESET_FAST: int +DISOpticalFlow_PRESET_MEDIUM: int +DISOpticalFlow_PRESET_ULTRAFAST: int +DIST_C: int +DIST_FAIR: int +DIST_HUBER: int +DIST_L1: int +DIST_L12: int +DIST_L2: int +DIST_LABEL_CCOMP: int +DIST_LABEL_PIXEL: int +DIST_MASK_3: int +DIST_MASK_5: int +DIST_MASK_PRECISE: int +DIST_USER: int +DIST_WELSCH: int +DRAW_MATCHES_FLAGS_DEFAULT: int +DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG: int +DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS: int +DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS: int +DescriptorMatcher_BRUTEFORCE: int +DescriptorMatcher_BRUTEFORCE_HAMMING: int +DescriptorMatcher_BRUTEFORCE_HAMMINGLUT: int +DescriptorMatcher_BRUTEFORCE_L1: int +DescriptorMatcher_BRUTEFORCE_SL2: int +DescriptorMatcher_FLANNBASED: int +DrawMatchesFlags_DEFAULT: int +DrawMatchesFlags_DRAW_OVER_OUTIMG: int +DrawMatchesFlags_DRAW_RICH_KEYPOINTS: int +DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS: int +EVENT_FLAG_ALTKEY: int +EVENT_FLAG_CTRLKEY: int +EVENT_FLAG_LBUTTON: int +EVENT_FLAG_MBUTTON: int +EVENT_FLAG_RBUTTON: int +EVENT_FLAG_SHIFTKEY: int +EVENT_LBUTTONDBLCLK: int +EVENT_LBUTTONDOWN: int +EVENT_LBUTTONUP: int +EVENT_MBUTTONDBLCLK: int +EVENT_MBUTTONDOWN: int +EVENT_MBUTTONUP: int +EVENT_MOUSEHWHEEL: int +EVENT_MOUSEMOVE: int +EVENT_MOUSEWHEEL: int +EVENT_RBUTTONDBLCLK: int +EVENT_RBUTTONDOWN: int +EVENT_RBUTTONUP: int +FACE_RECOGNIZER_SF_FR_COSINE: int +FACE_RECOGNIZER_SF_FR_NORM_L2: int +FAST_FEATURE_DETECTOR_FAST_N: int +FAST_FEATURE_DETECTOR_NONMAX_SUPPRESSION: int +FAST_FEATURE_DETECTOR_THRESHOLD: int +FAST_FEATURE_DETECTOR_TYPE_5_8: int +FAST_FEATURE_DETECTOR_TYPE_7_12: int +FAST_FEATURE_DETECTOR_TYPE_9_16: int +FILE_NODE_EMPTY: int +FILE_NODE_FLOAT: int +FILE_NODE_FLOW: int +FILE_NODE_INT: int +FILE_NODE_MAP: int +FILE_NODE_NAMED: int +FILE_NODE_NONE: int +FILE_NODE_REAL: int +FILE_NODE_SEQ: int +FILE_NODE_STR: int +FILE_NODE_STRING: int +FILE_NODE_TYPE_MASK: int +FILE_NODE_UNIFORM: int +FILE_STORAGE_APPEND: int +FILE_STORAGE_BASE64: int +FILE_STORAGE_FORMAT_AUTO: int +FILE_STORAGE_FORMAT_JSON: int +FILE_STORAGE_FORMAT_MASK: int +FILE_STORAGE_FORMAT_XML: int +FILE_STORAGE_FORMAT_YAML: int +FILE_STORAGE_INSIDE_MAP: int +FILE_STORAGE_MEMORY: int +FILE_STORAGE_NAME_EXPECTED: int +FILE_STORAGE_READ: int +FILE_STORAGE_UNDEFINED: int +FILE_STORAGE_VALUE_EXPECTED: int +FILE_STORAGE_WRITE: int +FILE_STORAGE_WRITE_BASE64: int +FILLED: int +FILTER_SCHARR: int +FLOODFILL_FIXED_RANGE: int +FLOODFILL_MASK_ONLY: int +FM_7POINT: int +FM_8POINT: int +FM_LMEDS: int +FM_RANSAC: int +FONT_HERSHEY_COMPLEX: int +FONT_HERSHEY_COMPLEX_SMALL: int +FONT_HERSHEY_DUPLEX: int +FONT_HERSHEY_PLAIN: int +FONT_HERSHEY_SCRIPT_COMPLEX: int +FONT_HERSHEY_SCRIPT_SIMPLEX: int +FONT_HERSHEY_SIMPLEX: int +FONT_HERSHEY_TRIPLEX: int +FONT_ITALIC: int +FORMATTER_FMT_C: int +FORMATTER_FMT_CSV: int +FORMATTER_FMT_DEFAULT: int +FORMATTER_FMT_MATLAB: int +FORMATTER_FMT_NUMPY: int +FORMATTER_FMT_PYTHON: int +FaceRecognizerSF_FR_COSINE: int +FaceRecognizerSF_FR_NORM_L2: int +FastFeatureDetector_FAST_N: int +FastFeatureDetector_NONMAX_SUPPRESSION: int +FastFeatureDetector_THRESHOLD: int +FastFeatureDetector_TYPE_5_8: int +FastFeatureDetector_TYPE_7_12: int +FastFeatureDetector_TYPE_9_16: int +FileNode_EMPTY: int +FileNode_FLOAT: int +FileNode_FLOW: int +FileNode_INT: int +FileNode_MAP: int +FileNode_NAMED: int +FileNode_NONE: int +FileNode_REAL: int +FileNode_SEQ: int +FileNode_STR: int +FileNode_STRING: int +FileNode_TYPE_MASK: int +FileNode_UNIFORM: int +FileStorage_APPEND: int +FileStorage_BASE64: int +FileStorage_FORMAT_AUTO: int +FileStorage_FORMAT_JSON: int +FileStorage_FORMAT_MASK: int +FileStorage_FORMAT_XML: int +FileStorage_FORMAT_YAML: int +FileStorage_INSIDE_MAP: int +FileStorage_MEMORY: int +FileStorage_NAME_EXPECTED: int +FileStorage_READ: int +FileStorage_UNDEFINED: int +FileStorage_VALUE_EXPECTED: int +FileStorage_WRITE: int +FileStorage_WRITE_BASE64: int +Formatter_FMT_C: int +Formatter_FMT_CSV: int +Formatter_FMT_DEFAULT: int +Formatter_FMT_MATLAB: int +Formatter_FMT_NUMPY: int +Formatter_FMT_PYTHON: int +GC_BGD: int +GC_EVAL: int +GC_EVAL_FREEZE_MODEL: int +GC_FGD: int +GC_INIT_WITH_MASK: int +GC_INIT_WITH_RECT: int +GC_PR_BGD: int +GC_PR_FGD: int +GEMM_1_T: int +GEMM_2_T: int +GEMM_3_T: int +GFLUID_KERNEL_KIND_FILTER: int +GFLUID_KERNEL_KIND_RESIZE: int +GFLUID_KERNEL_KIND_YUV420TO_RGB: int +GFluidKernel_Kind_Filter: int +GFluidKernel_Kind_Resize: int +GFluidKernel_Kind_YUV420toRGB: int +GSHAPE_GARRAY: int +GSHAPE_GFRAME: int +GSHAPE_GMAT: int +GSHAPE_GOPAQUE: int +GSHAPE_GSCALAR: int +GShape_GARRAY: int +GShape_GFRAME: int +GShape_GMAT: int +GShape_GOPAQUE: int +GShape_GSCALAR: int +HISTCMP_BHATTACHARYYA: int +HISTCMP_CHISQR: int +HISTCMP_CHISQR_ALT: int +HISTCMP_CORREL: int +HISTCMP_HELLINGER: int +HISTCMP_INTERSECT: int +HISTCMP_KL_DIV: int +HOGDESCRIPTOR_DEFAULT_NLEVELS: int +HOGDESCRIPTOR_DESCR_FORMAT_COL_BY_COL: int +HOGDESCRIPTOR_DESCR_FORMAT_ROW_BY_ROW: int +HOGDESCRIPTOR_L2HYS: int +HOGDescriptor_DEFAULT_NLEVELS: int +HOGDescriptor_DESCR_FORMAT_COL_BY_COL: int +HOGDescriptor_DESCR_FORMAT_ROW_BY_ROW: int +HOGDescriptor_L2Hys: int +HOUGH_GRADIENT: int +HOUGH_GRADIENT_ALT: int +HOUGH_MULTI_SCALE: int +HOUGH_PROBABILISTIC: int +HOUGH_STANDARD: int +IMREAD_ANYCOLOR: int +IMREAD_ANYDEPTH: int +IMREAD_COLOR: int +IMREAD_GRAYSCALE: int +IMREAD_IGNORE_ORIENTATION: int +IMREAD_LOAD_GDAL: int +IMREAD_REDUCED_COLOR_2: int +IMREAD_REDUCED_COLOR_4: int +IMREAD_REDUCED_COLOR_8: int +IMREAD_REDUCED_GRAYSCALE_2: int +IMREAD_REDUCED_GRAYSCALE_4: int +IMREAD_REDUCED_GRAYSCALE_8: int +IMREAD_UNCHANGED: int +IMWRITE_EXR_COMPRESSION: int +IMWRITE_EXR_COMPRESSION_B44: int +IMWRITE_EXR_COMPRESSION_B44A: int +IMWRITE_EXR_COMPRESSION_DWAA: int +IMWRITE_EXR_COMPRESSION_DWAB: int +IMWRITE_EXR_COMPRESSION_NO: int +IMWRITE_EXR_COMPRESSION_PIZ: int +IMWRITE_EXR_COMPRESSION_PXR24: int +IMWRITE_EXR_COMPRESSION_RLE: int +IMWRITE_EXR_COMPRESSION_ZIP: int +IMWRITE_EXR_COMPRESSION_ZIPS: int +IMWRITE_EXR_TYPE: int +IMWRITE_EXR_TYPE_FLOAT: int +IMWRITE_EXR_TYPE_HALF: int +IMWRITE_JPEG2000_COMPRESSION_X1000: int +IMWRITE_JPEG_CHROMA_QUALITY: int +IMWRITE_JPEG_LUMA_QUALITY: int +IMWRITE_JPEG_OPTIMIZE: int +IMWRITE_JPEG_PROGRESSIVE: int +IMWRITE_JPEG_QUALITY: int +IMWRITE_JPEG_RST_INTERVAL: int +IMWRITE_PAM_FORMAT_BLACKANDWHITE: int +IMWRITE_PAM_FORMAT_GRAYSCALE: int +IMWRITE_PAM_FORMAT_GRAYSCALE_ALPHA: int +IMWRITE_PAM_FORMAT_NULL: int +IMWRITE_PAM_FORMAT_RGB: int +IMWRITE_PAM_FORMAT_RGB_ALPHA: int +IMWRITE_PAM_TUPLETYPE: int +IMWRITE_PNG_BILEVEL: int +IMWRITE_PNG_COMPRESSION: int +IMWRITE_PNG_STRATEGY: int +IMWRITE_PNG_STRATEGY_DEFAULT: int +IMWRITE_PNG_STRATEGY_FILTERED: int +IMWRITE_PNG_STRATEGY_FIXED: int +IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY: int +IMWRITE_PNG_STRATEGY_RLE: int +IMWRITE_PXM_BINARY: int +IMWRITE_TIFF_COMPRESSION: int +IMWRITE_TIFF_RESUNIT: int +IMWRITE_TIFF_XDPI: int +IMWRITE_TIFF_YDPI: int +IMWRITE_WEBP_QUALITY: int +INPAINT_NS: int +INPAINT_TELEA: int +INTERSECT_FULL: int +INTERSECT_NONE: int +INTERSECT_PARTIAL: int +INTER_AREA: int +INTER_BITS: int +INTER_BITS2: int +INTER_CUBIC: int +INTER_LANCZOS4: int +INTER_LINEAR: int +INTER_LINEAR_EXACT: int +INTER_MAX: int +INTER_NEAREST: int +INTER_NEAREST_EXACT: int +INTER_TAB_SIZE: int +INTER_TAB_SIZE2: int +KAZE_DIFF_CHARBONNIER: int +KAZE_DIFF_PM_G1: int +KAZE_DIFF_PM_G2: int +KAZE_DIFF_WEICKERT: int +KMEANS_PP_CENTERS: int +KMEANS_RANDOM_CENTERS: int +KMEANS_USE_INITIAL_LABELS: int +LDR_SIZE: int +LINE_4: int +LINE_8: int +LINE_AA: int +LMEDS: int +LOCAL_OPTIM_GC: int +LOCAL_OPTIM_INNER_AND_ITER_LO: int +LOCAL_OPTIM_INNER_LO: int +LOCAL_OPTIM_NULL: int +LOCAL_OPTIM_SIGMA: int +LSD_REFINE_ADV: int +LSD_REFINE_NONE: int +LSD_REFINE_STD: int +MARKER_CROSS: int +MARKER_DIAMOND: int +MARKER_SQUARE: int +MARKER_STAR: int +MARKER_TILTED_CROSS: int +MARKER_TRIANGLE_DOWN: int +MARKER_TRIANGLE_UP: int +MAT_AUTO_STEP: int +MAT_CONTINUOUS_FLAG: int +MAT_DEPTH_MASK: int +MAT_MAGIC_MASK: int +MAT_MAGIC_VAL: int +MAT_SUBMATRIX_FLAG: int +MAT_TYPE_MASK: int +MEDIA_FORMAT_BGR: int +MEDIA_FORMAT_NV12: int +MEDIA_FRAME_ACCESS_R: int +MEDIA_FRAME_ACCESS_W: int +MIXED_CLONE: int +MONOCHROME_TRANSFER: int +MORPH_BLACKHAT: int +MORPH_CLOSE: int +MORPH_CROSS: int +MORPH_DILATE: int +MORPH_ELLIPSE: int +MORPH_ERODE: int +MORPH_GRADIENT: int +MORPH_HITMISS: int +MORPH_OPEN: int +MORPH_RECT: int +MORPH_TOPHAT: int +MOTION_AFFINE: int +MOTION_EUCLIDEAN: int +MOTION_HOMOGRAPHY: int +MOTION_TRANSLATION: int +Mat_AUTO_STEP: int +Mat_CONTINUOUS_FLAG: int +Mat_DEPTH_MASK: int +Mat_MAGIC_MASK: int +Mat_MAGIC_VAL: int +Mat_SUBMATRIX_FLAG: int +Mat_TYPE_MASK: int +MediaFormat_BGR: int +MediaFormat_NV12: int +MediaFrame_Access_R: int +MediaFrame_Access_W: int +NEIGH_FLANN_KNN: int +NEIGH_FLANN_RADIUS: int +NEIGH_GRID: int +NORMAL_CLONE: int +NORMCONV_FILTER: int +NORM_HAMMING: int +NORM_HAMMING2: int +NORM_INF: int +NORM_L1: int +NORM_L2: int +NORM_L2SQR: int +NORM_MINMAX: int +NORM_RELATIVE: int +NORM_TYPE_MASK: int +OPTFLOW_FARNEBACK_GAUSSIAN: int +OPTFLOW_LK_GET_MIN_EIGENVALS: int +OPTFLOW_USE_INITIAL_FLOW: int +ORB_FAST_SCORE: int +ORB_HARRIS_SCORE: int +PARAM_ALGORITHM: int +PARAM_BOOLEAN: int +PARAM_FLOAT: int +PARAM_INT: int +PARAM_MAT: int +PARAM_MAT_VECTOR: int +PARAM_REAL: int +PARAM_SCALAR: int +PARAM_STRING: int +PARAM_UCHAR: int +PARAM_UINT64: int +PARAM_UNSIGNED_INT: int +PCA_DATA_AS_COL: int +PCA_DATA_AS_ROW: int +PCA_USE_AVG: int +PROJ_SPHERICAL_EQRECT: int +PROJ_SPHERICAL_ORTHO: int +Param_ALGORITHM: int +Param_BOOLEAN: int +Param_FLOAT: int +Param_INT: int +Param_MAT: int +Param_MAT_VECTOR: int +Param_REAL: int +Param_SCALAR: int +Param_STRING: int +Param_UCHAR: int +Param_UINT64: int +Param_UNSIGNED_INT: int +QRCODE_ENCODER_CORRECT_LEVEL_H: int +QRCODE_ENCODER_CORRECT_LEVEL_L: int +QRCODE_ENCODER_CORRECT_LEVEL_M: int +QRCODE_ENCODER_CORRECT_LEVEL_Q: int +QRCODE_ENCODER_ECI_UTF8: int +QRCODE_ENCODER_MODE_ALPHANUMERIC: int +QRCODE_ENCODER_MODE_AUTO: int +QRCODE_ENCODER_MODE_BYTE: int +QRCODE_ENCODER_MODE_ECI: int +QRCODE_ENCODER_MODE_KANJI: int +QRCODE_ENCODER_MODE_NUMERIC: int +QRCODE_ENCODER_MODE_STRUCTURED_APPEND: int +QRCodeEncoder_CORRECT_LEVEL_H: int +QRCodeEncoder_CORRECT_LEVEL_L: int +QRCodeEncoder_CORRECT_LEVEL_M: int +QRCodeEncoder_CORRECT_LEVEL_Q: int +QRCodeEncoder_ECI_UTF8: int +QRCodeEncoder_MODE_ALPHANUMERIC: int +QRCodeEncoder_MODE_AUTO: int +QRCodeEncoder_MODE_BYTE: int +QRCodeEncoder_MODE_ECI: int +QRCodeEncoder_MODE_KANJI: int +QRCodeEncoder_MODE_NUMERIC: int +QRCodeEncoder_MODE_STRUCTURED_APPEND: int +QT_CHECKBOX: int +QT_FONT_BLACK: int +QT_FONT_BOLD: int +QT_FONT_DEMIBOLD: int +QT_FONT_LIGHT: int +QT_FONT_NORMAL: int +QT_NEW_BUTTONBAR: int +QT_PUSH_BUTTON: int +QT_RADIOBOX: int +QT_STYLE_ITALIC: int +QT_STYLE_NORMAL: int +QT_STYLE_OBLIQUE: int +QUAT_ASSUME_NOT_UNIT: int +QUAT_ASSUME_UNIT: int +QUAT_ENUM_EULER_ANGLES_MAX_VALUE: int +QUAT_ENUM_EXT_XYX: int +QUAT_ENUM_EXT_XYZ: int +QUAT_ENUM_EXT_XZX: int +QUAT_ENUM_EXT_XZY: int +QUAT_ENUM_EXT_YXY: int +QUAT_ENUM_EXT_YXZ: int +QUAT_ENUM_EXT_YZX: int +QUAT_ENUM_EXT_YZY: int +QUAT_ENUM_EXT_ZXY: int +QUAT_ENUM_EXT_ZXZ: int +QUAT_ENUM_EXT_ZYX: int +QUAT_ENUM_EXT_ZYZ: int +QUAT_ENUM_INT_XYX: int +QUAT_ENUM_INT_XYZ: int +QUAT_ENUM_INT_XZX: int +QUAT_ENUM_INT_XZY: int +QUAT_ENUM_INT_YXY: int +QUAT_ENUM_INT_YXZ: int +QUAT_ENUM_INT_YZX: int +QUAT_ENUM_INT_YZY: int +QUAT_ENUM_INT_ZXY: int +QUAT_ENUM_INT_ZXZ: int +QUAT_ENUM_INT_ZYX: int +QUAT_ENUM_INT_ZYZ: int +QuatEnum_EULER_ANGLES_MAX_VALUE: int +QuatEnum_EXT_XYX: int +QuatEnum_EXT_XYZ: int +QuatEnum_EXT_XZX: int +QuatEnum_EXT_XZY: int +QuatEnum_EXT_YXY: int +QuatEnum_EXT_YXZ: int +QuatEnum_EXT_YZX: int +QuatEnum_EXT_YZY: int +QuatEnum_EXT_ZXY: int +QuatEnum_EXT_ZXZ: int +QuatEnum_EXT_ZYX: int +QuatEnum_EXT_ZYZ: int +QuatEnum_INT_XYX: int +QuatEnum_INT_XYZ: int +QuatEnum_INT_XZX: int +QuatEnum_INT_XZY: int +QuatEnum_INT_YXY: int +QuatEnum_INT_YXZ: int +QuatEnum_INT_YZX: int +QuatEnum_INT_YZY: int +QuatEnum_INT_ZXY: int +QuatEnum_INT_ZXZ: int +QuatEnum_INT_ZYX: int +QuatEnum_INT_ZYZ: int +RANSAC: int +RECURS_FILTER: int +REDUCE_AVG: int +REDUCE_MAX: int +REDUCE_MIN: int +REDUCE_SUM: int +RETR_CCOMP: int +RETR_EXTERNAL: int +RETR_FLOODFILL: int +RETR_LIST: int +RETR_TREE: int +RHO: int +RMAT_ACCESS_R: int +RMAT_ACCESS_W: int +RMat_Access_R: int +RMat_Access_W: int +RNG_NORMAL: int +RNG_UNIFORM: int +ROTATE_180: int +ROTATE_90_CLOCKWISE: int +ROTATE_90_COUNTERCLOCKWISE: int +SAMPLING_NAPSAC: int +SAMPLING_PROGRESSIVE_NAPSAC: int +SAMPLING_PROSAC: int +SAMPLING_UNIFORM: int +SCORE_METHOD_LMEDS: int +SCORE_METHOD_MAGSAC: int +SCORE_METHOD_MSAC: int +SCORE_METHOD_RANSAC: int +SOLVELP_MULTI: int +SOLVELP_SINGLE: int +SOLVELP_UNBOUNDED: int +SOLVELP_UNFEASIBLE: int +SOLVEPNP_AP3P: int +SOLVEPNP_DLS: int +SOLVEPNP_EPNP: int +SOLVEPNP_IPPE: int +SOLVEPNP_IPPE_SQUARE: int +SOLVEPNP_ITERATIVE: int +SOLVEPNP_MAX_COUNT: int +SOLVEPNP_P3P: int +SOLVEPNP_SQPNP: int +SOLVEPNP_UPNP: int +SORT_ASCENDING: int +SORT_DESCENDING: int +SORT_EVERY_COLUMN: int +SORT_EVERY_ROW: int +SPARSE_MAT_HASH_BIT: int +SPARSE_MAT_HASH_SCALE: int +SPARSE_MAT_MAGIC_VAL: int +SPARSE_MAT_MAX_DIM: int +STEREO_BM_PREFILTER_NORMALIZED_RESPONSE: int +STEREO_BM_PREFILTER_XSOBEL: int +STEREO_MATCHER_DISP_SCALE: int +STEREO_MATCHER_DISP_SHIFT: int +STEREO_SGBM_MODE_HH: int +STEREO_SGBM_MODE_HH4: int +STEREO_SGBM_MODE_SGBM: int +STEREO_SGBM_MODE_SGBM_3WAY: int +STITCHER_ERR_CAMERA_PARAMS_ADJUST_FAIL: int +STITCHER_ERR_HOMOGRAPHY_EST_FAIL: int +STITCHER_ERR_NEED_MORE_IMGS: int +STITCHER_OK: int +STITCHER_PANORAMA: int +STITCHER_SCANS: int +SUBDIV2D_NEXT_AROUND_DST: int +SUBDIV2D_NEXT_AROUND_LEFT: int +SUBDIV2D_NEXT_AROUND_ORG: int +SUBDIV2D_NEXT_AROUND_RIGHT: int +SUBDIV2D_PREV_AROUND_DST: int +SUBDIV2D_PREV_AROUND_LEFT: int +SUBDIV2D_PREV_AROUND_ORG: int +SUBDIV2D_PREV_AROUND_RIGHT: int +SUBDIV2D_PTLOC_ERROR: int +SUBDIV2D_PTLOC_INSIDE: int +SUBDIV2D_PTLOC_ON_EDGE: int +SUBDIV2D_PTLOC_OUTSIDE_RECT: int +SUBDIV2D_PTLOC_VERTEX: int +SVD_FULL_UV: int +SVD_MODIFY_A: int +SVD_NO_UV: int +SparseMat_HASH_BIT: int +SparseMat_HASH_SCALE: int +SparseMat_MAGIC_VAL: int +SparseMat_MAX_DIM: int +StereoBM_PREFILTER_NORMALIZED_RESPONSE: int +StereoBM_PREFILTER_XSOBEL: int +StereoMatcher_DISP_SCALE: int +StereoMatcher_DISP_SHIFT: int +StereoSGBM_MODE_HH: int +StereoSGBM_MODE_HH4: int +StereoSGBM_MODE_SGBM: int +StereoSGBM_MODE_SGBM_3WAY: int +Stitcher_ERR_CAMERA_PARAMS_ADJUST_FAIL: int +Stitcher_ERR_HOMOGRAPHY_EST_FAIL: int +Stitcher_ERR_NEED_MORE_IMGS: int +Stitcher_OK: int +Stitcher_PANORAMA: int +Stitcher_SCANS: int +Subdiv2D_NEXT_AROUND_DST: int +Subdiv2D_NEXT_AROUND_LEFT: int +Subdiv2D_NEXT_AROUND_ORG: int +Subdiv2D_NEXT_AROUND_RIGHT: int +Subdiv2D_PREV_AROUND_DST: int +Subdiv2D_PREV_AROUND_LEFT: int +Subdiv2D_PREV_AROUND_ORG: int +Subdiv2D_PREV_AROUND_RIGHT: int +Subdiv2D_PTLOC_ERROR: int +Subdiv2D_PTLOC_INSIDE: int +Subdiv2D_PTLOC_ON_EDGE: int +Subdiv2D_PTLOC_OUTSIDE_RECT: int +Subdiv2D_PTLOC_VERTEX: int +TERM_CRITERIA_COUNT: int +TERM_CRITERIA_EPS: int +TERM_CRITERIA_MAX_ITER: int +THRESH_BINARY: int +THRESH_BINARY_INV: int +THRESH_MASK: int +THRESH_OTSU: int +THRESH_TOZERO: int +THRESH_TOZERO_INV: int +THRESH_TRIANGLE: int +THRESH_TRUNC: int +TM_CCOEFF: int +TM_CCOEFF_NORMED: int +TM_CCORR: int +TM_CCORR_NORMED: int +TM_SQDIFF: int +TM_SQDIFF_NORMED: int +TermCriteria_COUNT: int +TermCriteria_EPS: int +TermCriteria_MAX_ITER: int +UMAT_AUTO_STEP: int +UMAT_CONTINUOUS_FLAG: int +UMAT_DATA_ASYNC_CLEANUP: int +UMAT_DATA_COPY_ON_MAP: int +UMAT_DATA_DEVICE_COPY_OBSOLETE: int +UMAT_DATA_DEVICE_MEM_MAPPED: int +UMAT_DATA_HOST_COPY_OBSOLETE: int +UMAT_DATA_TEMP_COPIED_UMAT: int +UMAT_DATA_TEMP_UMAT: int +UMAT_DATA_USER_ALLOCATED: int +UMAT_DEPTH_MASK: int +UMAT_MAGIC_MASK: int +UMAT_MAGIC_VAL: int +UMAT_SUBMATRIX_FLAG: int +UMAT_TYPE_MASK: int +UMatData_ASYNC_CLEANUP: int +UMatData_COPY_ON_MAP: int +UMatData_DEVICE_COPY_OBSOLETE: int +UMatData_DEVICE_MEM_MAPPED: int +UMatData_HOST_COPY_OBSOLETE: int +UMatData_TEMP_COPIED_UMAT: int +UMatData_TEMP_UMAT: int +UMatData_USER_ALLOCATED: int +UMat_AUTO_STEP: int +UMat_CONTINUOUS_FLAG: int +UMat_DEPTH_MASK: int +UMat_MAGIC_MASK: int +UMat_MAGIC_VAL: int +UMat_SUBMATRIX_FLAG: int +UMat_TYPE_MASK: int +USAC_ACCURATE: int +USAC_DEFAULT: int +USAC_FAST: int +USAC_FM_8PTS: int +USAC_MAGSAC: int +USAC_PARALLEL: int +USAC_PROSAC: int +USAGE_ALLOCATE_DEVICE_MEMORY: int +USAGE_ALLOCATE_HOST_MEMORY: int +USAGE_ALLOCATE_SHARED_MEMORY: int +USAGE_DEFAULT: int +VIDEOWRITER_PROP_DEPTH: int +VIDEOWRITER_PROP_FRAMEBYTES: int +VIDEOWRITER_PROP_HW_ACCELERATION: int +VIDEOWRITER_PROP_HW_ACCELERATION_USE_OPENCL: int +VIDEOWRITER_PROP_HW_DEVICE: int +VIDEOWRITER_PROP_IS_COLOR: int +VIDEOWRITER_PROP_NSTRIPES: int +VIDEOWRITER_PROP_QUALITY: int +VIDEO_ACCELERATION_ANY: int +VIDEO_ACCELERATION_D3D11: int +VIDEO_ACCELERATION_MFX: int +VIDEO_ACCELERATION_NONE: int +VIDEO_ACCELERATION_VAAPI: int +WARP_FILL_OUTLIERS: int +WARP_INVERSE_MAP: int +WARP_POLAR_LINEAR: int +WARP_POLAR_LOG: int +WINDOW_AUTOSIZE: int +WINDOW_FREERATIO: int +WINDOW_FULLSCREEN: int +WINDOW_GUI_EXPANDED: int +WINDOW_GUI_NORMAL: int +WINDOW_KEEPRATIO: int +WINDOW_NORMAL: int +WINDOW_OPENGL: int +WND_PROP_ASPECT_RATIO: int +WND_PROP_AUTOSIZE: int +WND_PROP_FULLSCREEN: int +WND_PROP_OPENGL: int +WND_PROP_TOPMOST: int +WND_PROP_VISIBLE: int +WND_PROP_VSYNC: int + + +class AKAZE(Feature2D): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def getDefaultName(self, *args, **kwargs): ... # incomplete + def getDescriptorChannels(self, *args, **kwargs): ... # incomplete + def getDescriptorSize(self, *args, **kwargs): ... # incomplete + def getDescriptorType(self, *args, **kwargs): ... # incomplete + def getDiffusivity(self, *args, **kwargs): ... # incomplete + def getNOctaveLayers(self, *args, **kwargs): ... # incomplete + def getNOctaves(self, *args, **kwargs): ... # incomplete + def getThreshold(self, *args, **kwargs): ... # incomplete + def setDescriptorChannels(self, dch) -> None: ... + def setDescriptorSize(self, dsize) -> None: ... + def setDescriptorType(self, dtype) -> None: ... + def setDiffusivity(self, diff) -> None: ... + def setNOctaveLayers(self, octaveLayers) -> None: ... + def setNOctaves(self, octaves) -> None: ... + def setThreshold(self, threshold) -> None: ... + + +class AffineFeature(Feature2D): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def getDefaultName(self, *args, **kwargs): ... # incomplete + def getViewParams(self, tilts, rolls) -> None: ... + def setViewParams(self, tilts, rolls) -> None: ... + + +class AgastFeatureDetector(Feature2D): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def getDefaultName(self, *args, **kwargs): ... # incomplete + def getNonmaxSuppression(self, *args, **kwargs): ... # incomplete + def getThreshold(self, *args, **kwargs): ... # incomplete + def getType(self, *args, **kwargs): ... # incomplete + def setNonmaxSuppression(self, f) -> None: ... + def setThreshold(self, threshold) -> None: ... + def setType(self, type) -> None: ... + + +class Algorithm: + def __init__(self, *args, **kwargs) -> None: ... + def clear(self) -> None: ... + def empty(self, *args, **kwargs): ... # incomplete + def getDefaultName(self, *args, **kwargs): ... # incomplete + def read(self, fn) -> None: ... + def save(self, filename) -> None: ... + def write(self, *args, **kwargs): ... # incomplete + + +class AlignExposures(Algorithm): + def process(self, src, dst, times, response) -> None: ... + + +class AlignMTB(AlignExposures): + def calculateShift(self, *args, **kwargs): ... # incomplete + def computeBitmaps(self, *args, **kwargs): ... # incomplete + def getCut(self, *args, **kwargs): ... # incomplete + def getExcludeRange(self, *args, **kwargs): ... # incomplete + def getMaxBits(self, *args, **kwargs): ... # incomplete + @overload + def process(self, src, dst, times, response) -> None: ... + @overload + def process(self, src, dst) -> None: ... + def setCut(self, value) -> None: ... + def setExcludeRange(self, exclude_range) -> None: ... + def setMaxBits(self, max_bits) -> None: ... + def shiftMat(self, *args, **kwargs): ... # incomplete + + +class AsyncArray: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def get(self, *args, **kwargs): ... # incomplete + def release(self) -> None: ... + def valid(self, *args, **kwargs): ... # incomplete + def wait_for(self, *args, **kwargs): ... # incomplete + + +class BFMatcher(DescriptorMatcher): + def __init__(self, normType: int | None = ..., crossCheck: _Boolean = ...) -> None: ... + def create(self, *args, **kwargs): ... # incomplete + + +class BOWImgDescriptorExtractor: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def compute(self, *args, **kwargs): ... # incomplete + def descriptorSize(self, *args, **kwargs): ... # incomplete + def descriptorType(self, *args, **kwargs): ... # incomplete + def getVocabulary(self, *args, **kwargs): ... # incomplete + def setVocabulary(self, vocabulary) -> None: ... + + +class BOWKMeansTrainer(BOWTrainer): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def cluster(self, *args, **kwargs): ... # incomplete + + +class BOWTrainer: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def add(self, descriptors) -> None: ... + def clear(self) -> None: ... + def cluster(self, *args, **kwargs): ... # incomplete + def descriptorsCount(self, *args, **kwargs): ... # incomplete + def getDescriptors(self, *args, **kwargs): ... # incomplete + + +class BRISK(Feature2D): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def getDefaultName(self, *args, **kwargs): ... # incomplete + def getOctaves(self, *args, **kwargs): ... # incomplete + def getThreshold(self, *args, **kwargs): ... # incomplete + def setOctaves(self, octaves) -> None: ... + def setThreshold(self, threshold) -> None: ... + + +class BackgroundSubtractor(Algorithm): + def apply(self, *args, **kwargs): ... # incomplete + def getBackgroundImage(self, *args, **kwargs): ... # incomplete + + +class BackgroundSubtractorKNN(BackgroundSubtractor): + def getDetectShadows(self, *args, **kwargs): ... # incomplete + def getDist2Threshold(self, *args, **kwargs): ... # incomplete + def getHistory(self, *args, **kwargs): ... # incomplete + def getNSamples(self, *args, **kwargs): ... # incomplete + def getShadowThreshold(self, *args, **kwargs): ... # incomplete + def getShadowValue(self, *args, **kwargs): ... # incomplete + def getkNNSamples(self, *args, **kwargs): ... # incomplete + def setDetectShadows(self, detectShadows) -> None: ... + def setDist2Threshold(self, _dist2Threshold) -> None: ... + def setHistory(self, history) -> None: ... + def setNSamples(self, _nN) -> None: ... + def setShadowThreshold(self, threshold) -> None: ... + def setShadowValue(self, value) -> None: ... + def setkNNSamples(self, _nkNN) -> None: ... + + +class BackgroundSubtractorMOG2(BackgroundSubtractor): + def apply(self, *args, **kwargs): ... # incomplete + def getBackgroundRatio(self, *args, **kwargs): ... # incomplete + def getComplexityReductionThreshold(self, *args, **kwargs): ... # incomplete + def getDetectShadows(self, *args, **kwargs): ... # incomplete + def getHistory(self, *args, **kwargs): ... # incomplete + def getNMixtures(self, *args, **kwargs): ... # incomplete + def getShadowThreshold(self, *args, **kwargs): ... # incomplete + def getShadowValue(self, *args, **kwargs): ... # incomplete + def getVarInit(self, *args, **kwargs): ... # incomplete + def getVarMax(self, *args, **kwargs): ... # incomplete + def getVarMin(self, *args, **kwargs): ... # incomplete + def getVarThreshold(self, *args, **kwargs): ... # incomplete + def getVarThresholdGen(self, *args, **kwargs): ... # incomplete + def setBackgroundRatio(self, ratio) -> None: ... + def setComplexityReductionThreshold(self, ct) -> None: ... + def setDetectShadows(self, detectShadows) -> None: ... + def setHistory(self, history) -> None: ... + def setNMixtures(self, nmixtures) -> None: ... + def setShadowThreshold(self, threshold) -> None: ... + def setShadowValue(self, value) -> None: ... + def setVarInit(self, varInit) -> None: ... + def setVarMax(self, varMax) -> None: ... + def setVarMin(self, varMin) -> None: ... + def setVarThreshold(self, varThreshold) -> None: ... + def setVarThresholdGen(self, varThresholdGen) -> None: ... + + +class BaseCascadeClassifier(Algorithm): ... + + +class CLAHE(Algorithm): + def apply(self, *args, **kwargs): ... # incomplete + def collectGarbage(self) -> None: ... + def getClipLimit(self, *args, **kwargs): ... # incomplete + def getTilesGridSize(self, *args, **kwargs): ... # incomplete + def setClipLimit(self, clipLimit) -> None: ... + def setTilesGridSize(self, tileGridSize) -> None: ... + + +class CalibrateCRF(Algorithm): + def process(self, *args, **kwargs): ... # incomplete + + +class CalibrateDebevec(CalibrateCRF): + def getLambda(self, *args, **kwargs): ... # incomplete + def getRandom(self, *args, **kwargs): ... # incomplete + def getSamples(self, *args, **kwargs): ... # incomplete + def setLambda(self, lambda_) -> None: ... + def setRandom(self, random) -> None: ... + def setSamples(self, samples) -> None: ... + + +class CalibrateRobertson(CalibrateCRF): + def getMaxIter(self, *args, **kwargs): ... # incomplete + def getRadiance(self, *args, **kwargs): ... # incomplete + def getThreshold(self, *args, **kwargs): ... # incomplete + def setMaxIter(self, max_iter) -> None: ... + def setThreshold(self, threshold) -> None: ... + + +class CascadeClassifier: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def convert(self, *args, **kwargs): ... # incomplete + def detectMultiScale(self, *args, **kwargs): ... # incomplete + def detectMultiScale2(self, *args, **kwargs): ... # incomplete + def detectMultiScale3(self, *args, **kwargs): ... # incomplete + def empty(self, *args, **kwargs): ... # incomplete + def getFeatureType(self, *args, **kwargs): ... # incomplete + def getOriginalWindowSize(self, *args, **kwargs): ... # incomplete + def isOldFormatCascade(self, *args, **kwargs): ... # incomplete + def load(self, *args, **kwargs): ... # incomplete + def read(self, *args, **kwargs): ... # incomplete + + +class CirclesGridFinderParameters: + convexHullFactor: Incomplete + densityNeighborhoodSize: Incomplete + edgeGain: Incomplete + edgePenalty: Incomplete + existingVertexGain: Incomplete + keypointScale: Incomplete + kmeansAttempts: Incomplete + maxRectifiedDistance: Incomplete + minDensity: Incomplete + minDistanceToAddKeypoint: Incomplete + minGraphConfidence: Incomplete + minRNGEdgeSwitchDist: Incomplete + squareSize: Incomplete + vertexGain: Incomplete + vertexPenalty: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class DISOpticalFlow(DenseOpticalFlow): + def create(self, *args, **kwargs): ... # incomplete + def getFinestScale(self, *args, **kwargs): ... # incomplete + def getGradientDescentIterations(self, *args, **kwargs): ... # incomplete + def getPatchSize(self, *args, **kwargs): ... # incomplete + def getPatchStride(self, *args, **kwargs): ... # incomplete + def getUseMeanNormalization(self, *args, **kwargs): ... # incomplete + def getUseSpatialPropagation(self, *args, **kwargs): ... # incomplete + def getVariationalRefinementAlpha(self, *args, **kwargs): ... # incomplete + def getVariationalRefinementDelta(self, *args, **kwargs): ... # incomplete + def getVariationalRefinementGamma(self, *args, **kwargs): ... # incomplete + def getVariationalRefinementIterations(self, *args, **kwargs): ... # incomplete + def setFinestScale(self, val) -> None: ... + def setGradientDescentIterations(self, val) -> None: ... + def setPatchSize(self, val) -> None: ... + def setPatchStride(self, val) -> None: ... + def setUseMeanNormalization(self, val) -> None: ... + def setUseSpatialPropagation(self, val) -> None: ... + def setVariationalRefinementAlpha(self, val) -> None: ... + def setVariationalRefinementDelta(self, val) -> None: ... + def setVariationalRefinementGamma(self, val) -> None: ... + def setVariationalRefinementIterations(self, val) -> None: ... + + +class DMatch: + distance: Incomplete + imgIdx: Incomplete + queryIdx: Incomplete + trainIdx: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class DenseOpticalFlow(Algorithm): + def calc(self, I0, I1, flow) -> _flow: ... + def collectGarbage(self) -> None: ... + + +class DescriptorMatcher(Algorithm): + def add(self, descriptors) -> None: ... + def clear(self) -> None: ... + def clone(self, *args, **kwargs): ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def empty(self, *args, **kwargs): ... # incomplete + def getTrainDescriptors(self, *args, **kwargs): ... # incomplete + def isMaskSupported(self, *args, **kwargs): ... # incomplete + def knnMatch(self, *args, **kwargs): ... # incomplete + def match(self, *args, **kwargs): ... # incomplete + def radiusMatch(self, *args, **kwargs): ... # incomplete + def read(self, fileName) -> None: ... + def train(self) -> None: ... + def write(self, fileName) -> None: ... + + +class FaceDetectorYN: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def detect(self, *args, **kwargs): ... # incomplete + def getInputSize(self, *args, **kwargs): ... # incomplete + def getNMSThreshold(self, *args, **kwargs): ... # incomplete + def getScoreThreshold(self, *args, **kwargs): ... # incomplete + def getTopK(self, *args, **kwargs): ... # incomplete + def setInputSize(self, input_size) -> None: ... + def setNMSThreshold(self, nms_threshold) -> None: ... + def setScoreThreshold(self, score_threshold) -> None: ... + def setTopK(self, top_k) -> None: ... + + +class FaceRecognizerSF: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def alignCrop(self, *args, **kwargs): ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def feature(self, *args, **kwargs): ... # incomplete + def match(self, *args, **kwargs): ... # incomplete + + +class FarnebackOpticalFlow(DenseOpticalFlow): + def create(self, *args, **kwargs): ... # incomplete + def getFastPyramids(self, *args, **kwargs): ... # incomplete + def getFlags(self, *args, **kwargs): ... # incomplete + def getNumIters(self, *args, **kwargs): ... # incomplete + def getNumLevels(self, *args, **kwargs): ... # incomplete + def getPolyN(self, *args, **kwargs): ... # incomplete + def getPolySigma(self, *args, **kwargs): ... # incomplete + def getPyrScale(self, *args, **kwargs): ... # incomplete + def getWinSize(self, *args, **kwargs): ... # incomplete + def setFastPyramids(self, fastPyramids) -> None: ... + def setFlags(self, flags: int | None) -> None: ... + def setNumIters(self, numIters) -> None: ... + def setNumLevels(self, numLevels) -> None: ... + def setPolyN(self, polyN) -> None: ... + def setPolySigma(self, polySigma) -> None: ... + def setPyrScale(self, pyrScale) -> None: ... + def setWinSize(self, winSize) -> None: ... + + +class FastFeatureDetector(Feature2D): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def getDefaultName(self, *args, **kwargs): ... # incomplete + def getNonmaxSuppression(self, *args, **kwargs): ... # incomplete + def getThreshold(self, *args, **kwargs): ... # incomplete + def getType(self, *args, **kwargs): ... # incomplete + def setNonmaxSuppression(self, f) -> None: ... + def setThreshold(self, threshold) -> None: ... + def setType(self, type) -> None: ... + + +class Feature2D: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def compute(self, *args, **kwargs): ... # incomplete + def defaultNorm(self, *args, **kwargs): ... # incomplete + def descriptorSize(self, *args, **kwargs): ... # incomplete + def descriptorType(self, *args, **kwargs): ... # incomplete + def detect(self, *args, **kwargs): ... # incomplete + def detectAndCompute(self, *args, **kwargs): ... # incomplete + def empty(self, *args, **kwargs): ... # incomplete + def getDefaultName(self, *args, **kwargs): ... # incomplete + @overload + def read(self, fileName) -> None: ... + @overload + def read(self, arg1) -> None: ... + def write(self, fileName) -> None: ... + + +class FileNode: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def at(self, *args, **kwargs): ... # incomplete + def empty(self, *args, **kwargs): ... # incomplete + def getNode(self, *args, **kwargs): ... # incomplete + def isInt(self, *args, **kwargs): ... # incomplete + def isMap(self, *args, **kwargs): ... # incomplete + def isNamed(self, *args, **kwargs): ... # incomplete + def isNone(self, *args, **kwargs): ... # incomplete + def isReal(self, *args, **kwargs): ... # incomplete + def isSeq(self, *args, **kwargs): ... # incomplete + def isString(self, *args, **kwargs): ... # incomplete + def keys(self, *args, **kwargs): ... # incomplete + def mat(self, *args, **kwargs): ... # incomplete + def name(self, *args, **kwargs): ... # incomplete + def rawSize(self, *args, **kwargs): ... # incomplete + def real(self, *args, **kwargs): ... # incomplete + def size(self, *args, **kwargs): ... # incomplete + def string(self, *args, **kwargs): ... # incomplete + def type(self, *args, **kwargs): ... # incomplete + + +class FileStorage: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def endWriteStruct(self) -> None: ... + def getFirstTopLevelNode(self, *args, **kwargs): ... # incomplete + def getFormat(self, *args, **kwargs): ... # incomplete + def getNode(self, *args, **kwargs): ... # incomplete + def isOpened(self, *args, **kwargs): ... # incomplete + def open(self, *args, **kwargs): ... # incomplete + def release(self) -> None: ... + def releaseAndGetString(self, *args, **kwargs): ... # incomplete + def root(self, *args, **kwargs): ... # incomplete + def startWriteStruct(self, *args, **kwargs): ... # incomplete + def write(self, name, val) -> None: ... + def writeComment(self, *args, **kwargs): ... # incomplete + + +class FlannBasedMatcher(DescriptorMatcher): + def __init__(self, indexParams=..., searchParams=...) -> None: ... + def create(self, *args, **kwargs): ... # incomplete + + +class GArrayDesc: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class GArrayT: + def __init__(self, type: int) -> None: ... + def type(self) -> int: ... + + +class GCompileArg: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class GComputation: + def __init__(self, arg: gapi_GKernelPackage | gapi_GNetPackage | queue_capacity) -> None: ... + def apply(self): ... + def compileStreaming(self, *args, **kwargs): ... # incomplete + + +class GFTTDetector(Feature2D): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def getBlockSize(self, *args, **kwargs): ... # incomplete + def getDefaultName(self, *args, **kwargs): ... # incomplete + def getHarrisDetector(self, *args, **kwargs): ... # incomplete + def getK(self, *args, **kwargs): ... # incomplete + def getMaxFeatures(self, *args, **kwargs): ... # incomplete + def getMinDistance(self, *args, **kwargs): ... # incomplete + def getQualityLevel(self, *args, **kwargs): ... # incomplete + def setBlockSize(self, blockSize) -> None: ... + def setHarrisDetector(self, val) -> None: ... + def setK(self, k) -> None: ... + def setMaxFeatures(self, maxFeatures) -> None: ... + def setMinDistance(self, minDistance) -> None: ... + def setQualityLevel(self, qlevel) -> None: ... + + +class GFrame: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class GInferInputs: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def setInput(self, *args, **kwargs): ... # incomplete + + +class GInferListInputs: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def setInput(self, *args, **kwargs): ... # incomplete + + +class GInferListOutputs: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def at(self, *args, **kwargs): ... # incomplete + + +class GInferOutputs: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def at(self, *args, **kwargs): ... # incomplete + + +class GMat: + def __init__(self) -> None: ... + + +class GMatDesc: + chan: Incomplete + depth: Incomplete + dims: Incomplete + planar: Incomplete + size: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def asInterleaved(self, *args, **kwargs): ... # incomplete + def asPlanar(self, *args, **kwargs): ... # incomplete + def withDepth(self, *args, **kwargs): ... # incomplete + def withSize(self, *args, **kwargs): ... # incomplete + def withSizeDelta(self, *args, **kwargs): ... # incomplete + def withType(self, *args, **kwargs): ... # incomplete + + +class GOpaqueDesc: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class GOpaqueT: + def __init__(self, type: int) -> None: ... + def type(self) -> int: ... + + +class GScalar: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class GScalarDesc: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class GStreamingCompiled: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def pull(self, *args, **kwargs): ... # incomplete + def running(self, *args, **kwargs): ... # incomplete + def setSource(self, callback) -> None: ... + def start(self) -> None: ... + def stop(self) -> None: ... + + +class GeneralizedHough(Algorithm): + def detect(self, *args, **kwargs): ... # incomplete + def getCannyHighThresh(self, *args, **kwargs): ... # incomplete + def getCannyLowThresh(self, *args, **kwargs): ... # incomplete + def getDp(self, *args, **kwargs): ... # incomplete + def getMaxBufferSize(self, *args, **kwargs): ... # incomplete + def getMinDist(self, *args, **kwargs): ... # incomplete + def setCannyHighThresh(self, cannyHighThresh) -> None: ... + def setCannyLowThresh(self, cannyLowThresh) -> None: ... + def setDp(self, dp) -> None: ... + def setMaxBufferSize(self, maxBufferSize) -> None: ... + def setMinDist(self, minDist) -> None: ... + def setTemplate(self, *args, **kwargs): ... # incomplete + + +class GeneralizedHoughBallard(GeneralizedHough): + def getLevels(self, *args, **kwargs): ... # incomplete + def getVotesThreshold(self, *args, **kwargs): ... # incomplete + def setLevels(self, levels) -> None: ... + def setVotesThreshold(self, votesThreshold) -> None: ... + + +class GeneralizedHoughGuil(GeneralizedHough): + def getAngleEpsilon(self, *args, **kwargs): ... # incomplete + def getAngleStep(self, *args, **kwargs): ... # incomplete + def getAngleThresh(self, *args, **kwargs): ... # incomplete + def getLevels(self, *args, **kwargs): ... # incomplete + def getMaxAngle(self, *args, **kwargs): ... # incomplete + def getMaxScale(self, *args, **kwargs): ... # incomplete + def getMinAngle(self, *args, **kwargs): ... # incomplete + def getMinScale(self, *args, **kwargs): ... # incomplete + def getPosThresh(self, *args, **kwargs): ... # incomplete + def getScaleStep(self, *args, **kwargs): ... # incomplete + def getScaleThresh(self, *args, **kwargs): ... # incomplete + def getXi(self, *args, **kwargs): ... # incomplete + def setAngleEpsilon(self, angleEpsilon) -> None: ... + def setAngleStep(self, angleStep) -> None: ... + def setAngleThresh(self, angleThresh) -> None: ... + def setLevels(self, levels) -> None: ... + def setMaxAngle(self, maxAngle) -> None: ... + def setMaxScale(self, maxScale) -> None: ... + def setMinAngle(self, minAngle) -> None: ... + def setMinScale(self, minScale) -> None: ... + def setPosThresh(self, posThresh) -> None: ... + def setScaleStep(self, scaleStep) -> None: ... + def setScaleThresh(self, scaleThresh) -> None: ... + def setXi(self, xi) -> None: ... + + +class HOGDescriptor: + L2HysThreshold: Incomplete + blockSize: Incomplete + blockStride: Incomplete + cellSize: Incomplete + derivAperture: Incomplete + gammaCorrection: Incomplete + histogramNormType: Incomplete + nbins: Incomplete + nlevels: Incomplete + signedGradient: Incomplete + svmDetector: Incomplete + winSigma: Incomplete + winSize: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def checkDetectorSize(self, *args, **kwargs): ... # incomplete + def compute(self, *args, **kwargs): ... # incomplete + def computeGradient(self, *args, **kwargs): ... # incomplete + def detect(self, *args, **kwargs): ... # incomplete + def detectMultiScale(self, *args, **kwargs): ... # incomplete + def getDaimlerPeopleDetector(self, *args, **kwargs): ... # incomplete + def getDefaultPeopleDetector(self, *args, **kwargs): ... # incomplete + def getDescriptorSize(self, *args, **kwargs): ... # incomplete + def getWinSigma(self, *args, **kwargs): ... # incomplete + def load(self, *args, **kwargs): ... # incomplete + def save(self, *args, **kwargs): ... # incomplete + def setSVMDetector(self, svmdetector) -> None: ... + + +class KAZE(Feature2D): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def getDefaultName(self, *args, **kwargs): ... # incomplete + def getDiffusivity(self, *args, **kwargs): ... # incomplete + def getExtended(self, *args, **kwargs): ... # incomplete + def getNOctaveLayers(self, *args, **kwargs): ... # incomplete + def getNOctaves(self, *args, **kwargs): ... # incomplete + def getThreshold(self, *args, **kwargs): ... # incomplete + def getUpright(self, *args, **kwargs): ... # incomplete + def setDiffusivity(self, diff) -> None: ... + def setExtended(self, extended) -> None: ... + def setNOctaveLayers(self, octaveLayers) -> None: ... + def setNOctaves(self, octaves) -> None: ... + def setThreshold(self, threshold) -> None: ... + def setUpright(self, upright) -> None: ... + + +class KalmanFilter: + controlMatrix: Incomplete + errorCovPost: Incomplete + errorCovPre: Incomplete + gain: Incomplete + measurementMatrix: Incomplete + measurementNoiseCov: Incomplete + processNoiseCov: Incomplete + statePost: Incomplete + statePre: Incomplete + transitionMatrix: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def correct(self, *args, **kwargs): ... # incomplete + def predict(self, *args, **kwargs): ... # incomplete + + +class KeyPoint: + angle: Incomplete + class_id: Incomplete + octave: Incomplete + pt: Incomplete + response: Incomplete + size: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def convert(self, *args, **kwargs): ... # incomplete + def overlap(self, *args, **kwargs): ... # incomplete + + +class LineSegmentDetector(Algorithm): + def compareSegments(self, *args, **kwargs): ... # incomplete + def detect(self, *args, **kwargs): ... # incomplete + def drawSegments(self, image, lines) -> _image: ... + + +class MSER(Feature2D): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def detectRegions(self, *args, **kwargs): ... # incomplete + def getDefaultName(self, *args, **kwargs): ... # incomplete + def getDelta(self, *args, **kwargs): ... # incomplete + def getMaxArea(self, *args, **kwargs): ... # incomplete + def getMinArea(self, *args, **kwargs): ... # incomplete + def getPass2Only(self, *args, **kwargs): ... # incomplete + def setDelta(self, delta) -> None: ... + def setMaxArea(self, maxArea) -> None: ... + def setMinArea(self, minArea) -> None: ... + def setPass2Only(self, f) -> None: ... + + +class MergeDebevec(MergeExposures): + def process(self, *args, **kwargs): ... # incomplete + + +class MergeExposures(Algorithm): + def process(self, *args, **kwargs): ... # incomplete + + +class MergeMertens(MergeExposures): + def getContrastWeight(self, *args, **kwargs): ... # incomplete + def getExposureWeight(self, *args, **kwargs): ... # incomplete + def getSaturationWeight(self, *args, **kwargs): ... # incomplete + def process(self, *args, **kwargs): ... # incomplete + def setContrastWeight(self, contrast_weiht) -> None: ... + def setExposureWeight(self, exposure_weight) -> None: ... + def setSaturationWeight(self, saturation_weight) -> None: ... + + +class MergeRobertson(MergeExposures): + def process(self, *args, **kwargs): ... # incomplete + + +class ORB(Feature2D): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def getDefaultName(self, *args, **kwargs): ... # incomplete + def getEdgeThreshold(self, *args, **kwargs): ... # incomplete + def getFastThreshold(self, *args, **kwargs): ... # incomplete + def getFirstLevel(self, *args, **kwargs): ... # incomplete + def getMaxFeatures(self, *args, **kwargs): ... # incomplete + def getNLevels(self, *args, **kwargs): ... # incomplete + def getPatchSize(self, *args, **kwargs): ... # incomplete + def getScaleFactor(self, *args, **kwargs): ... # incomplete + def getScoreType(self, *args, **kwargs): ... # incomplete + def getWTA_K(self, *args, **kwargs): ... # incomplete + def setEdgeThreshold(self, edgeThreshold) -> None: ... + def setFastThreshold(self, fastThreshold) -> None: ... + def setFirstLevel(self, firstLevel) -> None: ... + def setMaxFeatures(self, maxFeatures) -> None: ... + def setNLevels(self, nlevels) -> None: ... + def setPatchSize(self, patchSize) -> None: ... + def setScaleFactor(self, scaleFactor) -> None: ... + def setScoreType(self, scoreType) -> None: ... + def setWTA_K(self, wta_k) -> None: ... + + +class PyRotationWarper: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def buildMaps(self, *args, **kwargs): ... # incomplete + def getScale(self, *args, **kwargs): ... # incomplete + def setScale(self, arg1) -> None: ... + def warp(self, *args, **kwargs): ... # incomplete + def warpBackward(self, *args, **kwargs): ... # incomplete + def warpPoint(self, *args, **kwargs): ... # incomplete + def warpPointBackward(self, *args, **kwargs): ... # incomplete + def warpRoi(self, *args, **kwargs): ... # incomplete + + +class QRCodeDetector: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def decode(self, *args, **kwargs): ... # incomplete + def decodeCurved(self, *args, **kwargs): ... # incomplete + def decodeMulti(self, *args, **kwargs): ... # incomplete + def detect(self, *args, **kwargs): ... # incomplete + def detectAndDecode(self, *args, **kwargs): ... # incomplete + def detectAndDecodeCurved(self, *args, **kwargs): ... # incomplete + def detectAndDecodeMulti(self, *args, **kwargs): ... # incomplete + def detectMulti(self, *args, **kwargs): ... # incomplete + def setEpsX(self, epsX) -> None: ... + def setEpsY(self, epsY) -> None: ... + + +class QRCodeEncoder: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def encode(self, *args, **kwargs): ... # incomplete + def encodeStructuredAppend(self, *args, **kwargs): ... # incomplete + + +class QRCodeEncoder_Params: + correction_level: Incomplete + mode: Incomplete + structure_number: Incomplete + version: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class SIFT(Feature2D): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def getDefaultName(self, *args, **kwargs): ... # incomplete + + +class SimpleBlobDetector(Feature2D): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def getDefaultName(self, *args, **kwargs): ... # incomplete + + +class SimpleBlobDetector_Params: + blobColor: Incomplete + filterByArea: Incomplete + filterByCircularity: Incomplete + filterByColor: Incomplete + filterByConvexity: Incomplete + filterByInertia: Incomplete + maxArea: Incomplete + maxCircularity: Incomplete + maxConvexity: Incomplete + maxInertiaRatio: Incomplete + maxThreshold: Incomplete + minArea: Incomplete + minCircularity: Incomplete + minConvexity: Incomplete + minDistBetweenBlobs: Incomplete + minInertiaRatio: Incomplete + minRepeatability: Incomplete + minThreshold: Incomplete + thresholdStep: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class SparseOpticalFlow(Algorithm): + def calc(self, *args, **kwargs): ... # incomplete + + +class SparsePyrLKOpticalFlow(SparseOpticalFlow): + def create(self, *args, **kwargs): ... # incomplete + def getFlags(self, *args, **kwargs): ... # incomplete + def getMaxLevel(self, *args, **kwargs): ... # incomplete + def getMinEigThreshold(self, *args, **kwargs): ... # incomplete + def getTermCriteria(self, *args, **kwargs): ... # incomplete + def getWinSize(self, *args, **kwargs): ... # incomplete + def setFlags(self, flags: int | None) -> None: ... + def setMaxLevel(self, maxLevel) -> None: ... + def setMinEigThreshold(self, minEigThreshold) -> None: ... + def setTermCriteria(self, crit) -> None: ... + def setWinSize(self, winSize) -> None: ... + + +class StereoBM(StereoMatcher): + def create(self, *args, **kwargs): ... # incomplete + def getPreFilterCap(self, *args, **kwargs): ... # incomplete + def getPreFilterSize(self, *args, **kwargs): ... # incomplete + def getPreFilterType(self, *args, **kwargs): ... # incomplete + def getROI1(self, *args, **kwargs): ... # incomplete + def getROI2(self, *args, **kwargs): ... # incomplete + def getSmallerBlockSize(self, *args, **kwargs): ... # incomplete + def getTextureThreshold(self, *args, **kwargs): ... # incomplete + def getUniquenessRatio(self, *args, **kwargs): ... # incomplete + def setPreFilterCap(self, preFilterCap) -> None: ... + def setPreFilterSize(self, preFilterSize) -> None: ... + def setPreFilterType(self, preFilterType) -> None: ... + def setROI1(self, roi1) -> None: ... + def setROI2(self, roi2) -> None: ... + def setSmallerBlockSize(self, blockSize) -> None: ... + def setTextureThreshold(self, textureThreshold) -> None: ... + def setUniquenessRatio(self, uniquenessRatio) -> None: ... + + +class StereoMatcher(Algorithm): + def compute(self, *args, **kwargs): ... # incomplete + def getBlockSize(self, *args, **kwargs): ... # incomplete + def getDisp12MaxDiff(self, *args, **kwargs): ... # incomplete + def getMinDisparity(self, *args, **kwargs): ... # incomplete + def getNumDisparities(self, *args, **kwargs): ... # incomplete + def getSpeckleRange(self, *args, **kwargs): ... # incomplete + def getSpeckleWindowSize(self, *args, **kwargs): ... # incomplete + def setBlockSize(self, blockSize) -> None: ... + def setDisp12MaxDiff(self, disp12MaxDiff) -> None: ... + def setMinDisparity(self, minDisparity) -> None: ... + def setNumDisparities(self, numDisparities) -> None: ... + def setSpeckleRange(self, speckleRange) -> None: ... + def setSpeckleWindowSize(self, speckleWindowSize) -> None: ... + + +class StereoSGBM(StereoMatcher): + def create(self, *args, **kwargs): ... # incomplete + def getMode(self, *args, **kwargs): ... # incomplete + def getP1(self, *args, **kwargs): ... # incomplete + def getP2(self, *args, **kwargs): ... # incomplete + def getPreFilterCap(self, *args, **kwargs): ... # incomplete + def getUniquenessRatio(self, *args, **kwargs): ... # incomplete + def setMode(self, mode) -> None: ... + def setP1(self, P1) -> None: ... + def setP2(self, P2) -> None: ... + def setPreFilterCap(self, preFilterCap) -> None: ... + def setUniquenessRatio(self, uniquenessRatio) -> None: ... + + +class Stitcher: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def composePanorama(self, *args, **kwargs): ... # incomplete + def compositingResol(self, *args, **kwargs): ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def estimateTransform(self, *args, **kwargs): ... # incomplete + def interpolationFlags(self, *args, **kwargs): ... # incomplete + def panoConfidenceThresh(self, *args, **kwargs): ... # incomplete + def registrationResol(self, *args, **kwargs): ... # incomplete + def seamEstimationResol(self, *args, **kwargs): ... # incomplete + def setCompositingResol(self, resol_mpx) -> None: ... + def setInterpolationFlags(self, interp_flags: int | None) -> None: ... + def setPanoConfidenceThresh(self, conf_thresh) -> None: ... + def setRegistrationResol(self, resol_mpx) -> None: ... + def setSeamEstimationResol(self, resol_mpx) -> None: ... + def setWaveCorrection(self, flag) -> None: ... + def stitch(self, *args, **kwargs): ... # incomplete + def waveCorrection(self, *args, **kwargs): ... # incomplete + def workScale(self, *args, **kwargs): ... # incomplete + + +class Subdiv2D: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def edgeDst(self, *args, **kwargs): ... # incomplete + def edgeOrg(self, *args, **kwargs): ... # incomplete + def findNearest(self, *args, **kwargs): ... # incomplete + def getEdge(self, *args, **kwargs): ... # incomplete + def getEdgeList(self) -> _edgeList: ... + def getLeadingEdgeList(self) -> _leadingEdgeList: ... + def getTriangleList(self) -> _triangleList: ... + def getVertex(self, *args, **kwargs): ... # incomplete + def getVoronoiFacetList(self, *args, **kwargs): ... # incomplete + def initDelaunay(self, rect) -> None: ... + def insert(self, ptvec) -> None: ... + def locate(self, *args, **kwargs): ... # incomplete + def nextEdge(self, *args, **kwargs): ... # incomplete + def rotateEdge(self, *args, **kwargs): ... # incomplete + def symEdge(self, *args, **kwargs): ... # incomplete + + +class TickMeter: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def getAvgTimeMilli(self, *args, **kwargs): ... # incomplete + def getAvgTimeSec(self, *args, **kwargs): ... # incomplete + def getCounter(self, *args, **kwargs): ... # incomplete + def getFPS(self, *args, **kwargs): ... # incomplete + def getTimeMicro(self, *args, **kwargs): ... # incomplete + def getTimeMilli(self, *args, **kwargs): ... # incomplete + def getTimeSec(self, *args, **kwargs): ... # incomplete + def getTimeTicks(self, *args, **kwargs): ... # incomplete + def reset(self) -> None: ... + def start(self) -> None: ... + def stop(self) -> None: ... + + +class Tonemap(Algorithm): + def getGamma(self, *args, **kwargs): ... # incomplete + def process(self, *args, **kwargs): ... # incomplete + def setGamma(self, gamma) -> None: ... + + +class TonemapDrago(Tonemap): + def getBias(self, *args, **kwargs): ... # incomplete + def getSaturation(self, *args, **kwargs): ... # incomplete + def setBias(self, bias) -> None: ... + def setSaturation(self, saturation) -> None: ... + + +class TonemapMantiuk(Tonemap): + def getSaturation(self, *args, **kwargs): ... # incomplete + def getScale(self, *args, **kwargs): ... # incomplete + def setSaturation(self, saturation) -> None: ... + def setScale(self, scale) -> None: ... + + +class TonemapReinhard(Tonemap): + def getColorAdaptation(self, *args, **kwargs): ... # incomplete + def getIntensity(self, *args, **kwargs): ... # incomplete + def getLightAdaptation(self, *args, **kwargs): ... # incomplete + def setColorAdaptation(self, color_adapt) -> None: ... + def setIntensity(self, intensity) -> None: ... + def setLightAdaptation(self, light_adapt) -> None: ... + + +class Tracker: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def init(self, image, boundingBox) -> None: ... + def update(self, *args, **kwargs): ... # incomplete + + +class TrackerDaSiamRPN(Tracker): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def getTrackingScore(self, *args, **kwargs): ... # incomplete + + +class TrackerDaSiamRPN_Params: + backend: Incomplete + kernel_cls1: Incomplete + kernel_r1: Incomplete + model: Incomplete + target: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class TrackerGOTURN(Tracker): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + + +class TrackerGOTURN_Params: + modelBin: Incomplete + modelTxt: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class TrackerMIL(Tracker): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + + +class TrackerMIL_Params: + featureSetNumFeatures: Incomplete + samplerInitInRadius: Incomplete + samplerInitMaxNegNum: Incomplete + samplerSearchWinSize: Incomplete + samplerTrackInRadius: Incomplete + samplerTrackMaxNegNum: Incomplete + samplerTrackMaxPosNum: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class UMat: + offset: Incomplete + @overload + def __init__(self, usageFlags: int | None = ...) -> None: ... + @overload + def __init__(self, rows: int | None, cols: int | None, type: int | None, usageFlags: int | None = ...) -> None: ... + @overload + def __init__(self, size: _Size | None, type: int | None, usageFlags: int | None = ...) -> None: ... + + @overload + def __init__( + self, rows: int | None, cols: int | None, type: int | None, s: _Scalar, usageFlags: int | None = ..., + ) -> None: ... + @overload + def __init__(self, size: _Size | None, type: int | None, s: _Scalar, usageFlags: int | None = ...) -> None: ... + @overload + def __init__(self, m: _UMat) -> None: ... + @overload + def __init__(self, m: _UMat, rowRange: _Range | None, colRange: _Range | None = ...) -> None: ... + @overload + def __init__(self, m: _UMat, roi: _Rect | None) -> None: ... + @overload + def __init__(self, m: _UMat, ranges: Sequence[_Range | None] | None) -> None: ... + @staticmethod + def context(): ... + def get(self): ... + def handle(self, accessFlags): ... + def isContinuous(self): ... + def isSubmatrix(self): ... + @staticmethod + def queue(): ... + + +class UsacParams: + confidence: Incomplete + isParallel: Incomplete + loIterations: Incomplete + loMethod: Incomplete + loSampleSize: Incomplete + maxIterations: Incomplete + neighborsSearch: Incomplete + randomGeneratorState: Incomplete + sampler: Incomplete + score: Incomplete + threshold: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class VariationalRefinement(DenseOpticalFlow): + def calcUV(self, *args, **kwargs): ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def getAlpha(self, *args, **kwargs): ... # incomplete + def getDelta(self, *args, **kwargs): ... # incomplete + def getFixedPointIterations(self, *args, **kwargs): ... # incomplete + def getGamma(self, *args, **kwargs): ... # incomplete + def getOmega(self, *args, **kwargs): ... # incomplete + def getSorIterations(self, *args, **kwargs): ... # incomplete + def setAlpha(self, val) -> None: ... + def setDelta(self, val) -> None: ... + def setFixedPointIterations(self, val) -> None: ... + def setGamma(self, val) -> None: ... + def setOmega(self, val) -> None: ... + def setSorIterations(self, val) -> None: ... + + +class VideoCapture: + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, filename: str) -> None: ... + @overload + def __init__(self, filename: str, apiPreference: int | None, params: Sequence[int] = ...) -> None: ... + @overload + def __init__(self, index: int) -> None: ... + @overload + def __init__(self, index: int, apiPreference: int | None, params: Sequence[int] = ...) -> None: ... + def get(self, propId: int) -> float: ... + def getBackendName(self) -> str: ... + def getExceptionMode(self) -> bool: ... + def grab(self) -> bool: ... + def isOpened(self) -> bool: ... + @overload + def open(self, filename: str, apiPreference: int = ...) -> bool: ... + @overload + def open(self, filename: str, apiPreference: int, params: Sequence[int]) -> bool: ... + @overload + def open(self, index: int, apiPreference: int = ...) -> bool: ... + @overload + def open(self, index: int, apiPreference: int, params: Sequence[int]) -> bool: ... + @overload + def read(self, image: Mat | None = ...) -> tuple[bool, Mat]: ... + @overload + def read(self, image: _UMat) -> tuple[bool, UMat]: ... + def release(self) -> None: ... + @overload + def retrieve(self, image: Mat | None = ..., flag: int = ...) -> tuple[bool, Mat]: ... + @overload + def retrieve(self, image: _UMat, flag: int = ...) -> tuple[bool, UMat]: ... + def set(self, propId: int, value: float) -> bool: ... + def setExceptionMode(self, enable: bool) -> None: ... + + +class VideoWriter: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def fourcc(self, *args, **kwargs): ... # incomplete + def get(self, *args, **kwargs): ... # incomplete + def getBackendName(self, *args, **kwargs): ... # incomplete + def isOpened(self, *args, **kwargs): ... # incomplete + def open(self, *args, **kwargs): ... # incomplete + def release(self) -> None: ... + def set(self, *args, **kwargs): ... # incomplete + def write(self, image) -> None: ... + + +class WarperCreator: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class cuda_BufferPool: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def getAllocator(self, *args, **kwargs): ... # incomplete + def getBuffer(self, *args, **kwargs): ... # incomplete + + +class cuda_DeviceInfo: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def ECCEnabled(self, *args, **kwargs): ... # incomplete + def asyncEngineCount(self, *args, **kwargs): ... # incomplete + def canMapHostMemory(self, *args, **kwargs): ... # incomplete + def clockRate(self, *args, **kwargs): ... # incomplete + def computeMode(self, *args, **kwargs): ... # incomplete + def concurrentKernels(self, *args, **kwargs): ... # incomplete + def deviceID(self, *args, **kwargs): ... # incomplete + def freeMemory(self, *args, **kwargs): ... # incomplete + def integrated(self, *args, **kwargs): ... # incomplete + def isCompatible(self, *args, **kwargs): ... # incomplete + def kernelExecTimeoutEnabled(self, *args, **kwargs): ... # incomplete + def l2CacheSize(self, *args, **kwargs): ... # incomplete + def majorVersion(self, *args, **kwargs): ... # incomplete + def maxGridSize(self, *args, **kwargs): ... # incomplete + def maxSurface1D(self, *args, **kwargs): ... # incomplete + def maxSurface1DLayered(self, *args, **kwargs): ... # incomplete + def maxSurface2D(self, *args, **kwargs): ... # incomplete + def maxSurface2DLayered(self, *args, **kwargs): ... # incomplete + def maxSurface3D(self, *args, **kwargs): ... # incomplete + def maxSurfaceCubemap(self, *args, **kwargs): ... # incomplete + def maxSurfaceCubemapLayered(self, *args, **kwargs): ... # incomplete + def maxTexture1D(self, *args, **kwargs): ... # incomplete + def maxTexture1DLayered(self, *args, **kwargs): ... # incomplete + def maxTexture1DLinear(self, *args, **kwargs): ... # incomplete + def maxTexture1DMipmap(self, *args, **kwargs): ... # incomplete + def maxTexture2D(self, *args, **kwargs): ... # incomplete + def maxTexture2DGather(self, *args, **kwargs): ... # incomplete + def maxTexture2DLayered(self, *args, **kwargs): ... # incomplete + def maxTexture2DLinear(self, *args, **kwargs): ... # incomplete + def maxTexture2DMipmap(self, *args, **kwargs): ... # incomplete + def maxTexture3D(self, *args, **kwargs): ... # incomplete + def maxTextureCubemap(self, *args, **kwargs): ... # incomplete + def maxTextureCubemapLayered(self, *args, **kwargs): ... # incomplete + def maxThreadsDim(self, *args, **kwargs): ... # incomplete + def maxThreadsPerBlock(self, *args, **kwargs): ... # incomplete + def maxThreadsPerMultiProcessor(self, *args, **kwargs): ... # incomplete + def memPitch(self, *args, **kwargs): ... # incomplete + def memoryBusWidth(self, *args, **kwargs): ... # incomplete + def memoryClockRate(self, *args, **kwargs): ... # incomplete + def minorVersion(self, *args, **kwargs): ... # incomplete + def multiProcessorCount(self, *args, **kwargs): ... # incomplete + def pciBusID(self, *args, **kwargs): ... # incomplete + def pciDeviceID(self, *args, **kwargs): ... # incomplete + def pciDomainID(self, *args, **kwargs): ... # incomplete + def queryMemory(self, totalMemory, freeMemory) -> None: ... + def regsPerBlock(self, *args, **kwargs): ... # incomplete + def sharedMemPerBlock(self, *args, **kwargs): ... # incomplete + def surfaceAlignment(self, *args, **kwargs): ... # incomplete + def tccDriver(self, *args, **kwargs): ... # incomplete + def textureAlignment(self, *args, **kwargs): ... # incomplete + def texturePitchAlignment(self, *args, **kwargs): ... # incomplete + def totalConstMem(self, *args, **kwargs): ... # incomplete + def totalGlobalMem(self, *args, **kwargs): ... # incomplete + def totalMemory(self, *args, **kwargs): ... # incomplete + def unifiedAddressing(self, *args, **kwargs): ... # incomplete + def warpSize(self, *args, **kwargs): ... # incomplete + + +class cuda_Event: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def elapsedTime(self, *args, **kwargs): ... # incomplete + def queryIfComplete(self, *args, **kwargs): ... # incomplete + def record(self, *args, **kwargs): ... # incomplete + def waitForCompletion(self) -> None: ... + + +class cuda_GpuData: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class cuda_GpuMat: + step: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def adjustROI(self, *args, **kwargs): ... # incomplete + def assignTo(self, *args, **kwargs): ... # incomplete + def channels(self, *args, **kwargs): ... # incomplete + def clone(self, *args, **kwargs): ... # incomplete + def col(self, *args, **kwargs): ... # incomplete + def colRange(self, *args, **kwargs): ... # incomplete + def convertTo(self, *args, **kwargs): ... # incomplete + def copyTo(self, *args, **kwargs): ... # incomplete + @overload + def create(self, rows, cols, type) -> None: ... + @overload + def create(self, size, type) -> None: ... + def cudaPtr(self, *args, **kwargs): ... # incomplete + def defaultAllocator(self, *args, **kwargs): ... # incomplete + def depth(self, *args, **kwargs): ... # incomplete + def download(self, *args, **kwargs): ... # incomplete + def elemSize(self, *args, **kwargs): ... # incomplete + def elemSize1(self, *args, **kwargs): ... # incomplete + def empty(self, *args, **kwargs): ... # incomplete + def isContinuous(self, *args, **kwargs): ... # incomplete + def locateROI(self, wholeSize, ofs) -> None: ... + def reshape(self, *args, **kwargs): ... # incomplete + def row(self, *args, **kwargs): ... # incomplete + def rowRange(self, *args, **kwargs): ... # incomplete + def setDefaultAllocator(self, *args, **kwargs): ... # incomplete + def setTo(self, *args, **kwargs): ... # incomplete + def size(self, *args, **kwargs): ... # incomplete + def step1(self, *args, **kwargs): ... # incomplete + def swap(self, mat) -> None: ... + def type(self, *args, **kwargs): ... # incomplete + def updateContinuityFlag(self) -> None: ... + @overload + def upload(self, arr) -> None: ... + @overload + def upload(self, arr, stream) -> None: ... + + +class cuda_GpuMatND: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class cuda_GpuMat_Allocator: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class cuda_HostMem: + step: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def channels(self, *args, **kwargs): ... # incomplete + def clone(self, *args, **kwargs): ... # incomplete + def create(self, rows, cols, type) -> None: ... + def createMatHeader(self, *args, **kwargs): ... # incomplete + def depth(self, *args, **kwargs): ... # incomplete + def elemSize(self, *args, **kwargs): ... # incomplete + def elemSize1(self, *args, **kwargs): ... # incomplete + def empty(self, *args, **kwargs): ... # incomplete + def isContinuous(self, *args, **kwargs): ... # incomplete + def reshape(self, *args, **kwargs): ... # incomplete + def size(self, *args, **kwargs): ... # incomplete + def step1(self, *args, **kwargs): ... # incomplete + def swap(self, b) -> None: ... + def type(self, *args, **kwargs): ... # incomplete + + +class cuda_Stream: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + @staticmethod + def Null() -> cuda_Stream: ... + def cudaPtr(self, *args, **kwargs): ... # incomplete + def queryIfComplete(self, *args, **kwargs): ... # incomplete + def waitEvent(self, event) -> None: ... + def waitForCompletion(self) -> None: ... + + +class cuda_TargetArchs: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def has(self, *args, **kwargs): ... # incomplete + def hasBin(self, *args, **kwargs): ... # incomplete + def hasEqualOrGreater(self, *args, **kwargs): ... # incomplete + def hasEqualOrGreaterBin(self, *args, **kwargs): ... # incomplete + def hasEqualOrGreaterPtx(self, *args, **kwargs): ... # incomplete + def hasEqualOrLessPtx(self, *args, **kwargs): ... # incomplete + def hasPtx(self, *args, **kwargs): ... # incomplete + + +class detail_AffineBasedEstimator(detail_Estimator): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class detail_AffineBestOf2NearestMatcher(detail_BestOf2NearestMatcher): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class detail_BestOf2NearestMatcher(detail_FeaturesMatcher): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def collectGarbage(self) -> None: ... + def create(self, *args, **kwargs): ... # incomplete + + +class detail_BestOf2NearestRangeMatcher(detail_BestOf2NearestMatcher): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class detail_Blender: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def blend(self, *args, **kwargs): ... # incomplete + def createDefault(self, *args, **kwargs): ... # incomplete + def feed(self, img, mask, tl) -> None: ... + @overload + def prepare(self, corners, sizes) -> None: ... + @overload + def prepare(self, dst_roi) -> None: ... + + +class detail_BlocksChannelsCompensator(detail_BlocksCompensator): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class detail_BlocksCompensator(detail_ExposureCompensator): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def apply(self, index, corner, image, mask) -> _image: ... + def getBlockSize(self, *args, **kwargs): ... # incomplete + def getMatGains(self, *args, **kwargs): ... # incomplete + def getNrFeeds(self, *args, **kwargs): ... # incomplete + def getNrGainsFilteringIterations(self, *args, **kwargs): ... # incomplete + def getSimilarityThreshold(self, *args, **kwargs): ... # incomplete + @overload + def setBlockSize(self, width, height) -> None: ... + @overload + def setBlockSize(self, size) -> None: ... + def setMatGains(self, umv) -> None: ... + def setNrFeeds(self, nr_feeds) -> None: ... + def setNrGainsFilteringIterations(self, nr_iterations) -> None: ... + def setSimilarityThreshold(self, similarity_threshold) -> None: ... + + +class detail_BlocksGainCompensator(detail_BlocksCompensator): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def apply(self, index, corner, image, mask) -> _image: ... + def getMatGains(self, *args, **kwargs): ... # incomplete + def setMatGains(self, umv) -> None: ... + + +class detail_BundleAdjusterAffine(detail_BundleAdjusterBase): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class detail_BundleAdjusterAffinePartial(detail_BundleAdjusterBase): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class detail_BundleAdjusterBase(detail_Estimator): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def confThresh(self, *args, **kwargs): ... # incomplete + def refinementMask(self, *args, **kwargs): ... # incomplete + def setConfThresh(self, conf_thresh) -> None: ... + def setRefinementMask(self, mask) -> None: ... + def setTermCriteria(self, term_criteria) -> None: ... + def termCriteria(self, *args, **kwargs): ... # incomplete + + +class detail_BundleAdjusterRay(detail_BundleAdjusterBase): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class detail_BundleAdjusterReproj(detail_BundleAdjusterBase): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class detail_CameraParams: + R: Incomplete + aspect: Incomplete + focal: Incomplete + ppx: Incomplete + ppy: Incomplete + t: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def K(self, *args, **kwargs): ... # incomplete + + +class detail_ChannelsCompensator(detail_ExposureCompensator): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def apply(self, index, corner, image, mask) -> _image: ... + def getMatGains(self, *args, **kwargs): ... # incomplete + def getNrFeeds(self, *args, **kwargs): ... # incomplete + def getSimilarityThreshold(self, *args, **kwargs): ... # incomplete + def setMatGains(self, umv) -> None: ... + def setNrFeeds(self, nr_feeds) -> None: ... + def setSimilarityThreshold(self, similarity_threshold) -> None: ... + + +class detail_DpSeamFinder(detail_SeamFinder): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def setCostFunction(self, val) -> None: ... + + +class detail_Estimator: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def apply(self, *args, **kwargs): ... # incomplete + + +class detail_ExposureCompensator: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def apply(self, index, corner, image, mask) -> _image: ... + def createDefault(self, *args, **kwargs): ... # incomplete + def feed(self, corners, images, masks) -> None: ... + def getMatGains(self, *args, **kwargs): ... # incomplete + def getUpdateGain(self, *args, **kwargs): ... # incomplete + def setMatGains(self, arg1) -> None: ... + def setUpdateGain(self, b) -> None: ... + + +class detail_FeatherBlender(detail_Blender): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def blend(self, *args, **kwargs): ... # incomplete + def createWeightMaps(self, *args, **kwargs): ... # incomplete + def feed(self, img, mask, tl) -> None: ... + def prepare(self, dst_roi) -> None: ... # type: ignore[override] + def setSharpness(self, val) -> None: ... + def sharpness(self, *args, **kwargs): ... # incomplete + + +class detail_FeaturesMatcher: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def apply(self, features1, features2) -> _matches_info: ... + def apply2(self, *args, **kwargs): ... # incomplete + def collectGarbage(self) -> None: ... + def isThreadSafe(self, *args, **kwargs): ... # incomplete + + +class detail_GainCompensator(detail_ExposureCompensator): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def apply(self, index, corner, image, mask) -> _image: ... + def getMatGains(self, *args, **kwargs): ... # incomplete + def getNrFeeds(self, *args, **kwargs): ... # incomplete + def getSimilarityThreshold(self, *args, **kwargs): ... # incomplete + def setMatGains(self, umv) -> None: ... + def setNrFeeds(self, nr_feeds) -> None: ... + def setSimilarityThreshold(self, similarity_threshold) -> None: ... + + +class detail_GraphCutSeamFinder: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def find(self, src, corners, masks) -> None: ... + + +class detail_HomographyBasedEstimator(detail_Estimator): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class detail_ImageFeatures: + descriptors: Incomplete + img_idx: Incomplete + img_size: Incomplete + keypoints: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def getKeypoints(self, *args, **kwargs): ... # incomplete + + +class detail_MatchesInfo: + H: Incomplete + confidence: Incomplete + dst_img_idx: Incomplete + num_inliers: Incomplete + src_img_idx: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def getInliers(self, *args, **kwargs): ... # incomplete + def getMatches(self, *args, **kwargs): ... # incomplete + + +class detail_MultiBandBlender(detail_Blender): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def blend(self, *args, **kwargs): ... # incomplete + def feed(self, img, mask, tl) -> None: ... + def numBands(self, *args, **kwargs): ... # incomplete + def prepare(self, dst_roi) -> None: ... # type: ignore[override] + def setNumBands(self, val) -> None: ... + + +class detail_NoBundleAdjuster(detail_BundleAdjusterBase): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class detail_NoExposureCompensator(detail_ExposureCompensator): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def apply(self, arg1, arg2, arg3, arg4) -> _arg3: ... + def getMatGains(self, *args, **kwargs): ... # incomplete + def setMatGains(self, umv) -> None: ... + + +class detail_NoSeamFinder(detail_SeamFinder): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def find(self, arg1, arg2, arg3) -> _arg3: ... + + +class detail_PairwiseSeamFinder(detail_SeamFinder): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def find(self, src, corners, masks) -> _masks: ... + + +class detail_ProjectorBase: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class detail_SeamFinder: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def createDefault(self, *args, **kwargs): ... # incomplete + def find(self, src, corners, masks) -> _masks: ... + + +class detail_SphericalProjector(detail_ProjectorBase): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def mapBackward(self, u, v, x, y) -> None: ... + def mapForward(self, x, y, u, v) -> None: ... + + +class detail_Timelapser: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def createDefault(self, *args, **kwargs): ... # incomplete + def getDst(self, *args, **kwargs): ... # incomplete + def initialize(self, corners, sizes) -> None: ... + def process(self, img, mask, tl) -> None: ... + + +class detail_TimelapserCrop(detail_Timelapser): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class detail_VoronoiSeamFinder(detail_PairwiseSeamFinder): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def find(self, src, corners, masks) -> _masks: ... + + +class dnn_ClassificationModel(dnn_Model): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def classify(self, *args, **kwargs): ... # incomplete + + +class dnn_DetectionModel(dnn_Model): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def detect(self, *args, **kwargs): ... # incomplete + def getNmsAcrossClasses(self, *args, **kwargs): ... # incomplete + def setNmsAcrossClasses(self, *args, **kwargs): ... # incomplete + + +class dnn_DictValue: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def getIntValue(self, *args, **kwargs): ... # incomplete + def getRealValue(self, *args, **kwargs): ... # incomplete + def getStringValue(self, *args, **kwargs): ... # incomplete + def isInt(self, *args, **kwargs): ... # incomplete + def isReal(self, *args, **kwargs): ... # incomplete + def isString(self, *args, **kwargs): ... # incomplete + + +class dnn_KeypointsModel(dnn_Model): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def estimate(self, *args, **kwargs): ... # incomplete + + +class dnn_Layer(Algorithm): + blobs: Incomplete + name: Incomplete + preferableTarget: Incomplete + type: Incomplete + def finalize(self, *args, **kwargs): ... # incomplete + def outputNameToIndex(self, *args, **kwargs): ... # incomplete + def run(self, *args, **kwargs): ... # incomplete + + +class dnn_Model: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def predict(self, *args, **kwargs): ... # incomplete + def setInputCrop(self, *args, **kwargs): ... # incomplete + def setInputMean(self, *args, **kwargs): ... # incomplete + def setInputParams(self, *args, **kwargs): ... # incomplete + def setInputScale(self, *args, **kwargs): ... # incomplete + def setInputSize(self, *args, **kwargs): ... # incomplete + def setInputSwapRB(self, *args, **kwargs): ... # incomplete + def setPreferableBackend(self, *args, **kwargs): ... # incomplete + def setPreferableTarget(self, *args, **kwargs): ... # incomplete + + +class dnn_Net: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def connect(self, outPin, inpPin) -> None: ... + def dump(self, *args, **kwargs): ... # incomplete + def dumpToFile(self, path) -> None: ... + def empty(self, *args, **kwargs): ... # incomplete + def enableFusion(self, fusion) -> None: ... + def forward(self, *args, **kwargs): ... # incomplete + def forwardAndRetrieve(self, outBlobNames) -> _outputBlobs: ... + def forwardAsync(self, *args, **kwargs): ... # incomplete + def getFLOPS(self, *args, **kwargs): ... # incomplete + def getInputDetails(self, *args, **kwargs): ... # incomplete + def getLayer(self, *args, **kwargs): ... # incomplete + def getLayerId(self, *args, **kwargs): ... # incomplete + def getLayerNames(self, *args, **kwargs): ... # incomplete + def getLayerTypes(self) -> _layersTypes: ... + def getLayersCount(self, *args, **kwargs): ... # incomplete + def getLayersShapes(self, *args, **kwargs): ... # incomplete + def getMemoryConsumption(self, *args, **kwargs): ... # incomplete + def getOutputDetails(self, *args, **kwargs): ... # incomplete + def getParam(self, *args, **kwargs): ... # incomplete + def getPerfProfile(self, *args, **kwargs): ... # incomplete + def getUnconnectedOutLayers(self, *args, **kwargs): ... # incomplete + def getUnconnectedOutLayersNames(self, *args, **kwargs): ... # incomplete + def quantize(self, *args, **kwargs): ... # incomplete + def readFromModelOptimizer(self, *args, **kwargs): ... # incomplete + def setHalideScheduler(self, scheduler) -> None: ... + def setInput(self, *args, **kwargs): ... # incomplete + def setInputShape(self, inputName, shape) -> None: ... + def setInputsNames(self, inputBlobNames) -> None: ... + def setParam(self, layer, numParam, blob) -> None: ... + def setPreferableBackend(self, backendId) -> None: ... + def setPreferableTarget(self, targetId) -> None: ... + + +class dnn_SegmentationModel(dnn_Model): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def segment(self, *args, **kwargs): ... # incomplete + + +class dnn_TextDetectionModel(dnn_Model): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def detect(self, frame) -> _detections: ... + def detectTextRectangles(self, frame) -> _detections: ... + + +class dnn_TextDetectionModel_DB(dnn_TextDetectionModel): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def getBinaryThreshold(self, *args, **kwargs): ... # incomplete + def getMaxCandidates(self, *args, **kwargs): ... # incomplete + def getPolygonThreshold(self, *args, **kwargs): ... # incomplete + def getUnclipRatio(self, *args, **kwargs): ... # incomplete + def setBinaryThreshold(self, *args, **kwargs): ... # incomplete + def setMaxCandidates(self, *args, **kwargs): ... # incomplete + def setPolygonThreshold(self, *args, **kwargs): ... # incomplete + def setUnclipRatio(self, *args, **kwargs): ... # incomplete + + +class dnn_TextDetectionModel_EAST(dnn_TextDetectionModel): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def getConfidenceThreshold(self, *args, **kwargs): ... # incomplete + def getNMSThreshold(self, *args, **kwargs): ... # incomplete + def setConfidenceThreshold(self, *args, **kwargs): ... # incomplete + def setNMSThreshold(self, *args, **kwargs): ... # incomplete + + +class dnn_TextRecognitionModel(dnn_Model): + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def getDecodeType(self, *args, **kwargs): ... # incomplete + def getVocabulary(self, *args, **kwargs): ... # incomplete + def recognize(self, frame, roiRects) -> _results: ... + def setDecodeOptsCTCPrefixBeamSearch(self, *args, **kwargs): ... # incomplete + def setDecodeType(self, *args, **kwargs): ... # incomplete + def setVocabulary(self, *args, **kwargs): ... # incomplete + + +class error(Exception): + code: ClassVar[int] + err: ClassVar[str] + file: ClassVar[str] + func: ClassVar[str] + line: ClassVar[int] + msg: ClassVar[str] + + +class flann_Index: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def build(self, *args, **kwargs): ... # incomplete + def getAlgorithm(self, *args, **kwargs): ... # incomplete + def getDistance(self, *args, **kwargs): ... # incomplete + def knnSearch(self, *args, **kwargs): ... # incomplete + def load(self, *args, **kwargs): ... # incomplete + def radiusSearch(self, *args, **kwargs): ... # incomplete + def release(self) -> None: ... + def save(self, filename) -> None: ... + + +class gapi_GKernelPackage: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class gapi_GNetPackage: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class gapi_GNetParam: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class gapi_ie_PyParams: + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, tag: str, model: str, device: str) -> None: ... + @overload + def __init__(self, tag: str, model: str, weights: str, device: str) -> None: ... + def cfgBatchSize(self, size): ... + def cfgNumRequests(self, nireq): ... + def constInput(self, layer_name, data, hint=...): ... + + +class gapi_streaming_queue_capacity: + capacity: int + def __init__(self, cap: int = ...) -> None: ... + + +class gapi_wip_GOutputs: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def getGArray(self, *args, **kwargs): ... # incomplete + def getGMat(self, *args, **kwargs): ... # incomplete + def getGOpaque(self, *args, **kwargs): ... # incomplete + def getGScalar(self, *args, **kwargs): ... # incomplete + + +class gapi_wip_IStreamSource: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class gapi_wip_draw_Circle: + center: Incomplete + color: Incomplete + lt: Incomplete + radius: Incomplete + shift: Incomplete + thick: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class gapi_wip_draw_Image: + alpha: Incomplete + img: Incomplete + org: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class gapi_wip_draw_Line: + color: Incomplete + lt: Incomplete + pt1: Incomplete + pt2: Incomplete + shift: Incomplete + thick: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class gapi_wip_draw_Mosaic: + cellSz: Incomplete + decim: Incomplete + mos: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class gapi_wip_draw_Poly: + color: Incomplete + lt: Incomplete + points: Incomplete + shift: Incomplete + thick: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class gapi_wip_draw_Rect: + color: Incomplete + lt: Incomplete + rect: Incomplete + shift: Incomplete + thick: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class gapi_wip_draw_Text: + bottom_left_origin: bool + color: tuple[float, float, float, float] + ff: int + fs: float + lt: int + org: _Point + text: str + thick: int + def __init__(self, text_: str, org_: _Point, ff_: int, fs_: float, color_: _Scalar) -> None: ... + + +class ml_ANN_MLP(ml_StatModel): + def create(self, *args, **kwargs): ... # incomplete + def getAnnealCoolingRatio(self, *args, **kwargs): ... # incomplete + def getAnnealFinalT(self, *args, **kwargs): ... # incomplete + def getAnnealInitialT(self, *args, **kwargs): ... # incomplete + def getAnnealItePerStep(self, *args, **kwargs): ... # incomplete + def getBackpropMomentumScale(self, *args, **kwargs): ... # incomplete + def getBackpropWeightScale(self, *args, **kwargs): ... # incomplete + def getLayerSizes(self, *args, **kwargs): ... # incomplete + def getRpropDW0(self, *args, **kwargs): ... # incomplete + def getRpropDWMax(self, *args, **kwargs): ... # incomplete + def getRpropDWMin(self, *args, **kwargs): ... # incomplete + def getRpropDWMinus(self, *args, **kwargs): ... # incomplete + def getRpropDWPlus(self, *args, **kwargs): ... # incomplete + def getTermCriteria(self, *args, **kwargs): ... # incomplete + def getTrainMethod(self, *args, **kwargs): ... # incomplete + def getWeights(self, *args, **kwargs): ... # incomplete + def load(self, *args, **kwargs): ... # incomplete + def setActivationFunction(self, *args, **kwargs): ... # incomplete + def setAnnealCoolingRatio(self, val) -> None: ... + def setAnnealFinalT(self, val) -> None: ... + def setAnnealInitialT(self, val) -> None: ... + def setAnnealItePerStep(self, val) -> None: ... + def setBackpropMomentumScale(self, val) -> None: ... + def setBackpropWeightScale(self, val) -> None: ... + def setLayerSizes(self, _layer_sizes) -> None: ... + def setRpropDW0(self, val) -> None: ... + def setRpropDWMax(self, val) -> None: ... + def setRpropDWMin(self, val) -> None: ... + def setRpropDWMinus(self, val) -> None: ... + def setRpropDWPlus(self, val) -> None: ... + def setTermCriteria(self, val) -> None: ... + def setTrainMethod(self, *args, **kwargs): ... # incomplete + + +class ml_Boost(ml_DTrees): + def create(self, *args, **kwargs): ... # incomplete + def getBoostType(self, *args, **kwargs): ... # incomplete + def getWeakCount(self, *args, **kwargs): ... # incomplete + def getWeightTrimRate(self, *args, **kwargs): ... # incomplete + def load(self, *args, **kwargs): ... # incomplete + def setBoostType(self, val) -> None: ... + def setWeakCount(self, val) -> None: ... + def setWeightTrimRate(self, val) -> None: ... + + +class ml_DTrees(ml_StatModel): + def create(self, *args, **kwargs): ... # incomplete + def getCVFolds(self, *args, **kwargs): ... # incomplete + def getMaxCategories(self, *args, **kwargs): ... # incomplete + def getMaxDepth(self, *args, **kwargs): ... # incomplete + def getMinSampleCount(self, *args, **kwargs): ... # incomplete + def getPriors(self, *args, **kwargs): ... # incomplete + def getRegressionAccuracy(self, *args, **kwargs): ... # incomplete + def getTruncatePrunedTree(self, *args, **kwargs): ... # incomplete + def getUse1SERule(self, *args, **kwargs): ... # incomplete + def getUseSurrogates(self, *args, **kwargs): ... # incomplete + def load(self, *args, **kwargs): ... # incomplete + def setCVFolds(self, val) -> None: ... + def setMaxCategories(self, val) -> None: ... + def setMaxDepth(self, val) -> None: ... + def setMinSampleCount(self, val) -> None: ... + def setPriors(self, val) -> None: ... + def setRegressionAccuracy(self, val) -> None: ... + def setTruncatePrunedTree(self, val) -> None: ... + def setUse1SERule(self, val) -> None: ... + def setUseSurrogates(self, val) -> None: ... + + +class ml_EM(ml_StatModel): + def create(self, *args, **kwargs): ... # incomplete + def getClustersNumber(self, *args, **kwargs): ... # incomplete + def getCovarianceMatrixType(self, *args, **kwargs): ... # incomplete + def getCovs(self, *args, **kwargs): ... # incomplete + def getMeans(self, *args, **kwargs): ... # incomplete + def getTermCriteria(self, *args, **kwargs): ... # incomplete + def getWeights(self, *args, **kwargs): ... # incomplete + def load(self, *args, **kwargs): ... # incomplete + def predict(self, *args, **kwargs): ... # incomplete + def predict2(self, *args, **kwargs): ... # incomplete + def setClustersNumber(self, val) -> None: ... + def setCovarianceMatrixType(self, val) -> None: ... + def setTermCriteria(self, val) -> None: ... + def trainE(self, *args, **kwargs): ... # incomplete + def trainEM(self, *args, **kwargs): ... # incomplete + def trainM(self, *args, **kwargs): ... # incomplete + + +class ml_KNearest(ml_StatModel): + def create(self, *args, **kwargs): ... # incomplete + def findNearest(self, *args, **kwargs): ... # incomplete + def getAlgorithmType(self, *args, **kwargs): ... # incomplete + def getDefaultK(self, *args, **kwargs): ... # incomplete + def getEmax(self, *args, **kwargs): ... # incomplete + def getIsClassifier(self, *args, **kwargs): ... # incomplete + def load(self, *args, **kwargs): ... # incomplete + def setAlgorithmType(self, val) -> None: ... + def setDefaultK(self, val) -> None: ... + def setEmax(self, val) -> None: ... + def setIsClassifier(self, val) -> None: ... + + +class ml_LogisticRegression(ml_StatModel): + def create(self, *args, **kwargs): ... # incomplete + def getIterations(self, *args, **kwargs): ... # incomplete + def getLearningRate(self, *args, **kwargs): ... # incomplete + def getMiniBatchSize(self, *args, **kwargs): ... # incomplete + def getRegularization(self, *args, **kwargs): ... # incomplete + def getTermCriteria(self, *args, **kwargs): ... # incomplete + def getTrainMethod(self, *args, **kwargs): ... # incomplete + def get_learnt_thetas(self, *args, **kwargs): ... # incomplete + def load(self, *args, **kwargs): ... # incomplete + def predict(self, *args, **kwargs): ... # incomplete + def setIterations(self, val) -> None: ... + def setLearningRate(self, val) -> None: ... + def setMiniBatchSize(self, val) -> None: ... + def setRegularization(self, val) -> None: ... + def setTermCriteria(self, val) -> None: ... + def setTrainMethod(self, val) -> None: ... + + +class ml_NormalBayesClassifier(ml_StatModel): + def create(self, *args, **kwargs): ... # incomplete + def load(self, *args, **kwargs): ... # incomplete + def predictProb(self, *args, **kwargs): ... # incomplete + + +class ml_ParamGrid: + logStep: Incomplete + maxVal: Incomplete + minVal: Incomplete + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + + +class ml_RTrees(ml_DTrees): + def create(self, *args, **kwargs): ... # incomplete + def getActiveVarCount(self, *args, **kwargs): ... # incomplete + def getCalculateVarImportance(self, *args, **kwargs): ... # incomplete + def getOOBError(self, *args, **kwargs): ... # incomplete + def getTermCriteria(self, *args, **kwargs): ... # incomplete + def getVarImportance(self, *args, **kwargs): ... # incomplete + def getVotes(self, *args, **kwargs): ... # incomplete + def load(self, *args, **kwargs): ... # incomplete + def setActiveVarCount(self, val) -> None: ... + def setCalculateVarImportance(self, val) -> None: ... + def setTermCriteria(self, val) -> None: ... + + +class ml_SVM(ml_StatModel): + def create(self, *args, **kwargs): ... # incomplete + def getC(self, *args, **kwargs): ... # incomplete + def getClassWeights(self, *args, **kwargs): ... # incomplete + def getCoef0(self, *args, **kwargs): ... # incomplete + def getDecisionFunction(self, *args, **kwargs): ... # incomplete + def getDefaultGridPtr(self, *args, **kwargs): ... # incomplete + def getDegree(self, *args, **kwargs): ... # incomplete + def getGamma(self, *args, **kwargs): ... # incomplete + def getKernelType(self, *args, **kwargs): ... # incomplete + def getNu(self, *args, **kwargs): ... # incomplete + def getP(self, *args, **kwargs): ... # incomplete + def getSupportVectors(self, *args, **kwargs): ... # incomplete + def getTermCriteria(self, *args, **kwargs): ... # incomplete + def getType(self, *args, **kwargs): ... # incomplete + def getUncompressedSupportVectors(self, *args, **kwargs): ... # incomplete + def load(self, *args, **kwargs): ... # incomplete + def setC(self, val) -> None: ... + def setClassWeights(self, val) -> None: ... + def setCoef0(self, val) -> None: ... + def setDegree(self, val) -> None: ... + def setGamma(self, val) -> None: ... + def setKernel(self, kernelType) -> None: ... + def setNu(self, val) -> None: ... + def setP(self, val) -> None: ... + def setTermCriteria(self, val) -> None: ... + def setType(self, val) -> None: ... + def trainAuto(self, *args, **kwargs): ... # incomplete + + +class ml_SVMSGD(ml_StatModel): + def create(self, *args, **kwargs): ... # incomplete + def getInitialStepSize(self, *args, **kwargs): ... # incomplete + def getMarginRegularization(self, *args, **kwargs): ... # incomplete + def getMarginType(self, *args, **kwargs): ... # incomplete + def getShift(self, *args, **kwargs): ... # incomplete + def getStepDecreasingPower(self, *args, **kwargs): ... # incomplete + def getSvmsgdType(self, *args, **kwargs): ... # incomplete + def getTermCriteria(self, *args, **kwargs): ... # incomplete + def getWeights(self, *args, **kwargs): ... # incomplete + def load(self, *args, **kwargs): ... # incomplete + def setInitialStepSize(self, InitialStepSize) -> None: ... + def setMarginRegularization(self, marginRegularization) -> None: ... + def setMarginType(self, marginType) -> None: ... + def setOptimalParameters(self, *args, **kwargs): ... # incomplete + def setStepDecreasingPower(self, stepDecreasingPower) -> None: ... + def setSvmsgdType(self, svmsgdType) -> None: ... + def setTermCriteria(self, val) -> None: ... + + +class ml_StatModel(Algorithm): + def calcError(self, *args, **kwargs): ... # incomplete + def empty(self, *args, **kwargs): ... # incomplete + def getVarCount(self, *args, **kwargs): ... # incomplete + def isClassifier(self, *args, **kwargs): ... # incomplete + def isTrained(self, *args, **kwargs): ... # incomplete + def predict(self, *args, **kwargs): ... # incomplete + def train(self, *args, **kwargs): ... # incomplete + + +class ml_TrainData: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def create(self, *args, **kwargs): ... # incomplete + def getCatCount(self, *args, **kwargs): ... # incomplete + def getCatMap(self, *args, **kwargs): ... # incomplete + def getCatOfs(self, *args, **kwargs): ... # incomplete + def getClassLabels(self, *args, **kwargs): ... # incomplete + def getDefaultSubstValues(self, *args, **kwargs): ... # incomplete + def getLayout(self, *args, **kwargs): ... # incomplete + def getMissing(self, *args, **kwargs): ... # incomplete + def getNAllVars(self, *args, **kwargs): ... # incomplete + def getNSamples(self, *args, **kwargs): ... # incomplete + def getNTestSamples(self, *args, **kwargs): ... # incomplete + def getNTrainSamples(self, *args, **kwargs): ... # incomplete + def getNVars(self, *args, **kwargs): ... # incomplete + def getNames(self, names) -> None: ... + def getNormCatResponses(self, *args, **kwargs): ... # incomplete + def getResponseType(self, *args, **kwargs): ... # incomplete + def getResponses(self, *args, **kwargs): ... # incomplete + def getSample(self, varIdx, sidx, buf) -> None: ... + def getSampleWeights(self, *args, **kwargs): ... # incomplete + def getSamples(self, *args, **kwargs): ... # incomplete + def getSubMatrix(self, *args, **kwargs): ... # incomplete + def getSubVector(self, *args, **kwargs): ... # incomplete + def getTestNormCatResponses(self, *args, **kwargs): ... # incomplete + def getTestResponses(self, *args, **kwargs): ... # incomplete + def getTestSampleIdx(self, *args, **kwargs): ... # incomplete + def getTestSampleWeights(self, *args, **kwargs): ... # incomplete + def getTestSamples(self, *args, **kwargs): ... # incomplete + def getTrainNormCatResponses(self, *args, **kwargs): ... # incomplete + def getTrainResponses(self, *args, **kwargs): ... # incomplete + def getTrainSampleIdx(self, *args, **kwargs): ... # incomplete + def getTrainSampleWeights(self, *args, **kwargs): ... # incomplete + def getTrainSamples(self, *args, **kwargs): ... # incomplete + def getValues(self, vi, sidx, values) -> None: ... + def getVarIdx(self, *args, **kwargs): ... # incomplete + def getVarSymbolFlags(self, *args, **kwargs): ... # incomplete + def getVarType(self, *args, **kwargs): ... # incomplete + def setTrainTestSplit(self, *args, **kwargs): ... # incomplete + def setTrainTestSplitRatio(self, *args, **kwargs): ... # incomplete + def shuffleTrainTest(self) -> None: ... + + +class ocl_Device: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def OpenCLVersion(self, *args, **kwargs): ... # incomplete + def OpenCL_C_Version(self, *args, **kwargs): ... # incomplete + def addressBits(self, *args, **kwargs): ... # incomplete + def available(self, *args, **kwargs): ... # incomplete + def compilerAvailable(self, *args, **kwargs): ... # incomplete + def deviceVersionMajor(self, *args, **kwargs): ... # incomplete + def deviceVersionMinor(self, *args, **kwargs): ... # incomplete + def doubleFPConfig(self, *args, **kwargs): ... # incomplete + def driverVersion(self, *args, **kwargs): ... # incomplete + def endianLittle(self, *args, **kwargs): ... # incomplete + def errorCorrectionSupport(self, *args, **kwargs): ... # incomplete + def executionCapabilities(self, *args, **kwargs): ... # incomplete + def extensions(self, *args, **kwargs): ... # incomplete + def getDefault(self, *args, **kwargs): ... # incomplete + def globalMemCacheLineSize(self, *args, **kwargs): ... # incomplete + def globalMemCacheSize(self, *args, **kwargs): ... # incomplete + def globalMemCacheType(self, *args, **kwargs): ... # incomplete + def globalMemSize(self, *args, **kwargs): ... # incomplete + def halfFPConfig(self, *args, **kwargs): ... # incomplete + def hostUnifiedMemory(self, *args, **kwargs): ... # incomplete + def image2DMaxHeight(self, *args, **kwargs): ... # incomplete + def image2DMaxWidth(self, *args, **kwargs): ... # incomplete + def image3DMaxDepth(self, *args, **kwargs): ... # incomplete + def image3DMaxHeight(self, *args, **kwargs): ... # incomplete + def image3DMaxWidth(self, *args, **kwargs): ... # incomplete + def imageFromBufferSupport(self, *args, **kwargs): ... # incomplete + def imageMaxArraySize(self, *args, **kwargs): ... # incomplete + def imageMaxBufferSize(self, *args, **kwargs): ... # incomplete + def imageSupport(self, *args, **kwargs): ... # incomplete + def intelSubgroupsSupport(self, *args, **kwargs): ... # incomplete + def isAMD(self, *args, **kwargs): ... # incomplete + def isExtensionSupported(self, *args, **kwargs): ... # incomplete + def isIntel(self, *args, **kwargs): ... # incomplete + def isNVidia(self, *args, **kwargs): ... # incomplete + def linkerAvailable(self, *args, **kwargs): ... # incomplete + def localMemSize(self, *args, **kwargs): ... # incomplete + def localMemType(self, *args, **kwargs): ... # incomplete + def maxClockFrequency(self, *args, **kwargs): ... # incomplete + def maxComputeUnits(self, *args, **kwargs): ... # incomplete + def maxConstantArgs(self, *args, **kwargs): ... # incomplete + def maxConstantBufferSize(self, *args, **kwargs): ... # incomplete + def maxMemAllocSize(self, *args, **kwargs): ... # incomplete + def maxParameterSize(self, *args, **kwargs): ... # incomplete + def maxReadImageArgs(self, *args, **kwargs): ... # incomplete + def maxSamplers(self, *args, **kwargs): ... # incomplete + def maxWorkGroupSize(self, *args, **kwargs): ... # incomplete + def maxWorkItemDims(self, *args, **kwargs): ... # incomplete + def maxWriteImageArgs(self, *args, **kwargs): ... # incomplete + def memBaseAddrAlign(self, *args, **kwargs): ... # incomplete + def name(self, *args, **kwargs): ... # incomplete + def nativeVectorWidthChar(self, *args, **kwargs): ... # incomplete + def nativeVectorWidthDouble(self, *args, **kwargs): ... # incomplete + def nativeVectorWidthFloat(self, *args, **kwargs): ... # incomplete + def nativeVectorWidthHalf(self, *args, **kwargs): ... # incomplete + def nativeVectorWidthInt(self, *args, **kwargs): ... # incomplete + def nativeVectorWidthLong(self, *args, **kwargs): ... # incomplete + def nativeVectorWidthShort(self, *args, **kwargs): ... # incomplete + def preferredVectorWidthChar(self, *args, **kwargs): ... # incomplete + def preferredVectorWidthDouble(self, *args, **kwargs): ... # incomplete + def preferredVectorWidthFloat(self, *args, **kwargs): ... # incomplete + def preferredVectorWidthHalf(self, *args, **kwargs): ... # incomplete + def preferredVectorWidthInt(self, *args, **kwargs): ... # incomplete + def preferredVectorWidthLong(self, *args, **kwargs): ... # incomplete + def preferredVectorWidthShort(self, *args, **kwargs): ... # incomplete + def printfBufferSize(self, *args, **kwargs): ... # incomplete + def profilingTimerResolution(self, *args, **kwargs): ... # incomplete + def singleFPConfig(self, *args, **kwargs): ... # incomplete + def type(self, *args, **kwargs): ... # incomplete + def vendorID(self, *args, **kwargs): ... # incomplete + def vendorName(self, *args, **kwargs): ... # incomplete + def version(self, *args, **kwargs): ... # incomplete + + +class ocl_OpenCLExecutionContext: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + + +class segmentation_IntelligentScissorsMB: + def __init__(self, *args, **kwargs) -> None: ... # incomplete + def applyImage(self, *args, **kwargs): ... # incomplete + def applyImageFeatures(self, *args, **kwargs): ... # incomplete + def buildMap(self, sourcePt) -> None: ... + def getContour(self, *args, **kwargs): ... # incomplete + def setEdgeFeatureCannyParameters(self, *args, **kwargs): ... # incomplete + def setEdgeFeatureZeroCrossingParameters(self, *args, **kwargs): ... # incomplete + def setGradientMagnitudeMaxLimit(self, *args, **kwargs): ... # incomplete + def setWeights(self, *args, **kwargs): ... # incomplete + + +def AKAZE_create( + descriptor_type=..., + descriptor_size=..., + descriptor_channels=..., + threshold=..., + nOctaves=..., + nOctaveLayers=..., + diffusivity=..., +): ... +def AffineFeature_create(*args, **kwargs): ... # incomplete +def AgastFeatureDetector_create(threshold=..., nonmaxSuppression=..., type=...): ... +def BFMatcher_create(normType: int = ..., crossCheck=...): ... +@overload +def BRISK_create(thresh=..., octaves=..., patternScale=...): ... +@overload +def BRISK_create(radiusList, numberList, dMax=..., dMin=..., indexChange=...): ... +@overload +def BRISK_create(thresh, octaves, radiusList, numberList, dMax=..., dMin=..., indexChange=...): ... +def CamShift(probImage, window, criteria) -> tuple[_RotatedRectResult, _window]: ... +@overload +def Canny(image: Mat, threshold1, threshold2, edges=..., apertureSize=..., L2gradient=...) -> _edges: ... +@overload +def Canny(dx, dy, threshold1, threshold2, edges=..., L2gradient=...) -> _edges: ... +def CascadeClassifier_convert(oldcascade, newcascade): ... +def DISOpticalFlow_create(preset=...): ... +@overload +def DescriptorMatcher_create(descriptorMatcherType: str) -> DescriptorMatcher: ... +@overload +def DescriptorMatcher_create(matcherType: int) -> DescriptorMatcher: ... + + +def EMD( + signature1, + signature2, + distType, + cost=..., + lowerBound=..., + flow=..., +) -> tuple[ + tuple[ + Incomplete, + _lowerBound, + _flow, + ] +]: ... + + +def FaceDetectorYN_create(*args, **kwargs): ... # incomplete +def FaceRecognizerSF_create(*args, **kwargs): ... # incomplete + + +def FarnebackOpticalFlow_create( + numLevels=..., + pyrScale=..., + fastPyramids=..., + winSize=..., + numIters=..., + polyN=..., + polySigma=..., + flags: int | None = ..., +): ... + + +def FastFeatureDetector_create(threshold=..., nonmaxSuppression=..., type=...): ... +def FlannBasedMatcher_create(): ... + + +@overload +def GFTTDetector_create( + maxCorners=..., + qualityLevel=..., + minDistance=..., + blockSize=..., + useHarrisDetector=..., + k=..., +): ... + + +@overload +def GFTTDetector_create( + maxCorners, + qualityLevel, + minDistance, + blockSize, + gradiantSize, + useHarrisDetector=..., + k=..., +): ... + + +def GaussianBlur(src: Mat, ksize, sigmaX, dst: Mat = ..., sigmaY=..., borderType=...) -> _dst: ... +def HOGDescriptor_getDaimlerPeopleDetector(): ... +def HOGDescriptor_getDefaultPeopleDetector(): ... + + +def HoughCircles( + image: Mat, method: int, dp, minDist, circles=..., param1=..., param2=..., minRadius=..., maxRadius=..., +) -> _circles: ... + + +def HoughLines( + image: Mat, rho, theta, threshold, lines=..., srn=..., + stn=..., min_theta=..., max_theta=..., +) -> _lines: ... + + +def HoughLinesP(image: Mat, rho, theta, threshold, lines=..., minLineLength=..., maxLineGap=...) -> _lines: ... + + +def HoughLinesPointSet( + _point, lines_max, threshold, min_rho, max_rho, rho_step, min_theta, max_theta, theta_step, _lines=..., +) -> _lines: ... +def HoughLinesWithAccumulator(*args, **kwargs): ... # incomplete +def HuMoments(m, hu=...) -> _hu: ... +def KAZE_create(extended=..., upright=..., threshold=..., nOctaves=..., nOctaveLayers=..., diffusivity=...): ... +@overload +def KeyPoint_convert(keypoints, keypointIndexes=...) -> _points2f: ... +@overload +def KeyPoint_convert(points2f, size=..., response=..., octave=..., class_id=...) -> _keypoints: ... +def KeyPoint_overlap(kp1, kp2): ... +def LUT(src: Mat, lut, dst: Mat = ...) -> _dst: ... +def Laplacian(src: Mat, ddepth, dst: Mat = ..., ksize=..., scale=..., delta=..., borderType=...) -> _dst: ... + + +def MSER_create( + _delta=..., + _min_area=..., + _max_area=..., + _max_variation=..., + _min_diversity=..., + _max_evolution=..., + _area_threshold=..., + _min_margin=..., + _edge_blur_size=..., +): ... +def Mahalanobis(v1, v2, icovar): ... + + +def ORB_create( + nfeatures=..., + scaleFactor=..., + nlevels=..., + edgeThreshold=..., + firstLevel=..., + WTA_K=..., + scoreType=..., + patchSize=..., + fastThreshold=..., +): ... +def PCABackProject(data, mean, eigenvectors, result=...): ... +@overload +def PCACompute(data, mean, eigenvectors=..., maxComponents=...) -> tuple[tuple[_mean, _eigenvectors]]: ... +@overload +def PCACompute(data, mean, retainedVariance, eigenvectors=...) -> tuple[tuple[_mean, _eigenvectors]]: ... + + +@overload +def PCACompute2( + data, mean, eigenvectors=..., eigenvalues=..., maxComponents=..., +) -> tuple[tuple[_mean, _eigenvectors, _eigenvalues]]: ... + + +@overload +def PCACompute2( + data, mean, retainedVariance, eigenvectors=..., eigenvalues=..., +) -> tuple[tuple[_mean, _eigenvectors, _eigenvalues]]: ... +def PCAProject(data, mean, eigenvectors, result=...) -> _result: ... +def PSNR(src1: Mat, src2: Mat, R=...): ... +def QRCodeEncoder_create(*args, **kwargs): ... # incomplete + + +def RQDecomp3x3( + src: Mat, mtxR=..., mtxQ=..., Qx=..., Qy=..., Qz=..., +) -> tuple[tuple[Incomplete, _mtxR, _mtxQ, _Qx, _Qy, _Qz]]: ... +def Rodrigues(src: Mat, dst: Mat = ..., jacobian=...) -> tuple[tuple[_dst, _jacobian]]: ... +def SIFT_create(nfeatures=..., nOctaveLayers=..., contrastThreshold=..., edgeThreshold=..., sigma=...): ... +def SVBackSubst(w, u, vt, rhs, dst: Mat = ...) -> _dst: ... +def SVDecomp(src: Mat, w=..., u=..., vt=..., flags: int | None = ...) -> tuple[tuple[_w, _u, _vt]]: ... +def Scharr(src: Mat, ddepth, dx, dy, dst: Mat = ..., scale=..., delta=..., borderType=...) -> _dst: ... +def SimpleBlobDetector_create(parameters=...): ... +def Sobel(src: Mat, ddepth, dx, dy, dst: Mat = ..., ksize=..., scale=..., delta=..., borderType=...) -> _dst: ... + + +def SparsePyrLKOpticalFlow_create( + winSize=..., + maxLevel=..., + crit=..., + flags: int | None = ..., + minEigThreshold=..., +): ... + + +def StereoBM_create(numDisparities=..., blockSize=...): ... + + +def StereoSGBM_create( + minDisparity=..., + numDisparities=..., + blockSize=..., + P1=..., + P2=..., + disp12MaxDiff=..., + preFilterCap=..., + uniquenessRatio=..., + speckleWindowSize=..., + speckleRange=..., + mode=..., +): ... +def Stitcher_create(mode=...): ... +def TrackerDaSiamRPN_create(*args, **kwargs): ... # incomplete +def TrackerGOTURN_create(*args, **kwargs): ... # incomplete +def TrackerMIL_create(*args, **kwargs): ... # incomplete +def UMat_context(): ... +def UMat_queue(): ... +def VariationalRefinement_create(): ... +def VideoWriter_fourcc(c1, c2, c3, c4): ... +def _registerMatType(*args, **kwargs): ... # incomplete +def absdiff(src1: Mat, src2: Mat, dst: Mat = ...) -> _dst: ... +def accumulate(src: Mat, dst: Mat, mask: Mat = ...) -> _dst: ... +def accumulateProduct(src1: Mat, src2: Mat, dst: Mat, mask: Mat = ...) -> _dst: ... +def accumulateSquare(src: Mat, dst: Mat, mask: Mat = ...) -> _dst: ... +def accumulateWeighted(src: Mat, dst: Mat, alpha, mask: Mat = ...) -> _dst: ... +def adaptiveThreshold(src: Mat, maxValue, adaptiveMethod, thresholdType, blockSize, C, dst: Mat = ...) -> _dst: ... +def add(src1: Mat | _NumericScalar, src2: Mat | _NumericScalar, dst: Mat = ..., mask: Mat = ..., dtype=...) -> _dst: ... +def addText(img: Mat, text, org, nameFont, pointSize=..., color=..., weight=..., style=..., spacing=...) -> None: ... +def addWeighted(src1: Mat, alpha, src2: Mat, beta, gamma, dst: Mat = ..., dtype=...) -> _dst: ... +@overload +def applyColorMap(src: Mat, colormap, dst: Mat = ...) -> _dst: ... +@overload +def applyColorMap(src, userColor, dst=...) -> _dst: ... +def approxPolyDP(curve, epsilon, closed, approxCurve=...) -> _approxCurve: ... +def arcLength(curve, closed): ... +def arrowedLine(img: Mat, pt1, pt2, color, thickness=..., line_type=..., shift=..., tipLength=...) -> _img: ... + + +def batchDistance( + src1: Mat, + src2: Mat, + dtype, + dist=..., + nidx=..., + normType: int = ..., + K=..., + mask: Mat = ..., + update=..., + crosscheck=..., +) -> tuple[ + _dist, + _nidx, +]: ... + + +def bilateralFilter(src: Mat, d, sigmaColor, sigmaSpace, dst: Mat = ..., borderType=...) -> _dst: ... +def bitwise_and(src1: Mat, src2: Mat, dst: Mat = ..., mask: Mat = ...) -> _dst: ... +def bitwise_not(src: Mat, dst: Mat = ..., mask: Mat = ...) -> _dst: ... +def bitwise_or(src1: Mat, src2: Mat, dst: Mat = ..., mask: Mat = ...) -> _dst: ... +def bitwise_xor(src1: Mat, src2: Mat, dst: Mat = ..., mask: Mat = ...) -> _dst: ... +def blendLinear(*args, **kwargs): ... # incomplete +def blur(src: Mat, ksize, dst: Mat = ..., anchor=..., borderType=...) -> _dst: ... +def borderInterpolate(p, len, borderType): ... +def boundingRect(array): ... +def boxFilter(src: Mat, ddepth, ksize, dst: Mat = ..., anchor=..., normalize=..., borderType=...) -> _dst: ... +def boxPoints(box, points=...) -> _points: ... + + +def buildOpticalFlowPyramid( + img: Mat, + winSize, + maxLevel, + pyramid=..., + withDerivatives=..., + pyrBorder=..., + derivBorder=..., + tryReuseInputImage=..., +) -> tuple[ + Incomplete, + _pyramid, +]: ... + + +def calcBackProject( + images: Sequence[Mat], channels: Sequence[int], hist, ranges: Sequence[int], scale, dst: Mat = ..., +) -> _dst: ... +def calcCovarMatrix(samples, mean, flags: int | None, covar=..., ctype=...) -> tuple[_covar, _mean]: ... + + +def calcHist( + images: Sequence[Mat], + channels: Sequence[int], + mask: Mat | None, + histSize: Sequence[int], + ranges: Sequence[int], + hist: Mat = ..., + accumulate=..., +) -> Mat: ... + + +def calcOpticalFlowFarneback( + prev, next, flow, pyr_scale, levels, winsize, iterations, poly_n, poly_sigma, flags: int | None, +) -> _flow: ... + + +def calcOpticalFlowPyrLK( + prevImg, + nextImg, + prevPts, + nextPts, + status=..., + err=..., + winSize=..., + maxLevel=..., + criteria=..., + flags: int | None = ..., + minEigThreshold=..., +) -> tuple[_nextPts, _status, _err]: ... + + +def calibrateCamera( + objectPoints, + imagePoints, + imageSize, + cameraMatrix, + distCoeffs, + rvecs=..., + tvecs=..., + flags: int | None = ..., + criteria=..., +) -> tuple[ + Incomplete, + _cameraMatrix, + _distCoeffs, + _rvecs, + _tvecs, +]: ... + + +def calibrateCameraExtended( + objectPoints, + imagePoints, + imageSize, + cameraMatrix, + distCoeffs, + rvecs=..., + tvecs=..., + stdDeviationsIntrinsics=..., + stdDeviationsExtrinsics=..., + perViewErrors=..., + flags: int | None = ..., + criteria=..., +) -> tuple[ + Incomplete, + _cameraMatrix, + _distCoeffs, + _rvecs, + _tvecs, + _stdDeviationsIntrinsics, + _stdDeviationsExtrinsics, + _perViewErrors, +]: ... + + +def calibrateCameraRO( + objectPoints, + imagePoints, + imageSize, + iFixedPoint, + cameraMatrix, + distCoeffs, + rvecs=..., + tvecs=..., + newObjPoints=..., + flags: int | None = ..., + criteria=..., +) -> tuple[Incomplete, _cameraMatrix, _distCoeffs, _rvecs, _tvecs, _newObjPoints]: ... + + +def calibrateCameraROExtended( + objectPoints, + imagePoints, + imageSize, + iFixedPoint, + cameraMatrix, + distCoeffs, + rvecs=..., + tvecs=..., + newObjPoints=..., + stdDeviationsIntrinsics=..., + stdDeviationsExtrinsics=..., + stdDeviationsObjPoints=..., + perViewErrors=..., + flags: int | None = ..., + criteria=..., +) -> tuple[ + Incomplete, + _cameraMatrix, + _distCoeffs, + _rvecs, + _tvecs, + _newObjPoints, + _stdDeviationsIntrinsics, + _stdDeviationsExtrinsics, + _stdDeviationsObjPoints, + _perViewErrors, +]: ... + + +def calibrateHandEye( + R_gripper2base, t_gripper2base, R_target2cam, t_target2cam, R_cam2gripper=..., t_cam2gripper=..., method: int = ..., +) -> tuple[_R_cam2gripper, _t_cam2gripper]: ... +def calibrateRobotWorldHandEye(*args, **kwargs): ... # incomplete + + +def calibrationMatrixValues( + cameraMatrix, imageSize, apertureWidth, apertureHeight, +) -> tuple[_fovx, _fovy, _focalLength, _principalPoint, _aspectRatio]: ... +def cartToPolar(x, y, magnitude=..., angle=..., angleInDegrees=...) -> tuple[_magnitude, _angle]: ... +def checkChessboard(img: Mat, size): ... +def checkHardwareSupport(feature): ... +def checkRange(a, quiet=..., minVal=..., maxVal=...) -> tuple[Incomplete, _pos]: ... +def circle(img: Mat, center, radius, color, thickness=..., lineType=..., shift=...) -> _img: ... +def clipLine(imgRect, pt1, pt2) -> tuple[Incomplete, _pt1, _pt2]: ... +def colorChange(src: Mat, mask: Mat, dst: Mat = ..., red_mul=..., green_mul=..., blue_mul=...) -> _dst: ... +def compare(src1: Mat, src2: Mat, cmpop, dst: Mat = ...) -> _dst: ... +def compareHist(H1: Mat, H2: Mat, method: int) -> float: ... +def completeSymm(m, lowerToUpper=...) -> _m: ... + + +def composeRT( + rvec1, + tvec1, + rvec2, + tvec2, + rvec3=..., + tvec3=..., + dr3dr1=..., + dr3dt1=..., + dr3dr2=..., + dr3dt2=..., + dt3dr1=..., + dt3dt1=..., + dt3dr2=..., + dt3dt2=..., +) -> tuple[_rvec3, _tvec3, _dr3dr1, _dr3dt1, _dr3dr2, _dr3dt2, _dt3dr1, _dt3dt1, _dt3dr2, _dt3dt2]: ... +def computeCorrespondEpilines(points, whichImage, F, lines=...) -> _lines: ... +def computeECC(templateImage, inputImage, inputMask=...): ... +def connectedComponents(image: Mat, labels=..., connectivity=..., ltype=...) -> tuple[Incomplete, _labels]: ... + + +def connectedComponentsWithAlgorithm( + image: Mat, + connectivity, + ltype, + ccltype, + labels=..., +) -> tuple[ + Incomplete, + _labels, +]: ... + + +def connectedComponentsWithStats( + image: Mat, labels=..., stats=..., centroids=..., connectivity=..., ltype=..., +) -> tuple[Incomplete, _labels, _stats, _centroids]: ... + + +def connectedComponentsWithStatsWithAlgorithm( + image: Mat, connectivity, ltype, ccltype, labels=..., stats=..., centroids=..., +) -> tuple[Incomplete, _labels, _stats, _centroids]: ... +@overload +def contourArea(approx): ... +@overload +def contourArea(contour, oriented=...): ... +def convertFp16(src: Mat, dst: Mat = ...) -> _dst: ... + + +def convertMaps( + map1, + map2, + dstmap1type, + dstmap1=..., + dstmap2=..., + nninterpolation=..., +) -> tuple[ + _dstmap1, + _dstmap2, +]: ... + + +def convertPointsFromHomogeneous(src: Mat, dst: Mat = ...) -> _dst: ... +def convertPointsToHomogeneous(src: Mat, dst: Mat = ...) -> _dst: ... +def convertScaleAbs(src: Mat, dst: Mat = ..., alpha=..., beta=...) -> _dst: ... +def convexHull(points, hull=..., clockwise=..., returnPoints=...) -> _hull: ... +def convexityDefects(contour, convexhull, convexityDefects=...) -> _convexityDefects: ... +def copyMakeBorder(src: Mat, top, bottom, left, right, borderType, dst: Mat = ..., value=...) -> _dst: ... +def copyTo(src: Mat, mask: Mat, dst: Mat = ...) -> _dst: ... +def cornerEigenValsAndVecs(src: Mat, blockSize, ksize, dst: Mat = ..., borderType=...) -> _dst: ... +def cornerHarris(src: Mat, blockSize, ksize, k, dst: Mat = ..., borderType=...) -> _dst: ... +def cornerMinEigenVal(src: Mat, blockSize, dst: Mat = ..., ksize=..., borderType=...) -> _dst: ... +def cornerSubPix(image: Mat, corners, winSize, zeroZone, criteria) -> _corners: ... +def correctMatches(F, points1, points2, newPoints1=..., newPoints2=...) -> tuple[_newPoints1, _newPoints2]: ... +def countNonZero(src: Mat | _NumericScalar) -> int: ... +def createAlignMTB(max_bits=..., exclude_range=..., cut=...): ... +def createBackgroundSubtractorKNN(history=..., dist2Threshold=..., detectShadows=...): ... +def createBackgroundSubtractorMOG2(history=..., varThreshold=..., detectShadows=...): ... +def createButton(buttonName, onChange, userData=..., buttonType=..., initialButtonState=...) -> None: ... +def createCLAHE(clipLimit=..., tileGridSize=...): ... +def createCalibrateDebevec(samples=..., lambda_=..., random=...): ... +def createCalibrateRobertson(max_iter=..., threshold=...): ... +def createGeneralizedHoughBallard(): ... +def createGeneralizedHoughGuil(): ... +def createHanningWindow(winSize, type, dst: Mat = ...) -> _dst: ... + + +def createLineSegmentDetector( + _refine=..., _scale=..., _sigma_scale=..., _quant=..., _ang_th=..., _log_eps=..., _density_th=..., _n_bins=..., +): ... +def createMergeDebevec(): ... +def createMergeMertens(contrast_weight=..., saturation_weight=..., exposure_weight=...): ... +def createMergeRobertson(): ... +def createTonemap(gamma=...): ... +def createTonemapDrago(gamma=..., saturation=..., bias=...): ... +def createTonemapMantiuk(gamma=..., scale=..., saturation=...): ... +def createTonemapReinhard(gamma=..., intensity=..., light_adapt=..., color_adapt=...): ... +def createTrackbar(trackbarName, windowName, value, count, onChange) -> None: ... +def cubeRoot(val): ... +def cvtColor(src: Mat, code: int, dst: Mat = ..., dstCn: int = ...) -> Mat: ... +def cvtColorTwoPlane(src1: Mat, src2: Mat, code: int, dst: Mat = ...) -> _dst: ... +def dct(src: Mat, dst: Mat = ..., flags: int | None = ...) -> _dst: ... +def decolor(src: Mat, grayscale=..., color_boost=...) -> tuple[_grayscale, _color_boost]: ... +def decomposeEssentialMat(E, R1=..., R2=..., t=...) -> tuple[_R1, _R2, _t]: ... + + +def decomposeHomographyMat( + H, K, rotations=..., translations=..., normals=..., +) -> tuple[Incomplete, _rotations, _translations, _normals]: ... + + +def decomposeProjectionMatrix( + projMatrix, + cameraMatrix=..., + rotMatrix=..., + transVect=..., + rotMatrixX=..., + rotMatrixY=..., + rotMatrixZ=..., + eulerAngles=..., +) -> tuple[ + _cameraMatrix, + _rotMatrix, + _transVect, + _rotMatrixX, + _rotMatrixY, + _rotMatrixZ, + _eulerAngles, +]: ... + + +def demosaicing(src: Mat, code: int, dst: Mat = ..., dstCn: int = ...) -> _dst: ... +def denoise_TVL1(observations, result, lambda_=..., niters=...) -> None: ... +def destroyAllWindows() -> None: ... +def destroyWindow(winname) -> None: ... +def detailEnhance(src: Mat, dst: Mat = ..., sigma_s=..., sigma_r=...) -> _dst: ... +def determinant(mtx): ... +def dft(src: Mat, dst: Mat = ..., flags: int | None = ..., nonzeroRows=...) -> _dst: ... +def dilate(src: Mat, kernel, dst: Mat = ..., anchor=..., iterations=..., borderType=..., borderValue=...) -> _dst: ... +def displayOverlay(winname, text, delayms=...) -> None: ... +def displayStatusBar(winname, text, delayms=...) -> None: ... +def distanceTransform(src: Mat, distanceType, maskSize, dst: Mat = ..., dstType=...) -> _dst: ... + + +def distanceTransformWithLabels( + src: Mat, distanceType, maskSize, dst: Mat = ..., labels=..., labelType=..., +) -> tuple[_dst, _labels]: ... +def divSpectrums(*args, **kwargs): ... # incomplete +@overload +def divide(src1: Mat, src2: Mat, dst: Mat = ..., scale=..., dtype=...) -> _dst: ... +@overload +def divide(scale, src2, dst=..., dtype=...) -> _dst: ... +def dnn_registerLayer() -> None: ... +def dnn_unregisterLayer() -> None: ... +def drawChessboardCorners(image: Mat, patternSize, corners, patternWasFound) -> _image: ... + + +def drawContours( + image: Mat, contours, contourIdx, color, thickness=..., lineType=..., hierarchy=..., maxLevel=..., offset=..., +) -> _image: ... +def drawFrameAxes(image: Mat, cameraMatrix, distCoeffs, rvec, tvec, length, thickness=...) -> _image: ... +def drawKeypoints(image: Mat, keypoints, outImage, color=..., flags: int | None = ...) -> _outImage: ... +def drawMarker(img: Mat, position, color, markerType=..., markerSize=..., thickness=..., line_type=...) -> _img: ... + + +def drawMatches( + img1, + keypoints1, + img2, + keypoints2, + matches1to2, + outImg, + matchColor=..., + singlePointColor=..., + matchesMask=..., + flags: int | None = ..., +) -> _outImg: ... + + +def drawMatchesKnn( + img1, + keypoints1, + img2, + keypoints2, + matches1to2, + outImg, + matchColor=..., + singlePointColor=..., + matchesMask=..., + flags: int | None = ..., +) -> _outImg: ... +def edgePreservingFilter(src: Mat, dst: Mat = ..., flags: int | None = ..., sigma_s=..., sigma_r=...) -> _dst: ... +def eigen(src: Mat, eigenvalues=..., eigenvectors=...) -> tuple[Incomplete, _eigenvalues, _eigenvectors]: ... +def eigenNonSymmetric(src: Mat, eigenvalues=..., eigenvectors=...) -> tuple[_eigenvalues, _eigenvectors]: ... + + +@overload +def ellipse( + img: Mat, + center, + axes, + angle, + startAngle, + endAngle, + color, + thickness=..., + lineType=..., + shift=..., +) -> _img: ... + + +@overload +def ellipse(img, box, color, thickness=..., lineType=...) -> _img: ... +def ellipse2Poly(center, axes, angle, arcStart, arcEnd, delta) -> _pts: ... +def empty_array_desc(*args, **kwargs): ... # incomplete +def empty_gopaque_desc(*args, **kwargs): ... # incomplete +def empty_scalar_desc(*args, **kwargs): ... # incomplete +def equalizeHist(src: Mat, dst: Mat = ...) -> _dst: ... +def erode(src: Mat, kernel, dst: Mat = ..., anchor=..., iterations=..., borderType=..., borderValue=...) -> _dst: ... + + +def estimateAffine2D( + from_, to, inliers=..., method: int = ..., ransacReprojThreshold=..., maxIters=..., confidence=..., refineIters=..., +) -> tuple[Incomplete, _inliers]: ... + + +def estimateAffine3D( + src: Mat, dst: Mat, out=..., inliers=..., ransacThreshold=..., confidence=..., +) -> tuple[Incomplete, _out, _inliers]: ... + + +def estimateAffinePartial2D( + from_, to, inliers=..., method: int = ..., ransacReprojThreshold=..., maxIters=..., confidence=..., refineIters=..., +) -> tuple[Incomplete, _inliers]: ... + + +def estimateChessboardSharpness( + image: Mat, patternSize, corners, rise_distance=..., vertical=..., sharpness=..., +) -> tuple[Incomplete, _sharpness]: ... + + +def estimateTranslation3D( + src: Mat, dst: Mat, out=..., inliers=..., ransacThreshold=..., confidence=..., +) -> tuple[Incomplete, _out, _inliers]: ... +def exp(src: Mat, dst: Mat = ...) -> _dst: ... +def extractChannel(src: Mat, coi, dst: Mat = ...) -> _dst: ... +def fastAtan2(y, x): ... +@overload +def fastNlMeansDenoising(src: Mat, dst: Mat = ..., h=..., templateWindowSize=..., searchWindowSize=...) -> _dst: ... +@overload +def fastNlMeansDenoising(src, h, dst=..., templateWindowSize=..., searchWindowSize=..., normType=...) -> _dst: ... + + +def fastNlMeansDenoisingColored( + src: Mat, dst: Mat = ..., h=..., hColor=..., templateWindowSize=..., searchWindowSize=..., +) -> _dst: ... + + +def fastNlMeansDenoisingColoredMulti( + srcImgs, + imgToDenoiseIndex, + temporalWindowSize, + dst: Mat = ..., + h=..., + hColor=..., + templateWindowSize=..., + searchWindowSize=..., +) -> _dst: ... + + +@overload +def fastNlMeansDenoisingMulti( + srcImgs, imgToDenoiseIndex, temporalWindowSize, dst: Mat = ..., h=..., templateWindowSize=..., searchWindowSize=..., +) -> _dst: ... + + +@overload +def fastNlMeansDenoisingMulti( + srcImgs, + imgToDenoiseIndex, + temporalWindowSize, + h, + dst=..., + templateWindowSize=..., + searchWindowSize=..., + normType=..., +) -> _dst: ... + + +def fillConvexPoly(img: Mat, points, color, lineType=..., shift=...) -> _img: ... +def fillPoly(img: Mat, pts, color, lineType=..., shift=..., offset=...) -> _img: ... +def filter2D(src: Mat, ddepth, kernel, dst: Mat = ..., anchor=..., delta=..., borderType=...) -> _dst: ... + + +def filterHomographyDecompByVisibleRefpoints( + rotations, normals, beforePoints, afterPoints, possibleSolutions=..., pointsMask=..., +) -> _possibleSolutions: ... +def filterSpeckles(img: Mat, newVal, maxSpeckleSize, maxDiff, buf=...) -> tuple[_img, _buf]: ... +def find4QuadCornerSubpix(img: Mat, corners, region_size) -> tuple[Incomplete, _corners]: ... + + +def findChessboardCorners( + image: Mat, + patternSize, + corners=..., + flags: int | None = ..., +) -> tuple[ + Incomplete, + _corners, +]: ... + + +def findChessboardCornersSB( + image: Mat, + patternSize, + corners=..., + flags: int | None = ..., +) -> tuple[ + Incomplete, + _corners, +]: ... + + +def findChessboardCornersSBWithMeta( + image: Mat, patternSize, flags: int | None, corners=..., meta=..., +) -> tuple[Incomplete, _corners, _meta]: ... + + +@overload +def findCirclesGrid( + image: Mat, patternSize, flags: int | None, blobDetector, parameters, centers=..., +) -> tuple[Incomplete, _centers]: ... +@overload +def findCirclesGrid(image, patternSize, centers=..., flags=..., blobDetector=...) -> tuple[Incomplete, _centers]: ... + + +def findContours( + image: Mat, + mode, + method: int, + contours=..., + hierarchy=..., + offset=..., +) -> tuple[ + _contours, + _hierarchy, +]: ... + + +@overload +def findEssentialMat( + points1, points2, cameraMatrix, method: int = ..., prob=..., threshold=..., mask: Mat = ..., +) -> tuple[Incomplete, _mask]: ... + + +@overload +def findEssentialMat( + points1, points2, focal=..., pp=..., method=..., prob=..., threshold=..., mask=..., +) -> tuple[Incomplete, _mask]: ... + + +@overload +def findFundamentalMat( + points1, points2, method: int, ransacReprojThreshold, confidence, maxIters, mask: Mat = ..., +) -> tuple[Incomplete, _mask]: ... + + +@overload +def findFundamentalMat( + points1, points2, method=..., ransacReprojThreshold=..., confidence=..., mask=..., +) -> tuple[Incomplete, _mask]: ... + + +def findHomography( + srcPoints, dstPoints, method: int = ..., ransacReprojThreshold=..., mask: Mat = ..., maxIters=..., confidence=..., +) -> tuple[Incomplete, _mask]: ... +def findNonZero(src: Mat, idx=...) -> _idx: ... + + +def findTransformECC( + templateImage, inputImage, warpMatrix, motionType, criteria, inputMask, gaussFiltSize, +) -> tuple[Incomplete, _warpMatrix]: ... +def fitEllipse(points): ... +def fitEllipseAMS(points): ... +def fitEllipseDirect(points): ... +def fitLine(points, distType, param, reps, aeps, line=...) -> _line: ... +def flip(src: Mat, flipCode, dst: Mat = ...) -> _dst: ... + + +def floodFill( + image: Mat, mask: Mat | None, seedPoint, newVal, loDiff=..., upDiff=..., flags: int | None = ..., +) -> tuple[Incomplete, _image, _mask, _rect]: ... +def gemm(src1: Mat, src2: Mat, alpha, src3, beta, dst: Mat = ..., flags: int | None = ...) -> _dst: ... +def getAffineTransform(src: Mat, dst: Mat): ... +def getBuildInformation(): ... +def getCPUFeaturesLine(): ... +def getCPUTickCount(): ... +def getDefaultNewCameraMatrix(cameraMatrix, imgsize=..., centerPrincipalPoint=...): ... +def getDerivKernels(dx, dy, ksize, kx=..., ky=..., normalize=..., ktype=...) -> tuple[_kx, _ky]: ... +def getFontScaleFromHeight(fontFace, pixelHeight, thickness=...): ... +def getGaborKernel(ksize, sigma, theta, lambd, gamma, psi=..., ktype=...): ... +def getGaussianKernel(ksize, sigma, ktype=...): ... +def getHardwareFeatureName(feature): ... +def getLogLevel(*args, **kwargs): ... # incomplete +def getNumThreads(): ... +def getNumberOfCPUs(): ... +def getOptimalDFTSize(vecsize): ... + + +def getOptimalNewCameraMatrix( + cameraMatrix, distCoeffs, imageSize, alpha, newImgSize=..., centerPrincipalPoint=..., +) -> tuple[Incomplete, _validPixROI]: ... +def getPerspectiveTransform(src: Mat, dst: Mat, solveMethod=...): ... +def getRectSubPix(image: Mat, patchSize, center, patch=..., patchType=...) -> _patch: ... +def getRotationMatrix2D(center, angle, scale): ... +def getStructuringElement(shape, ksize, anchor=...): ... +def getTextSize(text, fontFace, fontScale, thickness) -> tuple[Incomplete, _baseLine]: ... +def getThreadNum(): ... +def getTickCount(): ... +def getTickFrequency(): ... +def getTrackbarPos(trackbarname, winname): ... +def getValidDisparityROI(roi1, roi2, minDisparity, numberOfDisparities, blockSize): ... +def getVersionMajor(): ... +def getVersionMinor(): ... +def getVersionRevision(): ... +def getVersionString(): ... +def getWindowImageRect(winname): ... +def getWindowProperty(winname, prop_id): ... + + +@overload +def goodFeaturesToTrack( + image: Mat, + maxCorners, + qualityLevel, + minDistance, + corners=..., + mask: Mat = ..., + blockSize=..., + useHarrisDetector=..., + k=..., +) -> _corners: ... + + +@overload +def goodFeaturesToTrack( + image, + maxCorners, + qualityLevel, + minDistance, + mask, + blockSize, + gradientSize, + corners=..., + useHarrisDetector=..., + k=..., +) -> _corners: ... + + +def goodFeaturesToTrackWithQuality(*args, **kwargs): ... # incomplete + + +def grabCut( + img: Mat, + mask: Mat | None, + rect, + bgdModel, + fgdModel, + iterCount, + mode=..., +) -> tuple[ + _mask, + _bgdModel, + _fgdModel, +]: ... + + +def groupRectangles(rectList, groupThreshold, eps=...) -> tuple[_rectList, _weights]: ... +def haveImageReader(filename: str): ... +def haveImageWriter(filename: str): ... +def haveOpenVX(): ... +def hconcat(src: Mat | Sequence[Mat], dst: Mat = ...) -> _dst: ... +def idct(src: Mat, dst: Mat = ..., flags: int | None = ...) -> _dst: ... +def idft(src: Mat, dst: Mat = ..., flags: int | None = ..., nonzeroRows=...) -> _dst: ... +def illuminationChange(src: Mat, mask: Mat, dst: Mat = ..., alpha=..., beta=...) -> _dst: ... +def imcount(*args, **kwargs): ... # incomplete +def imdecode(buf, flags: int | None): ... +def imencode(ext, img: Mat, params=...) -> tuple[Incomplete, _buf]: ... +def imread(filename: str, flags: int | None = ...) -> Mat: ... +def imreadmulti(filename: str, mats=..., flags: int | None = ...) -> tuple[Incomplete, _mats]: ... +def imshow(winname, mat) -> None: ... +def imwrite(filename: str, img: Mat, params: Sequence[int] = ...) -> bool: ... +def imwritemulti(*args, **kwargs): ... # incomplete +def inRange(src: Mat, lowerBound: Mat, upperbBound: Mat, dst: Mat = ...) -> Mat: ... +def initCameraMatrix2D(objectPoints, imagePoints, imageSize, aspectRatio=...): ... +def initInverseRectificationMap(*args, **kwargs): ... # incomplete + + +def initUndistortRectifyMap( + cameraMatrix, distCoeffs, R, newCameraMatrix, size, m1type, map1=..., map2=..., +) -> tuple[_map1, _map2]: ... +def inpaint(src: Mat, inpaintMask, inpaintRadius, flags: int | None, dst: Mat = ...) -> _dst: ... +def insertChannel(src: Mat, dst: Mat, coi) -> _dst: ... +def integral(src: Mat, sum=..., sdepth=...) -> _sum: ... +def integral2(src: Mat, sum=..., sqsum=..., sdepth=..., sqdepth=...) -> tuple[_sum, _sqsum]: ... +def integral3(src: Mat, sum=..., sqsum=..., tilted=..., sdepth=..., sqdepth=...) -> tuple[_sum, _sqsum, _tilted]: ... +def intersectConvexConvex(_p1, _p2, _p12=..., handleNested=...) -> tuple[Incomplete, _p12]: ... +def invert(src: Mat, dst: Mat = ..., flags: int | None = ...) -> tuple[Incomplete, _dst]: ... +def invertAffineTransform(M, iM=...) -> _iM: ... +def isContourConvex(contour): ... + + +def kmeans( + data, K, bestLabels, criteria, attempts, flags: int | None, centers=..., +) -> tuple[Incomplete, _bestLabels, _centers]: ... +def line(img: Mat, pt1, pt2, color, thickness=..., lineType=..., shift=...) -> _img: ... +def linearPolar(src: Mat, center, maxRadius, flags: int | None, dst: Mat = ...) -> _dst: ... +def log(src: Mat, dst: Mat = ...) -> _dst: ... +def logPolar(src: Mat, center, M, flags: int | None, dst: Mat = ...) -> _dst: ... +def magnitude(x, y, magnitude=...) -> _magnitude: ... +def matMulDeriv(A, B, dABdA=..., dABdB=...) -> tuple[_dABdA, _dABdB]: ... +def matchShapes(contour1, contour2, method: int, parameter): ... +def matchTemplate(image: Mat, templ: Mat, method: int, result: Mat = ..., mask: Mat | None = ...) -> Mat: ... +def max(src1: Mat, src2: Mat, dst: Mat = ...) -> _dst: ... +def mean(src: Mat, mask: Mat = ...): ... +def meanShift(probImage, window, criteria) -> tuple[Incomplete, _window]: ... +def meanStdDev(src: Mat, mean=..., stddev=..., mask: Mat = ...) -> tuple[_mean, _stddev]: ... +def medianBlur(src: Mat, ksize, dst: Mat = ...) -> _dst: ... +def merge(mv, dst: Mat = ...) -> _dst: ... +def min(src1: Mat, src2: Mat, dst: Mat = ...) -> _dst: ... +def minAreaRect(points): ... +def minEnclosingCircle(points) -> tuple[_center, _radius]: ... +def minEnclosingTriangle(points, triangle=...) -> tuple[Incomplete, _triangle]: ... +def minMaxLoc(src: Mat, mask: Mat = ...) -> tuple[float, float, tuple[int, int], tuple[int, int]]: ... +def mixChannels(src: Mat, dst: Mat, fromTo) -> _dst: ... +def moments(array, binaryImage=...): ... + + +def morphologyEx( + src: Mat, + op, + kernel, + dst: Mat = ..., + anchor=..., + iterations=..., + borderType=..., + borderValue=..., +) -> _dst: ... + + +def moveWindow(winname, x, y) -> None: ... +def mulSpectrums(a, b, flags: int | None, c=..., conjB=...) -> _c: ... +def mulTransposed(src: Mat, aTa, dst: Mat = ..., delta=..., scale=..., dtype=...) -> _dst: ... +def multiply(src1: Mat, src2: Mat, dst: Mat = ..., scale=..., dtype=...) -> _dst: ... +def namedWindow(winname, flags: int | None = ...) -> None: ... +@overload +def norm(src1: Mat, src2: Mat, normType: int = ..., mask: Mat | None = ...) -> float: ... +@overload +def norm(src1: Mat, src2: Mat, mask: Mat | None = ...) -> float: ... +def normalize(src: Mat, dst: Mat, alpha=..., beta=..., norm_type: int = ..., dtype=..., mask: Mat = ...) -> Mat: ... +def patchNaNs(a, val=...) -> _a: ... + + +def pencilSketch( + src: Mat, dst1: Mat = ..., dst2: Mat = ..., sigma_s=..., sigma_r=..., shade_factor=..., +) -> tuple[_dst1, _dst2]: ... +def perspectiveTransform(src: Mat, m, dst: Mat = ...) -> _dst: ... +def phase(x, y, angle=..., angleInDegrees=...) -> _angle: ... +def phaseCorrelate(src1: Mat, src2: Mat, window=...) -> tuple[Incomplete, _response]: ... +def pointPolygonTest(contour, pt, measureDist): ... +def polarToCart(magnitude, angle, x=..., y=..., angleInDegrees=...) -> tuple[_x, _y]: ... +def pollKey(*args, **kwargs): ... # incomplete +def polylines(img: Mat, pts, isClosed, color, thickness=..., lineType=..., shift=...) -> _img: ... +def pow(src: Mat, power, dst: Mat = ...) -> _dst: ... +def preCornerDetect(src: Mat, ksize, dst: Mat = ..., borderType=...) -> _dst: ... + + +def projectPoints( + objectPoints, rvec, tvec, cameraMatrix, distCoeffs, imagePoints=..., jacobian=..., aspectRatio=..., +) -> tuple[_imagePoints, _jacobian]: ... + + +def putText( + img: Mat, + text, + org, + fontFace, + fontScale, + color, + thickness=..., + lineType=..., + bottomLeftOrigin=..., +) -> _img: ... + + +def pyrDown(src: Mat, dst: Mat = ..., dstsize=..., borderType=...) -> _dst: ... +def pyrMeanShiftFiltering(src: Mat, sp, sr, dst: Mat = ..., maxLevel=..., termcrit=...) -> _dst: ... +def pyrUp(src: Mat, dst: Mat = ..., dstsize=..., borderType=...) -> _dst: ... +def randShuffle(dst: Mat, iterFactor=...) -> _dst: ... +def randn(dst: Mat, mean, stddev) -> _dst: ... +def randu(dst: Mat, low, high) -> _dst: ... +def readOpticalFlow(path): ... +@overload +def recoverPose(points1, points2, cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, E, R, t, mask): ... + + +@overload +def recoverPose( + E, + points1, + points2, + cameraMatrix, + R=..., + t=..., + mask: Mat = ..., +) -> tuple[ + Incomplete, + _R, + _t, + _mask, +]: ... + + +@overload +def recoverPose(E, points1, points2, R=..., t=..., focal=..., pp=..., mask=...) -> tuple[Incomplete, _R, _t, _mask]: ... + + +@overload +def recoverPose( + E, points1, points2, cameraMatrix, distanceThresh, R=..., t=..., mask=..., triangulatedPoints=..., +) -> tuple[Incomplete, _R, _t, _mask, _triangulatedPoints]: ... +@overload +def rectangle(img: Mat, pt1: _Point, pt2: _Point, color, thickness=..., lineType=..., shift=...) -> Mat: ... +@overload +def rectangle(img: Mat, rec: _Rect, color, thickness=..., lineType=..., shift=...) -> Mat: ... + + +def rectify3Collinear( + cameraMatrix1, + distCoeffs1, + cameraMatrix2, + distCoeffs2, + cameraMatrix3, + distCoeffs3, + imgpt1, + imgpt3, + imageSize, + R12, + T12, + R13, + T13, + alpha, + newImgSize, + flags: int | None, + R1=..., + R2=..., + R3=..., + P1=..., + P2=..., + P3=..., + Q=..., +) -> tuple[Incomplete, _R1, _R2, _R3, _P1, _P2, _P3, _Q, _roi1, _roi2]: ... +def redirectError(onError) -> None: ... +def reduce(src: Mat, dim, rtype, dst: Mat = ..., dtype=...) -> _dst: ... +def reduceArgMax(*args, **kwargs): ... # incomplete +def reduceArgMin(*args, **kwargs): ... # incomplete +def remap(src: Mat, map1, map2, interpolation: int, dst: Mat = ..., borderMode=..., borderValue=...) -> _dst: ... +def repeat(src: Mat, ny, nx, dst: Mat = ...) -> _dst: ... +def reprojectImageTo3D(disparity, Q, _3dImage=..., handleMissingValues=..., ddepth=...) -> _3dImage: ... + + +def resize( + src: Mat | int | bool, + dsize: _Size | None, + dst: Mat | _NumericScalar = ..., + fx: float = ..., + fy: float = ..., + interpolation: int = ..., +) -> Mat: ... +@overload +def resizeWindow(winname, width, height) -> None: ... +@overload +def resizeWindow(winname, size) -> None: ... +def rotate(src: Mat, rotateCode, dst: Mat = ...) -> _dst: ... +def rotatedRectangleIntersection(rect1, rect2, intersectingRegion=...) -> tuple[Incomplete, _intersectingRegion]: ... +def sampsonDistance(pt1, pt2, F): ... +def scaleAdd(src1: Mat, alpha, src2: Mat, dst: Mat = ...) -> _dst: ... +def seamlessClone(src: Mat, dst: Mat, mask: Mat | None, p, flags: int | None, blend=...) -> _blend: ... +@overload +def selectROI(windowName, img: Mat, showCrosshair=..., fromCenter=...): ... +@overload +def selectROI(img: Mat, showCrosshair=..., fromCenter=...): ... +def selectROIs(windowName, img: Mat, showCrosshair=..., fromCenter=...) -> _boundingBoxes: ... +def sepFilter2D(src: Mat, ddepth, kernelX, kernelY, dst: Mat = ..., anchor=..., delta=..., borderType=...) -> _dst: ... +def setIdentity(mtx, s=...) -> _mtx: ... +def setLogLevel(*args, **kwargs): ... # incomplete +def setMouseCallback(windowName, onMouse, param=...) -> None: ... +def setNumThreads(nthreads) -> None: ... +def setRNGSeed(seed) -> None: ... +def setTrackbarMax(trackbarname, winname, maxval) -> None: ... +def setTrackbarMin(trackbarname, winname, minval) -> None: ... +def setTrackbarPos(trackbarname, winname, pos) -> None: ... +def setUseOpenVX(flag) -> None: ... +def setUseOptimized(onoff) -> None: ... +def setWindowProperty(winname, prop_id, prop_value) -> None: ... +def setWindowTitle(winname, title) -> None: ... +def solve(src1: Mat, src2: Mat, dst: Mat = ..., flags: int | None = ...) -> tuple[Incomplete, _dst]: ... +def solveCubic(coeffs, roots=...) -> tuple[Incomplete, _roots]: ... +def solveLP(Func, Constr, z=...) -> tuple[Incomplete, _z]: ... + + +def solveP3P( + objectPoints, imagePoints, cameraMatrix, distCoeffs, flags: int | None, rvecs=..., tvecs=..., +) -> tuple[Incomplete, _rvecs, _tvecs]: ... + + +def solvePnP( + objectPoints, + imagePoints, + cameraMatrix, + distCoeffs, + rvec=..., + tvec=..., + useExtrinsicGuess=..., + flags: int | None = ..., +) -> tuple[ + Incomplete, + _rvec, + _tvec, +]: ... + + +def solvePnPGeneric( + objectPoints, + imagePoints, + cameraMatrix, + distCoeffs, + rvecs=..., + tvecs=..., + useExtrinsicGuess=..., + flags: int | None = ..., + rvec=..., + tvec=..., + reprojectionError=..., +) -> tuple[Incomplete, _rvecs, _tvecs, _reprojectionError]: ... + + +def solvePnPRansac( + objectPoints, + imagePoints, + cameraMatrix, + distCoeffs, + rvec=..., + tvec=..., + useExtrinsicGuess=..., + iterationsCount=..., + reprojectionError=..., + confidence=..., + inliers=..., + flags: int | None = ..., +) -> tuple[Incomplete, _rvec, _tvec, _inliers]: ... + + +def solvePnPRefineLM( + objectPoints, + imagePoints, + cameraMatrix, + distCoeffs, + rvec, + tvec, + criteria=..., +) -> tuple[ + _rvec, + _tvec, +]: ... + + +def solvePnPRefineVVS( + objectPoints, imagePoints, cameraMatrix, distCoeffs, rvec, tvec, criteria=..., VVSlambda=..., +) -> tuple[_rvec, _tvec]: ... +def solvePoly(coeffs, roots=..., maxIters=...) -> tuple[Incomplete, _roots]: ... +def sort(src: Mat, flags: int | None, dst: Mat = ...) -> _dst: ... +def sortIdx(src: Mat, flags: int | None, dst: Mat = ...) -> _dst: ... +def spatialGradient(src: Mat, dx=..., dy=..., ksize=..., borderType=...) -> tuple[_dx, _dy]: ... +def split(m, mv=...) -> _mv: ... +def sqrBoxFilter(src: Mat, ddepth, ksize, dst: Mat = ..., anchor=..., normalize=..., borderType=...) -> _dst: ... +def sqrt(src: Mat, dst: Mat = ...) -> _dst: ... +def startWindowThread(): ... + + +def stereoCalibrate( + objectPoints, + imagePoints1, + imagePoints2, + cameraMatrix1, + distCoeffs1, + cameraMatrix2, + distCoeffs2, + imageSize, + R=..., + T=..., + E=..., + F=..., + flags: int | None = ..., + criteria=..., +) -> tuple[Incomplete, _cameraMatrix1, _distCoeffs1, _cameraMatrix2, _distCoeffs2, _R, _T, _E, _F]: ... + + +def stereoCalibrateExtended( + objectPoints, + imagePoints1, + imagePoints2, + cameraMatrix1, + distCoeffs1, + cameraMatrix2, + distCoeffs2, + imageSize, + R, + T, + E=..., + F=..., + perViewErrors=..., + flags: int | None = ..., + criteria=..., +) -> tuple[Incomplete, _cameraMatrix1, _distCoeffs1, _cameraMatrix2, _distCoeffs2, _R, _T, _E, _F, _perViewErrors]: ... + + +def stereoRectify( + cameraMatrix1, + distCoeffs1, + cameraMatrix2, + distCoeffs2, + imageSize, + R, + T, + R1=..., + R2=..., + P1=..., + P2=..., + Q=..., + flags: int | None = ..., + alpha=..., + newImageSize=..., +) -> tuple[_R1, _R2, _P1, _P2, _Q, _validPixROI1, _validPixROI2]: ... + + +def stereoRectifyUncalibrated( + points1, + points2, + F, + imgSize, + H1=..., + H2=..., + threshold=..., +) -> tuple[ + Incomplete, + _H1, + _H2, +]: ... + + +def stylization(src: Mat, dst: Mat = ..., sigma_s=..., sigma_r=...) -> _dst: ... + + +def subtract( + src1: Mat | _NumericScalar, + src2: Mat | _NumericScalar, + dst: Mat = ..., + mask: Mat = ..., + dtype=..., +) -> _dst: ... + + +def sumElems(src): ... + + +def textureFlattening( + src: Mat, + mask: Mat, + dst: Mat = ..., + low_threshold=..., + high_threshold=..., + kernel_size=..., +) -> _dst: ... + + +def threshold(src: Mat, thresh, maxval, type, dst: Mat = ...) -> tuple[Incomplete, _dst]: ... +def trace(mtx): ... +def transform(src: Mat, m, dst: Mat = ...) -> _dst: ... +def transpose(src: Mat, dst: Mat = ...) -> _dst: ... +def triangulatePoints(projMatr1, projMatr2, projPoints1, projPoints2, points4D=...) -> _points4D: ... +def undistort(src: Mat, cameraMatrix, distCoeffs, dst: Mat = ..., newCameraMatrix=...) -> _dst: ... +def undistortPoints(src: Mat, cameraMatrix, distCoeffs, dst: Mat = ..., R=..., P=...) -> _dst: ... +def undistortPointsIter(src: Mat, cameraMatrix, distCoeffs, R, P, criteria, dst: Mat = ...) -> _dst: ... +def useOpenVX(): ... +def useOptimized(): ... +def validateDisparity(disparity, cost, minDisparity, numberOfDisparities, disp12MaxDisp=...) -> _disparity: ... +def vconcat(src: Mat | Sequence[Mat], dst: Mat = ...) -> Mat: ... +def waitKey(delay=...): ... +def waitKeyEx(delay=...): ... + + +def warpAffine( + src: Mat, M, dsize: _Size, _dst: Mat = ..., _flags: int | None = ..., _borderMode=..., _borderValue=..., +) -> _dst: ... + + +def warpPerspective( + src: Mat, M, dsize: _Size, _dst: Mat = ..., _flags: int | None = ..., _borderMode=..., _borderValue=..., +) -> _dst: ... +def warpPolar(src: Mat, dsize: _Size, _center, _maxRadius, _flags: int | None, _dst: Mat = ...) -> _dst: ... +def watershed(image: Mat, markers) -> _markers: ... +def writeOpticalFlow(path, flow): ... diff --git a/typings/cv2/gapi/streaming.pyi b/typings/cv2/gapi/streaming.pyi new file mode 100644 index 00000000..2c49b006 --- /dev/null +++ b/typings/cv2/gapi/streaming.pyi @@ -0,0 +1,16 @@ +from cv2.cv2 import GMat, GOpaqueT, gapi_streaming_queue_capacity +from typing_extensions import TypeAlias + +SYNC_POLICY_DONT_SYNC: int +SYNC_POLICY_DROP: int +sync_policy_dont_sync: int +sync_policy_drop: int + +queue_capacity: TypeAlias = gapi_streaming_queue_capacity + + +def desync(g: GMat) -> GMat: ... +def seqNo(arg1: GMat) -> GOpaqueT: ... +def seq_id(arg1: GMat) -> GOpaqueT: ... +def size(src: GMat) -> GOpaqueT: ... +def timestamp(arg1: GMat) -> GOpaqueT: ... diff --git a/typings/cv2/mat_wrapper/__init__.pyi b/typings/cv2/mat_wrapper/__init__.pyi new file mode 100644 index 00000000..fa91b2f9 --- /dev/null +++ b/typings/cv2/mat_wrapper/__init__.pyi @@ -0,0 +1,19 @@ +import numpy as np +from typing_extensions import TypeAlias + +_Unused: TypeAlias = object + +__all__: list[str] = [] + + +_NDArray: TypeAlias = np.ndarray[float, np.dtype[np.generic]] + +# TODO: Make Mat generic with int or float + + +class Mat(_NDArray): + wrap_channels: bool | None + + def __new__(cls, arr: _NDArray, wrap_channels: bool = ..., **kwargs: _Unused) -> _NDArray: ... + def __init__(self, arr: _NDArray, wrap_channels: bool = ...) -> None: ... + def __array_finalize__(self, obj: _NDArray | None) -> None: ...